From 1d7733bd8068809d3aec8ba527eb46746b0db40a Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:29:50 -0500 Subject: [PATCH 01/47] chore: add doc for tracking feature --- DEVELOPMENT.md | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 DEVELOPMENT.md diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..61a8f6c --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,107 @@ +This branch is designed to verify capacity of setting the clipboard data with images. + +```python +from clipboard import * + + +# required `Pillow` package for testing +# TODO: Get bytes data so we don't need the dependency for testing. +# Or just add it to the `test` extra. +with Image.open(png_file) as img: + output = io.BytesIO() + img.convert("RGB").save(output, "BMP") + # TODO: Why 14? + data = output.getvalue()[14:] + output.close() + + # Here is what we want to replace + win32clipboard.OpenClipboard() + win32clipboard.EmptyClipboard() + win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data) + win32clipboard.CloseClipboard() + + # With this + with clipboard() as cb: + cb.set_clipboard(data, ClipboardFormat.CF_DIB) + # Mime types (idea) + cb.set_clipboard(data, "image/png") + cb.set_clipboard(data, "image/jpeg") + cb.set_clipboard(data, "image/gif") + # Alternative APIs (idea) + cb.set_image(img) + cb.set_image(data) + cb.set_image(path) + # OR this + set_clipboard(data, ClipboardFormat.CF_DIB) + # Mime types (idea) + set_clipboard(data, "image/png") + set_clipboard(data, "image/jpeg") + set_clipboard(data, "image/gif") + # Alternative APIs (idea) + set_image(img) +``` + +Here is the inspiration. I'd like to replace the `win32clipboard` code with `clip-util`. + +```python +#!/usr/bin/env pip-run +"""Takes a PNG file, adds a white background to it, and copies it to the +clipboard. + +Uses `pip-run` to run the script with the required packages installed. +""" +__requires__ = ["Pillow", "pywin32"] + +import io +import sys +from pathlib import Path +from typing import TypeAlias +from typing import Union + +import win32clipboard +from PIL import Image + + +PathLike = Union[str, bytes, Path] +RGBA: TypeAlias = tuple[int, int, int, int] + + +def add_white_bg_to_png( + png_file: PathLike, + copy: bool = True, + open: bool = False, + background_color: RGBA = (255, 255, 255, 255), +) -> None: + """Add a white background to a PNG file and copy it to the clipboard.""" + with Image.open(png_file) as img: + img = img.convert("RGBA") + bg = Image.new("RGBA", img.size, background_color) + img = Image.alpha_composite(bg, img) + + # Copy to clipboard + if copy: + output = io.BytesIO() + img.convert("RGB").save(output, "BMP") + data = output.getvalue()[14:] + output.close() + win32clipboard.OpenClipboard() + win32clipboard.EmptyClipboard() + win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data) + win32clipboard.CloseClipboard() + + # Open the image + if open: + img.show() + print("Image copied to clipboard.") + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(f"Usage: {sys.argv[0]} ") + print( + "Takes a PNG file and adds a white background to it, copying it to the clipboard." + ) + raise SystemExit(1) + + add_white_bg_to_png(sys.argv[1]) +``` From cea6e51f90c10e8e962047ede30728078062bed3 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:51:13 -0500 Subject: [PATCH 02/47] chore: update dev doc --- DEVELOPMENT.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 61a8f6c..2e5e028 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,8 +1,18 @@ This branch is designed to verify capacity of setting the clipboard data with images. +- [ ] Add tests for setting clipboard data with images. +- [ ] Add section in README for setting clipboard data with images. +- [ ] Copy README to readme test. +- [ ] Copy README to docs/source/quickstart.rst + ```python +import io + from clipboard import * +import win32clipboard +from PIL import Image + # required `Pillow` package for testing # TODO: Get bytes data so we don't need the dependency for testing. @@ -21,7 +31,7 @@ with Image.open(png_file) as img: win32clipboard.CloseClipboard() # With this - with clipboard() as cb: + with Clipboard() as cb: cb.set_clipboard(data, ClipboardFormat.CF_DIB) # Mime types (idea) cb.set_clipboard(data, "image/png") From f24be8e1a10bf8ea3338df0c785f8e7fe835b3c2 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:51:46 -0500 Subject: [PATCH 03/47] chore: update todo comments --- src/clipboard/clipboard.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 7977345..9b6be63 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -1,12 +1,14 @@ """Clipboard module. +FIXME: Should be able to set bytes, not just a string. TODO: Make `get_clipboard`'s default to check available formats instead of just using the default format. FIXME: Copying using normal methods and then using `get_clipboard` doesn't work properly. It gives a bunch of null bytes as a string. FIXME: HTML_Format doesn't work for some reason. Fix this. FIXME: Typing appears to be off. - * There are a lot of parameters with a default of None that are not Optional. + * There are a lot of parameters with a default of None that are not + Optional. * There are some parameters typed as `int`, but they look like they should be `Optional[HANDLE]` instead. """ From 2212c329fc9ad0c6a36a75d372ee2e00b0b4d800 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:52:12 -0500 Subject: [PATCH 04/47] fix: fix type errors --- src/clipboard/clipboard.py | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 9b6be63..2befe43 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -37,6 +37,7 @@ from clipboard.errors import EmptyClipboardError from clipboard.errors import FormatNotSupportedError from clipboard.errors import GetClipboardError +from clipboard.errors import GetFormatsError from clipboard.errors import LockError from clipboard.errors import OpenClipboardError from clipboard.errors import SetClipboardError @@ -65,7 +66,9 @@ logger.addHandler(file_handler) -def get_clipboard(format: Optional[Union[int, ClipboardFormat]] = None) -> Optional[Union[str, bytes]]: +def get_clipboard( + format: Optional[Union[int, ClipboardFormat]] = None, +) -> Optional[Union[str, bytes]]: """Conveniency wrapper to get clipboard. Instead of using the `Clipboard.default_format`, this function uses the @@ -80,23 +83,37 @@ def get_clipboard(format: Optional[Union[int, ClipboardFormat]] = None) -> Optio def set_clipboard( - content: str, format: Union[int, ClipboardFormat] = None -) -> None: - """Conveniency wrapper to set clipboard.""" + content: str, + format: Optional[Union[int, ClipboardFormat]] = None, +) -> HANDLE: + """Conveniency wrapper to set clipboard. + + Raises + ------ + SetClipboardError + If setting the clipboard failed. + """ with Clipboard() as cb: return cb.set_clipboard(content=content, format=format) + raise SetClipboardError("Setting the clipboard failed.") def get_available_formats() -> list[int]: - """Conveniency wrapper to get available formats.""" + """Conveniency wrapper to get available formats. + + Raises + ------ + OpenClipboardError + If the context manager caught an error. + """ with Clipboard() as cb: return cb.available_formats() + raise GetFormatsError("Failed to get available formats.") class Clipboard: default_format: ClipboardFormat = ClipboardFormat.CF_UNICODETEXT - def __init__(self, format: Union[ClipboardFormat, str, int] = None): if format is None: format = self.default_format.value else: From b46760b4a2cf325fd27b6e77f4fe6e5eccd31382 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:52:57 -0500 Subject: [PATCH 05/47] feat: add error for failure to get formats --- src/clipboard/errors.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/clipboard/errors.py b/src/clipboard/errors.py index 188e0d7..8551dde 100644 --- a/src/clipboard/errors.py +++ b/src/clipboard/errors.py @@ -27,3 +27,7 @@ class ClipboardError(Exception): class LockError(Exception): """Exception raised when locking the clipboard fails.""" + + +class GetFormatsError(Exception): + """Exception raised when getting clipboard formats fails.""" From 6014c618e4c7964456705437f4286a5dc9d2095b Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:53:10 -0500 Subject: [PATCH 06/47] feat: add error to root public api --- src/clipboard/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/clipboard/__init__.py b/src/clipboard/__init__.py index 3b348f3..c7363d3 100644 --- a/src/clipboard/__init__.py +++ b/src/clipboard/__init__.py @@ -7,6 +7,7 @@ from clipboard.errors import EmptyClipboardError from clipboard.errors import FormatNotSupportedError from clipboard.errors import GetClipboardError +from clipboard.errors import GetFormatsError from clipboard.errors import LockError from clipboard.errors import OpenClipboardError from clipboard.errors import SetClipboardError @@ -29,6 +30,7 @@ "EmptyClipboardError", "FormatNotSupportedError", "GetClipboardError", + "GetFormatsError", "LockError", "OpenClipboardError", "SetClipboardError", From 66a3107c804c0be25fb88920c5e96aacf7928328 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:53:44 -0500 Subject: [PATCH 07/47] fix: fix type errors --- src/clipboard/clipboard.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 2befe43..5d82098 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -114,6 +114,10 @@ def get_available_formats() -> list[int]: class Clipboard: default_format: ClipboardFormat = ClipboardFormat.CF_UNICODETEXT + def __init__( + self, + format: Optional[Union[ClipboardFormat, str, int]] = None, + ): if format is None: format = self.default_format.value else: @@ -150,7 +154,7 @@ def get_formats() -> List[int]: return available_formats def get_clipboard( - self, format: Union[int, ClipboardFormat] = None + self, format: Optional[Union[int, ClipboardFormat]] = None ) -> Optional[Union[str, bytes]]: """Get data from clipboard, returning None if nothing is on it. @@ -178,11 +182,12 @@ def get_clipboard( formats = self.available_formats() if format not in formats: raise FormatNotSupportedError( - f"{format} is not supported for getting the clipboard. Choose from following {formats}" + f"{format} is not supported for getting the clipboard." + f" Choose from following {formats}" ) # Info - self.h_clip_mem: HANDLE = GetClipboardData(format) + self.h_clip_mem = GetClipboardData(format) if self.h_clip_mem is None: raise GetClipboardError("The `GetClipboardData` function failed.") self.address = self._lock(self.h_clip_mem) # type: ignore @@ -195,7 +200,8 @@ def get_clipboard( # A handle to the locale identifier associated with the text in the # clipboard. # TODO: There are other types that could be supported as well, such as - # audio data: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats + # audio data: + # https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats string: ctypes.Array[ctypes.c_byte] content: str if format == ClipboardFormat.CF_UNICODETEXT.value: @@ -230,7 +236,9 @@ def get_clipboard( return content def set_clipboard( - self, content: str, format: Union[int, ClipboardFormat] = None + self, + content: str, + format: Optional[Union[int, ClipboardFormat]] = None, ) -> HANDLE: """Set clipboard. @@ -246,7 +254,9 @@ def set_clipboard( return set_handle def _set_clipboard( - self, content: str, format: Union[int, ClipboardFormat] = None + self, + content: str, + format: Optional[Union[int, ClipboardFormat]] = None, ) -> HANDLE: """Hides the HANDLE. @@ -357,7 +367,7 @@ def _resolve_format(self, format: Union[ClipboardFormat, str, int]) -> int: format = ClipboardFormat.CF_HTML.value return format # type: ignore - def __getitem__(self, format: Union[int, ClipboardFormat] = None): + def __getitem__(self, format: Union[int, ClipboardFormat]): """Get data from clipboard, returning None if nothing is on it. Raises @@ -427,7 +437,7 @@ def __exit__( self._close() return True - def _open(self, handle: int = None) -> bool: + def _open(self, handle: Optional[HANDLE] = None) -> bool: logger.info("_Opening clipboard") opened: bool = bool(OpenClipboard(handle)) self.opened = opened @@ -460,7 +470,7 @@ def _lock(self, handle: HANDLE) -> LPVOID: return locked - def _unlock(self, handle: HANDLE = None) -> bool: + def _unlock(self, handle: Optional[HANDLE] = None) -> bool: """Unlock clipboard. Raises From 2a57bdf8958ce0cdf73f688586393fa98ef5e5dc Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:54:04 -0500 Subject: [PATCH 08/47] chore: remove dead, broken code --- src/clipboard/formats.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/clipboard/formats.py b/src/clipboard/formats.py index cc23ce6..01b2c2c 100644 --- a/src/clipboard/formats.py +++ b/src/clipboard/formats.py @@ -58,21 +58,6 @@ def __eq__(self, other): return False -def get_clipboard_formats(formats: List[int] = None) -> List[int]: - """Return all available clipboard formats on clipboard. - - First format is the format on the clipboard, depending on your system. - """ - if formats is None: - formats = [EnumClipboardFormats(0)] - - last_format: int = formats[-1] - if last_format == 0: - return formats[:-1] - else: - return formats + [EnumClipboardFormats(last_format)] - - def get_format_name(format_code: int) -> Optional[str]: """Get the name of the format by it's number. From dfb06233501c35a30d1bfafc8e0c8b56225339e2 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:54:25 -0500 Subject: [PATCH 09/47] feat: add new format --- src/clipboard/formats.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/clipboard/formats.py b/src/clipboard/formats.py index 01b2c2c..0787170 100644 --- a/src/clipboard/formats.py +++ b/src/clipboard/formats.py @@ -2,12 +2,10 @@ from enum import Enum from enum import EnumMeta from typing import Any -from typing import List from typing import Optional from clipboard._c_interface import CF_HTML from clipboard._c_interface import CF_RTF -from clipboard._c_interface import EnumClipboardFormats from clipboard._c_interface import GetClipboardFormatNameA @@ -23,14 +21,21 @@ def __contains__(cls, item: Any): class ClipboardFormat(Enum, metaclass=ExtendedEnum): + # Constants CF_TEXT = 1 CF_UNICODETEXT = 13 CF_LOCALE = 16 + CF_DIB = 8 + """A memory object containing a + [BITMAPINFO](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ + ns-wingdi-bitmapinfo) structure followed by the bitmap bits.""" + # Registered Formats CF_HTML = CF_HTML CF_RTF = CF_RTF HTML_Format = 49418 + # Aliases text = CF_UNICODETEXT # alias html = HTML_Format # alias HTML = html # alias From 091c162c534f8760b39339b2fb83ac792713c5eb Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:56:47 -0500 Subject: [PATCH 10/47] chore: update --- DEVELOPMENT.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 2e5e028..0d8164a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,5 +1,6 @@ This branch is designed to verify capacity of setting the clipboard data with images. +- [ ] FIXME: Need to refactor to allow for bytes to be copied to clipboard. - [ ] Add tests for setting clipboard data with images. - [ ] Add section in README for setting clipboard data with images. - [ ] Copy README to readme test. From 11e0290a5f96ae804cd7efd4c5bd6745c1bf8af4 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:39:13 -0500 Subject: [PATCH 11/47] chore: add pylintrc --- .pylintrc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..4adff93 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,2 @@ +[MESSAGES CONTROL] +disable=redefined-builtin From 29b31698850bfc01fbb7a46dfda268294086e309 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:39:27 -0500 Subject: [PATCH 12/47] chore: update pylintrc --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 4adff93..615594e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,2 +1,2 @@ [MESSAGES CONTROL] -disable=redefined-builtin +disable=redefined-builtin,invalid-name From 1e07a6b0715c8021ec1edfef48a198d5b9c34026 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:39:52 -0500 Subject: [PATCH 13/47] fix: fix opening clipboard issue --- src/clipboard/clipboard.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 5d82098..4f1c08b 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -16,6 +16,7 @@ import ctypes import logging import os +import time from typing import List from typing import Optional from typing import Union @@ -416,6 +417,8 @@ def __enter__(self): max_tries = 3 tries = 0 while not self.opened and tries < max_tries: + if tries > 0: + time.sleep(0.01) tries += 1 if self._open(): return self From 19b8fda8637ff813aeb57ad54d3ded73e613d607 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:40:14 -0500 Subject: [PATCH 14/47] chore: fix lints --- src/clipboard/clipboard.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 4f1c08b..d9a64c2 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -17,6 +17,7 @@ import logging import os import time +import traceback from typing import List from typing import Optional from typing import Union @@ -431,8 +432,6 @@ def __exit__( ) -> bool: logger.info("Exiting context manager") if exception_type is not None: - import traceback - traceback.print_exception( exception_type, exception_value, exception_traceback ) From 1a7b3f4d2e263062225f64e985b1c80ca2f4d2bf Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:40:39 -0500 Subject: [PATCH 15/47] docs: update docstring --- src/clipboard/clipboard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index d9a64c2..826c1cf 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -114,6 +114,7 @@ def get_available_formats() -> list[int]: class Clipboard: + """Represents the system clipboard.""" default_format: ClipboardFormat = ClipboardFormat.CF_UNICODETEXT def __init__( From 8a5966d1775072c44c72eaabb90e39dd3583ccf2 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:41:03 -0500 Subject: [PATCH 16/47] refactor: allow bytes or strings --- src/clipboard/clipboard.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 826c1cf..67394ba 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -85,7 +85,7 @@ def get_clipboard( def set_clipboard( - content: str, + content: Union[str, bytes], format: Optional[Union[int, ClipboardFormat]] = None, ) -> HANDLE: """Conveniency wrapper to set clipboard. @@ -206,7 +206,7 @@ def get_clipboard( # audio data: # https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats string: ctypes.Array[ctypes.c_byte] - content: str + content: Union[str, bytes] if format == ClipboardFormat.CF_UNICODETEXT.value: string = (ctypes.c_byte * self.size).from_address( int(self.address) # type: ignore @@ -240,7 +240,7 @@ def get_clipboard( def set_clipboard( self, - content: str, + content: Union[str, bytes], format: Optional[Union[int, ClipboardFormat]] = None, ) -> HANDLE: """Set clipboard. @@ -258,7 +258,7 @@ def set_clipboard( def _set_clipboard( self, - content: str, + content: Union[str, bytes], format: Optional[Union[int, ClipboardFormat]] = None, ) -> HANDLE: """Hides the HANDLE. @@ -294,7 +294,11 @@ def _set_clipboard( content_bytes: bytes contents_ptr: LPVOID if format == ClipboardFormat.CF_UNICODETEXT.value: - content_bytes = content.encode(encoding="utf-16le") + content_bytes: bytes + if isinstance(content, str): + content_bytes = content.encode(encoding=UTF_ENCODING) + else: + content_bytes = content alloc_handle = GlobalAlloc( GMEM_MOVEABLE | GMEM_ZEROINIT, len(content_bytes) + 2 @@ -309,7 +313,12 @@ def _set_clipboard( format == ClipboardFormat.CF_HTML.value or format == ClipboardFormat.HTML_Format.value ): - template: HTMLTemplate = HTMLTemplate(content) + content_str: str # utf-8 + if isinstance(content, bytes): + content_str = content.decode(encoding=HTML_ENCODING) + else: + content_str = content + template: HTMLTemplate = HTMLTemplate(content_str) html_content_bytes: bytes = template.generate().encode( encoding=HTML_ENCODING ) @@ -325,7 +334,12 @@ def _set_clipboard( set_handle = SetClipboardData(format, alloc_handle) else: - content_bytes = content.encode(encoding="utf-8") + content_bytes: bytes + if isinstance(content, str): + # Most general content is going to be utf-8. + content_bytes = content.encode(encoding="utf-8") + else: + content_bytes = content alloc_handle = GlobalAlloc(GMEM_MOVEABLE, len(content_bytes) + 1) contents_ptr = GlobalLock(alloc_handle) From 68e1544140fc29365aa7f80564408e7a53327199 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:41:13 -0500 Subject: [PATCH 17/47] chore: fix lints --- src/clipboard/clipboard.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 67394ba..d2614b1 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -372,12 +372,12 @@ def _resolve_format(self, format: Union[ClipboardFormat, str, int]) -> int: elif isinstance(format, str): try: format = ClipboardFormat[format].value - except KeyError: + except KeyError as exc: formats = self.available_formats() raise FormatNotSupportedError( f"{format} is not a supported clipboard format." f" Choose from following {formats}" - ) + ) from exc # FIXME: There are issues with HTML_Format, so use CF_HTML if format == ClipboardFormat.HTML_Format.value: @@ -439,8 +439,8 @@ def __enter__(self): if self._open(): return self self._close() - else: - raise OpenClipboardError("Failed to open clipboard.") + + raise OpenClipboardError("Failed to open clipboard.") def __exit__( self, exception_type, exception_value, exception_traceback From 446ebb60ff4da0c77df181515a83f98635b72c11 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:51:18 -0500 Subject: [PATCH 18/47] chore: fix lints --- src/clipboard/clipboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index d2614b1..dc44708 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -317,7 +317,7 @@ def _set_clipboard( if isinstance(content, bytes): content_str = content.decode(encoding=HTML_ENCODING) else: - content_str = content + content_str = content # type: ignore template: HTMLTemplate = HTMLTemplate(content_str) html_content_bytes: bytes = template.generate().encode( encoding=HTML_ENCODING From 6cbdbd3d7b9d35e897d38866c05f55f8fd1139da Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:29:50 -0500 Subject: [PATCH 19/47] chore: add doc for tracking feature --- DEVELOPMENT.md | 107 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 DEVELOPMENT.md diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md new file mode 100644 index 0000000..61a8f6c --- /dev/null +++ b/DEVELOPMENT.md @@ -0,0 +1,107 @@ +This branch is designed to verify capacity of setting the clipboard data with images. + +```python +from clipboard import * + + +# required `Pillow` package for testing +# TODO: Get bytes data so we don't need the dependency for testing. +# Or just add it to the `test` extra. +with Image.open(png_file) as img: + output = io.BytesIO() + img.convert("RGB").save(output, "BMP") + # TODO: Why 14? + data = output.getvalue()[14:] + output.close() + + # Here is what we want to replace + win32clipboard.OpenClipboard() + win32clipboard.EmptyClipboard() + win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data) + win32clipboard.CloseClipboard() + + # With this + with clipboard() as cb: + cb.set_clipboard(data, ClipboardFormat.CF_DIB) + # Mime types (idea) + cb.set_clipboard(data, "image/png") + cb.set_clipboard(data, "image/jpeg") + cb.set_clipboard(data, "image/gif") + # Alternative APIs (idea) + cb.set_image(img) + cb.set_image(data) + cb.set_image(path) + # OR this + set_clipboard(data, ClipboardFormat.CF_DIB) + # Mime types (idea) + set_clipboard(data, "image/png") + set_clipboard(data, "image/jpeg") + set_clipboard(data, "image/gif") + # Alternative APIs (idea) + set_image(img) +``` + +Here is the inspiration. I'd like to replace the `win32clipboard` code with `clip-util`. + +```python +#!/usr/bin/env pip-run +"""Takes a PNG file, adds a white background to it, and copies it to the +clipboard. + +Uses `pip-run` to run the script with the required packages installed. +""" +__requires__ = ["Pillow", "pywin32"] + +import io +import sys +from pathlib import Path +from typing import TypeAlias +from typing import Union + +import win32clipboard +from PIL import Image + + +PathLike = Union[str, bytes, Path] +RGBA: TypeAlias = tuple[int, int, int, int] + + +def add_white_bg_to_png( + png_file: PathLike, + copy: bool = True, + open: bool = False, + background_color: RGBA = (255, 255, 255, 255), +) -> None: + """Add a white background to a PNG file and copy it to the clipboard.""" + with Image.open(png_file) as img: + img = img.convert("RGBA") + bg = Image.new("RGBA", img.size, background_color) + img = Image.alpha_composite(bg, img) + + # Copy to clipboard + if copy: + output = io.BytesIO() + img.convert("RGB").save(output, "BMP") + data = output.getvalue()[14:] + output.close() + win32clipboard.OpenClipboard() + win32clipboard.EmptyClipboard() + win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data) + win32clipboard.CloseClipboard() + + # Open the image + if open: + img.show() + print("Image copied to clipboard.") + + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(f"Usage: {sys.argv[0]} ") + print( + "Takes a PNG file and adds a white background to it, copying it to the clipboard." + ) + raise SystemExit(1) + + add_white_bg_to_png(sys.argv[1]) +``` From e15ac8ab08d687d45d5b1f2fc4e281c11c19a2be Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:51:13 -0500 Subject: [PATCH 20/47] chore: update dev doc --- DEVELOPMENT.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 61a8f6c..2e5e028 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,8 +1,18 @@ This branch is designed to verify capacity of setting the clipboard data with images. +- [ ] Add tests for setting clipboard data with images. +- [ ] Add section in README for setting clipboard data with images. +- [ ] Copy README to readme test. +- [ ] Copy README to docs/source/quickstart.rst + ```python +import io + from clipboard import * +import win32clipboard +from PIL import Image + # required `Pillow` package for testing # TODO: Get bytes data so we don't need the dependency for testing. @@ -21,7 +31,7 @@ with Image.open(png_file) as img: win32clipboard.CloseClipboard() # With this - with clipboard() as cb: + with Clipboard() as cb: cb.set_clipboard(data, ClipboardFormat.CF_DIB) # Mime types (idea) cb.set_clipboard(data, "image/png") From b1e62f9c940350db00d6cab52b981c9a43d02be9 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:51:46 -0500 Subject: [PATCH 21/47] chore: update todo comments --- src/clipboard/clipboard.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 5303636..bfd14d4 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -1,12 +1,14 @@ """Clipboard module. +FIXME: Should be able to set bytes, not just a string. TODO: Make `get_clipboard`'s default to check available formats instead of just using the default format. FIXME: Copying using normal methods and then using `get_clipboard` doesn't work properly. It gives a bunch of null bytes as a string. FIXME: HTML_Format doesn't work for some reason. Fix this. FIXME: Typing appears to be off. - * There are a lot of parameters with a default of None that are not Optional. + * There are a lot of parameters with a default of None that are not + Optional. * There are some parameters typed as `int`, but they look like they should be `Optional[HANDLE]` instead. """ From 1a26fb3ae6de8f390043f98dd7d1341712c8eea1 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 21:59:43 -0500 Subject: [PATCH 22/47] chore: resolve conflict --- src/clipboard/clipboard.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index bfd14d4..b541ac1 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -37,6 +37,7 @@ from clipboard.errors import EmptyClipboardError from clipboard.errors import FormatNotSupportedError from clipboard.errors import GetClipboardError +from clipboard.errors import GetFormatsError from clipboard.errors import LockError from clipboard.errors import OpenClipboardError from clipboard.errors import SetClipboardError @@ -82,23 +83,37 @@ def get_clipboard( def set_clipboard( - content: str, format: Union[int, ClipboardFormat] = None -) -> None: - """Conveniency wrapper to set clipboard.""" + content: str, + format: Optional[Union[int, ClipboardFormat]] = None, +) -> HANDLE: + """Conveniency wrapper to set clipboard. + + Raises + ------ + SetClipboardError + If setting the clipboard failed. + """ with Clipboard() as cb: return cb.set_clipboard(content=content, format=format) + raise SetClipboardError("Setting the clipboard failed.") def get_available_formats() -> list[int]: - """Conveniency wrapper to get available formats.""" + """Conveniency wrapper to get available formats. + + Raises + ------ + OpenClipboardError + If the context manager caught an error. + """ with Clipboard() as cb: return cb.available_formats() + raise GetFormatsError("Failed to get available formats.") class Clipboard: default_format: ClipboardFormat = ClipboardFormat.CF_UNICODETEXT - def __init__(self, format: Union[ClipboardFormat, str, int] = None): if format is None: format = self.default_format.value else: From fae152b698fbe75c431f41b86d2e4fc29a70a6bc Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:52:57 -0500 Subject: [PATCH 23/47] feat: add error for failure to get formats --- src/clipboard/errors.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/clipboard/errors.py b/src/clipboard/errors.py index 188e0d7..8551dde 100644 --- a/src/clipboard/errors.py +++ b/src/clipboard/errors.py @@ -27,3 +27,7 @@ class ClipboardError(Exception): class LockError(Exception): """Exception raised when locking the clipboard fails.""" + + +class GetFormatsError(Exception): + """Exception raised when getting clipboard formats fails.""" From b4e649a36a869857daa24d1f1a023d72f8dd2e85 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:53:10 -0500 Subject: [PATCH 24/47] feat: add error to root public api --- src/clipboard/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/clipboard/__init__.py b/src/clipboard/__init__.py index b01d405..846b3d2 100644 --- a/src/clipboard/__init__.py +++ b/src/clipboard/__init__.py @@ -7,6 +7,7 @@ from clipboard.errors import EmptyClipboardError from clipboard.errors import FormatNotSupportedError from clipboard.errors import GetClipboardError +from clipboard.errors import GetFormatsError from clipboard.errors import LockError from clipboard.errors import OpenClipboardError from clipboard.errors import SetClipboardError @@ -29,6 +30,7 @@ "EmptyClipboardError", "FormatNotSupportedError", "GetClipboardError", + "GetFormatsError", "LockError", "OpenClipboardError", "SetClipboardError", From 83b151675665accc43eaf2c81fae00693c8db9d0 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:53:44 -0500 Subject: [PATCH 25/47] fix: fix type errors --- src/clipboard/clipboard.py | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index b541ac1..00b95fe 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -114,6 +114,10 @@ def get_available_formats() -> list[int]: class Clipboard: default_format: ClipboardFormat = ClipboardFormat.CF_UNICODETEXT + def __init__( + self, + format: Optional[Union[ClipboardFormat, str, int]] = None, + ): if format is None: format = self.default_format.value else: @@ -150,7 +154,7 @@ def get_formats() -> List[int]: return available_formats def get_clipboard( - self, format: Union[int, ClipboardFormat] = None + self, format: Optional[Union[int, ClipboardFormat]] = None ) -> Optional[Union[str, bytes]]: """Get data from clipboard, returning None if nothing is on it. @@ -178,11 +182,12 @@ def get_clipboard( formats = self.available_formats() if format not in formats: raise FormatNotSupportedError( - f"{format} is not supported for getting the clipboard. Choose from following {formats}" + f"{format} is not supported for getting the clipboard." + f" Choose from following {formats}" ) # Info - self.h_clip_mem: HANDLE = GetClipboardData(format) + self.h_clip_mem = GetClipboardData(format) if self.h_clip_mem is None: raise GetClipboardError("The `GetClipboardData` function failed.") self.address = self._lock(self.h_clip_mem) # type: ignore @@ -195,7 +200,8 @@ def get_clipboard( # A handle to the locale identifier associated with the text in the # clipboard. # TODO: There are other types that could be supported as well, such as - # audio data: https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats + # audio data: + # https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats string: ctypes.Array[ctypes.c_byte] content: str if format == ClipboardFormat.CF_UNICODETEXT.value: @@ -230,7 +236,9 @@ def get_clipboard( return content def set_clipboard( - self, content: str, format: Union[int, ClipboardFormat] = None + self, + content: str, + format: Optional[Union[int, ClipboardFormat]] = None, ) -> HANDLE: """Set clipboard. @@ -246,7 +254,9 @@ def set_clipboard( return set_handle def _set_clipboard( - self, content: str, format: Union[int, ClipboardFormat] = None + self, + content: str, + format: Optional[Union[int, ClipboardFormat]] = None, ) -> HANDLE: """Hides the HANDLE. @@ -357,7 +367,7 @@ def _resolve_format(self, format: Union[ClipboardFormat, str, int]) -> int: format = ClipboardFormat.CF_HTML.value return format # type: ignore - def __getitem__(self, format: Union[int, ClipboardFormat] = None): + def __getitem__(self, format: Union[int, ClipboardFormat]): """Get data from clipboard, returning None if nothing is on it. Raises @@ -427,7 +437,7 @@ def __exit__( self._close() return True - def _open(self, handle: int = None) -> bool: + def _open(self, handle: Optional[HANDLE] = None) -> bool: logger.info("_Opening clipboard") opened: bool = bool(OpenClipboard(handle)) self.opened = opened @@ -460,7 +470,7 @@ def _lock(self, handle: HANDLE) -> LPVOID: return locked - def _unlock(self, handle: HANDLE = None) -> bool: + def _unlock(self, handle: Optional[HANDLE] = None) -> bool: """Unlock clipboard. Raises From d40e4c1d55486b61447190d714b4a29d3f99af3a Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:54:04 -0500 Subject: [PATCH 26/47] chore: remove dead, broken code --- src/clipboard/formats.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/clipboard/formats.py b/src/clipboard/formats.py index cc23ce6..01b2c2c 100644 --- a/src/clipboard/formats.py +++ b/src/clipboard/formats.py @@ -58,21 +58,6 @@ def __eq__(self, other): return False -def get_clipboard_formats(formats: List[int] = None) -> List[int]: - """Return all available clipboard formats on clipboard. - - First format is the format on the clipboard, depending on your system. - """ - if formats is None: - formats = [EnumClipboardFormats(0)] - - last_format: int = formats[-1] - if last_format == 0: - return formats[:-1] - else: - return formats + [EnumClipboardFormats(last_format)] - - def get_format_name(format_code: int) -> Optional[str]: """Get the name of the format by it's number. From c73a33d9267b22cd94901bea8b8462d86ded8acd Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:54:25 -0500 Subject: [PATCH 27/47] feat: add new format --- src/clipboard/formats.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/clipboard/formats.py b/src/clipboard/formats.py index 01b2c2c..0787170 100644 --- a/src/clipboard/formats.py +++ b/src/clipboard/formats.py @@ -2,12 +2,10 @@ from enum import Enum from enum import EnumMeta from typing import Any -from typing import List from typing import Optional from clipboard._c_interface import CF_HTML from clipboard._c_interface import CF_RTF -from clipboard._c_interface import EnumClipboardFormats from clipboard._c_interface import GetClipboardFormatNameA @@ -23,14 +21,21 @@ def __contains__(cls, item: Any): class ClipboardFormat(Enum, metaclass=ExtendedEnum): + # Constants CF_TEXT = 1 CF_UNICODETEXT = 13 CF_LOCALE = 16 + CF_DIB = 8 + """A memory object containing a + [BITMAPINFO](https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ + ns-wingdi-bitmapinfo) structure followed by the bitmap bits.""" + # Registered Formats CF_HTML = CF_HTML CF_RTF = CF_RTF HTML_Format = 49418 + # Aliases text = CF_UNICODETEXT # alias html = HTML_Format # alias HTML = html # alias From 677e0bdf32290374f6953852198e2c5a116f69b9 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 00:56:47 -0500 Subject: [PATCH 28/47] chore: update --- DEVELOPMENT.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 2e5e028..0d8164a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,5 +1,6 @@ This branch is designed to verify capacity of setting the clipboard data with images. +- [ ] FIXME: Need to refactor to allow for bytes to be copied to clipboard. - [ ] Add tests for setting clipboard data with images. - [ ] Add section in README for setting clipboard data with images. - [ ] Copy README to readme test. From cd627c3d2f1dac028d14b5e6742f8924847146c0 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:39:13 -0500 Subject: [PATCH 29/47] chore: add pylintrc --- .pylintrc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .pylintrc diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..4adff93 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,2 @@ +[MESSAGES CONTROL] +disable=redefined-builtin From 4359076b7e9cc0eb7af7431fbca464aa4c37e395 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:39:27 -0500 Subject: [PATCH 30/47] chore: update pylintrc --- .pylintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pylintrc b/.pylintrc index 4adff93..615594e 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,2 +1,2 @@ [MESSAGES CONTROL] -disable=redefined-builtin +disable=redefined-builtin,invalid-name From 62bf846e59866c58a2aa4cc7e980722c1fcbe27c Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:39:52 -0500 Subject: [PATCH 31/47] fix: fix opening clipboard issue --- src/clipboard/clipboard.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 00b95fe..86654b5 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -16,6 +16,7 @@ import ctypes import logging import os +import time from typing import List from typing import Optional from typing import Union @@ -416,6 +417,8 @@ def __enter__(self): max_tries = 3 tries = 0 while not self.opened and tries < max_tries: + if tries > 0: + time.sleep(0.01) tries += 1 if self._open(): return self From 73ca346e830aa7f8fb38e52c2255bea51a3368af Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:40:14 -0500 Subject: [PATCH 32/47] chore: fix lints --- src/clipboard/clipboard.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 86654b5..dcad240 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -17,6 +17,7 @@ import logging import os import time +import traceback from typing import List from typing import Optional from typing import Union @@ -431,8 +432,6 @@ def __exit__( ) -> bool: logger.info("Exiting context manager") if exception_type is not None: - import traceback - traceback.print_exception( exception_type, exception_value, exception_traceback ) From d6c5f4ef76e9a63b0cab56b4be82b55f436e4976 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:40:39 -0500 Subject: [PATCH 33/47] docs: update docstring --- src/clipboard/clipboard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index dcad240..6d12f76 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -114,6 +114,7 @@ def get_available_formats() -> list[int]: class Clipboard: + """Represents the system clipboard.""" default_format: ClipboardFormat = ClipboardFormat.CF_UNICODETEXT def __init__( From 67dc9e5b2fab79178c9e4ee99b2b11110d727c69 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:41:03 -0500 Subject: [PATCH 34/47] refactor: allow bytes or strings --- src/clipboard/clipboard.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 6d12f76..fe3b859 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -85,7 +85,7 @@ def get_clipboard( def set_clipboard( - content: str, + content: Union[str, bytes], format: Optional[Union[int, ClipboardFormat]] = None, ) -> HANDLE: """Conveniency wrapper to set clipboard. @@ -206,7 +206,7 @@ def get_clipboard( # audio data: # https://learn.microsoft.com/en-us/windows/win32/dataxchg/standard-clipboard-formats string: ctypes.Array[ctypes.c_byte] - content: str + content: Union[str, bytes] if format == ClipboardFormat.CF_UNICODETEXT.value: string = (ctypes.c_byte * self.size).from_address( int(self.address) # type: ignore @@ -240,7 +240,7 @@ def get_clipboard( def set_clipboard( self, - content: str, + content: Union[str, bytes], format: Optional[Union[int, ClipboardFormat]] = None, ) -> HANDLE: """Set clipboard. @@ -258,7 +258,7 @@ def set_clipboard( def _set_clipboard( self, - content: str, + content: Union[str, bytes], format: Optional[Union[int, ClipboardFormat]] = None, ) -> HANDLE: """Hides the HANDLE. @@ -294,7 +294,11 @@ def _set_clipboard( content_bytes: bytes contents_ptr: LPVOID if format == ClipboardFormat.CF_UNICODETEXT.value: - content_bytes = content.encode(encoding="utf-16le") + content_bytes: bytes + if isinstance(content, str): + content_bytes = content.encode(encoding=UTF_ENCODING) + else: + content_bytes = content alloc_handle = GlobalAlloc( GMEM_MOVEABLE | GMEM_ZEROINIT, len(content_bytes) + 2 @@ -309,7 +313,12 @@ def _set_clipboard( format == ClipboardFormat.CF_HTML.value or format == ClipboardFormat.HTML_Format.value ): - template: HTMLTemplate = HTMLTemplate(content) + content_str: str # utf-8 + if isinstance(content, bytes): + content_str = content.decode(encoding=HTML_ENCODING) + else: + content_str = content + template: HTMLTemplate = HTMLTemplate(content_str) html_content_bytes: bytes = template.generate().encode( encoding=HTML_ENCODING ) @@ -325,7 +334,12 @@ def _set_clipboard( set_handle = SetClipboardData(format, alloc_handle) else: - content_bytes = content.encode(encoding="utf-8") + content_bytes: bytes + if isinstance(content, str): + # Most general content is going to be utf-8. + content_bytes = content.encode(encoding="utf-8") + else: + content_bytes = content alloc_handle = GlobalAlloc(GMEM_MOVEABLE, len(content_bytes) + 1) contents_ptr = GlobalLock(alloc_handle) From e0c1f66af022f9a5cf48049a43ca247618b700b1 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:41:13 -0500 Subject: [PATCH 35/47] chore: fix lints --- src/clipboard/clipboard.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index fe3b859..c2d32ec 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -372,12 +372,12 @@ def _resolve_format(self, format: Union[ClipboardFormat, str, int]) -> int: elif isinstance(format, str): try: format = ClipboardFormat[format].value - except KeyError: + except KeyError as exc: formats = self.available_formats() raise FormatNotSupportedError( f"{format} is not a supported clipboard format." f" Choose from following {formats}" - ) + ) from exc # FIXME: There are issues with HTML_Format, so use CF_HTML if format == ClipboardFormat.HTML_Format.value: @@ -439,8 +439,8 @@ def __enter__(self): if self._open(): return self self._close() - else: - raise OpenClipboardError("Failed to open clipboard.") + + raise OpenClipboardError("Failed to open clipboard.") def __exit__( self, exception_type, exception_value, exception_traceback From 59c04de4deffd0dd3d468f547d604f2a52516f5f Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Fri, 12 Apr 2024 16:51:18 -0500 Subject: [PATCH 36/47] chore: fix lints --- src/clipboard/clipboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index c2d32ec..7e6a7e4 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -317,7 +317,7 @@ def _set_clipboard( if isinstance(content, bytes): content_str = content.decode(encoding=HTML_ENCODING) else: - content_str = content + content_str = content # type: ignore template: HTMLTemplate = HTMLTemplate(content_str) html_content_bytes: bytes = template.generate().encode( encoding=HTML_ENCODING From f4578eb068e2edc498f492bcf19ff2f5bd2e7a22 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:02:24 -0500 Subject: [PATCH 37/47] chore: add lxml dep --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index b467f35..72d5acf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "clip-util" description = "Clipboard utilities for use with Python." version = "0.1.25" requires-python = ">=3.9" -dependencies = [] +dependencies = ["lxml"] license = {file = "LICENSE"} authors = [ { name = "Kyle L. Davis", email = "aceofspades5757.github@gmail.com" }, From b2cd91907957d2e9eb3477f63de1af63aa262d14 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:02:39 -0500 Subject: [PATCH 38/47] chore: remove file from other feature branch --- DEVELOPMENT.md | 118 ------------------------------------------------- 1 file changed, 118 deletions(-) delete mode 100644 DEVELOPMENT.md diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md deleted file mode 100644 index 0d8164a..0000000 --- a/DEVELOPMENT.md +++ /dev/null @@ -1,118 +0,0 @@ -This branch is designed to verify capacity of setting the clipboard data with images. - -- [ ] FIXME: Need to refactor to allow for bytes to be copied to clipboard. -- [ ] Add tests for setting clipboard data with images. -- [ ] Add section in README for setting clipboard data with images. -- [ ] Copy README to readme test. -- [ ] Copy README to docs/source/quickstart.rst - -```python -import io - -from clipboard import * - -import win32clipboard -from PIL import Image - - -# required `Pillow` package for testing -# TODO: Get bytes data so we don't need the dependency for testing. -# Or just add it to the `test` extra. -with Image.open(png_file) as img: - output = io.BytesIO() - img.convert("RGB").save(output, "BMP") - # TODO: Why 14? - data = output.getvalue()[14:] - output.close() - - # Here is what we want to replace - win32clipboard.OpenClipboard() - win32clipboard.EmptyClipboard() - win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data) - win32clipboard.CloseClipboard() - - # With this - with Clipboard() as cb: - cb.set_clipboard(data, ClipboardFormat.CF_DIB) - # Mime types (idea) - cb.set_clipboard(data, "image/png") - cb.set_clipboard(data, "image/jpeg") - cb.set_clipboard(data, "image/gif") - # Alternative APIs (idea) - cb.set_image(img) - cb.set_image(data) - cb.set_image(path) - # OR this - set_clipboard(data, ClipboardFormat.CF_DIB) - # Mime types (idea) - set_clipboard(data, "image/png") - set_clipboard(data, "image/jpeg") - set_clipboard(data, "image/gif") - # Alternative APIs (idea) - set_image(img) -``` - -Here is the inspiration. I'd like to replace the `win32clipboard` code with `clip-util`. - -```python -#!/usr/bin/env pip-run -"""Takes a PNG file, adds a white background to it, and copies it to the -clipboard. - -Uses `pip-run` to run the script with the required packages installed. -""" -__requires__ = ["Pillow", "pywin32"] - -import io -import sys -from pathlib import Path -from typing import TypeAlias -from typing import Union - -import win32clipboard -from PIL import Image - - -PathLike = Union[str, bytes, Path] -RGBA: TypeAlias = tuple[int, int, int, int] - - -def add_white_bg_to_png( - png_file: PathLike, - copy: bool = True, - open: bool = False, - background_color: RGBA = (255, 255, 255, 255), -) -> None: - """Add a white background to a PNG file and copy it to the clipboard.""" - with Image.open(png_file) as img: - img = img.convert("RGBA") - bg = Image.new("RGBA", img.size, background_color) - img = Image.alpha_composite(bg, img) - - # Copy to clipboard - if copy: - output = io.BytesIO() - img.convert("RGB").save(output, "BMP") - data = output.getvalue()[14:] - output.close() - win32clipboard.OpenClipboard() - win32clipboard.EmptyClipboard() - win32clipboard.SetClipboardData(win32clipboard.CF_DIB, data) - win32clipboard.CloseClipboard() - - # Open the image - if open: - img.show() - print("Image copied to clipboard.") - - -if __name__ == "__main__": - if len(sys.argv) != 2: - print(f"Usage: {sys.argv[0]} ") - print( - "Takes a PNG file and adds a white background to it, copying it to the clipboard." - ) - raise SystemExit(1) - - add_white_bg_to_png(sys.argv[1]) -``` From 53ad8b52da9c441bd7bcbf4d644f33ab6f9435c0 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:08:04 -0500 Subject: [PATCH 39/47] chore: remove dead comments --- src/clipboard/clipboard.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 7e6a7e4..2b3457a 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -1,16 +1,8 @@ """Clipboard module. -FIXME: Should be able to set bytes, not just a string. TODO: Make `get_clipboard`'s default to check available formats instead of just using the default format. -FIXME: Copying using normal methods and then using `get_clipboard` doesn't work -properly. It gives a bunch of null bytes as a string. FIXME: HTML_Format doesn't work for some reason. Fix this. -FIXME: Typing appears to be off. - * There are a lot of parameters with a default of None that are not - Optional. - * There are some parameters typed as `int`, but they look like they should - be `Optional[HANDLE]` instead. """ import ctypes From f14deaf27a4960543a42b5acdfe4bcdce98a8c91 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:08:31 -0500 Subject: [PATCH 40/47] refactor: add type alias for format type and add str to it --- src/clipboard/clipboard.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 2b3457a..5b464e8 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -39,6 +39,7 @@ from clipboard.html_clipboard import HTMLTemplate +ClipboardFormatType = Union[int, str, ClipboardFormat] # Type Alias hMem = HANDLE # Type Alias GMEM_MOVEABLE = 0x0002 GMEM_ZEROINIT = 0x0040 @@ -61,7 +62,7 @@ def get_clipboard( - format: Optional[Union[int, ClipboardFormat]] = None + format: Optional[ClipboardFormatType] = None ) -> Optional[Union[str, bytes]]: """Conveniency wrapper to get clipboard. @@ -78,7 +79,7 @@ def get_clipboard( def set_clipboard( content: Union[str, bytes], - format: Optional[Union[int, ClipboardFormat]] = None, + format: Optional[ClipboardFormatType] = None, ) -> HANDLE: """Conveniency wrapper to set clipboard. @@ -111,7 +112,7 @@ class Clipboard: def __init__( self, - format: Optional[Union[ClipboardFormat, str, int]] = None, + format: Optional[ClipboardFormatType] = None, ): if format is None: format = self.default_format.value @@ -149,7 +150,7 @@ def get_formats() -> List[int]: return available_formats def get_clipboard( - self, format: Optional[Union[int, ClipboardFormat]] = None + self, format: Optional[ClipboardFormatType] = None ) -> Optional[Union[str, bytes]]: """Get data from clipboard, returning None if nothing is on it. @@ -233,7 +234,7 @@ def get_clipboard( def set_clipboard( self, content: Union[str, bytes], - format: Optional[Union[int, ClipboardFormat]] = None, + format: Optional[ClipboardFormatType] = None, ) -> HANDLE: """Set clipboard. @@ -251,7 +252,7 @@ def set_clipboard( def _set_clipboard( self, content: Union[str, bytes], - format: Optional[Union[int, ClipboardFormat]] = None, + format: Optional[ClipboardFormatType] = None, ) -> HANDLE: """Hides the HANDLE. @@ -345,7 +346,7 @@ def _set_clipboard( return set_handle - def _resolve_format(self, format: Union[ClipboardFormat, str, int]) -> int: + def _resolve_format(self, format: ClipboardFormatType) -> int: """Given an integer, respresenting a clipboard format, or a ClipboardFormat object, return the respective integer. @@ -376,7 +377,7 @@ def _resolve_format(self, format: Union[ClipboardFormat, str, int]) -> int: format = ClipboardFormat.CF_HTML.value return format # type: ignore - def __getitem__(self, format: Union[int, ClipboardFormat]): + def __getitem__(self, format: ClipboardFormatType): """Get data from clipboard, returning None if nothing is on it. Raises From 89ab89c4dfb1cc56c8fcafecc95bc8d870fda267 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:10:16 -0500 Subject: [PATCH 41/47] chore: run formatters --- src/clipboard/clipboard.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index 5b464e8..c00549b 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -62,7 +62,7 @@ def get_clipboard( - format: Optional[ClipboardFormatType] = None + format: Optional[ClipboardFormatType] = None, ) -> Optional[Union[str, bytes]]: """Conveniency wrapper to get clipboard. @@ -95,7 +95,7 @@ def set_clipboard( def get_available_formats() -> list[int]: """Conveniency wrapper to get available formats. - + Raises ------ OpenClipboardError @@ -108,6 +108,7 @@ def get_available_formats() -> list[int]: class Clipboard: """Represents the system clipboard.""" + default_format: ClipboardFormat = ClipboardFormat.CF_UNICODETEXT def __init__( From e84cc76571b3911ae5c6623ca22324352b1d8c7d Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:14:09 -0500 Subject: [PATCH 42/47] chore: fix lints --- src/clipboard/clipboard.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index c00549b..ef36d38 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -75,6 +75,7 @@ def get_clipboard( format = available[0] with Clipboard() as cb: return cb.get_clipboard(format=format) + return None def set_clipboard( @@ -288,7 +289,6 @@ def _set_clipboard( content_bytes: bytes contents_ptr: LPVOID if format == ClipboardFormat.CF_UNICODETEXT.value: - content_bytes: bytes if isinstance(content, str): content_bytes = content.encode(encoding=UTF_ENCODING) else: @@ -328,7 +328,6 @@ def _set_clipboard( set_handle = SetClipboardData(format, alloc_handle) else: - content_bytes: bytes if isinstance(content, str): # Most general content is going to be utf-8. content_bytes = content.encode(encoding="utf-8") @@ -516,3 +515,5 @@ def _empty(self) -> bool: return bool(EmptyClipboard()) else: raise EmptyClipboardError("Emptying the clipboard failed.") + + return False From 3137c91f560677c3f5426c6dd3900e56ef9634bd Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:17:55 -0500 Subject: [PATCH 43/47] docs: add docstrings --- src/clipboard/constants.py | 1 + src/clipboard/formats.py | 7 ++++++- src/clipboard/html_clipboard.py | 8 ++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/clipboard/constants.py b/src/clipboard/constants.py index 995c958..98812c1 100644 --- a/src/clipboard/constants.py +++ b/src/clipboard/constants.py @@ -1,2 +1,3 @@ +"""Constants""" UTF_ENCODING: str = "UTF-16" HTML_ENCODING: str = "UTF-8" diff --git a/src/clipboard/formats.py b/src/clipboard/formats.py index 0787170..aa7aeda 100644 --- a/src/clipboard/formats.py +++ b/src/clipboard/formats.py @@ -1,3 +1,4 @@ +"""Clipboard Formats""" import ctypes from enum import Enum from enum import EnumMeta @@ -10,6 +11,7 @@ class ExtendedEnum(EnumMeta): + """Extended Enum Meta Class""" def __contains__(cls, item: Any): return any( [ @@ -21,6 +23,7 @@ def __contains__(cls, item: Any): class ClipboardFormat(Enum, metaclass=ExtendedEnum): + """Clipboard Formats""" # Constants CF_TEXT = 1 CF_UNICODETEXT = 13 @@ -44,11 +47,13 @@ class ClipboardFormat(Enum, metaclass=ExtendedEnum): @classmethod # type: ignore @property def values(cls): + """Get the values of the enum.""" return [i.value for i in cls] @classmethod # type: ignore @property def names(cls): + """Get the names of the enum.""" return [i.name for i in cls] def __str__(self): @@ -76,7 +81,7 @@ def get_format_name(format_code: int) -> Optional[str]: None if the format is not found. """ # Built-In - if format_code in ClipboardFormat.values: # type: ignore + if format_code in ClipboardFormat.values: # pylint: disable=unsupported-membership-test return ClipboardFormat(format_code).name buffer_size = 256 diff --git a/src/clipboard/html_clipboard.py b/src/clipboard/html_clipboard.py index 16029ab..499c20d 100644 --- a/src/clipboard/html_clipboard.py +++ b/src/clipboard/html_clipboard.py @@ -1,3 +1,4 @@ +"""Code for handling HTML clipboard data.""" import re from typing import List from typing import Optional @@ -51,6 +52,7 @@ def __init__(self, content: str = ""): self.content: str = content def generate(self) -> str: + """Generate the HTML template.""" fragments: List[str] = ( self.fragments if self.fragments else [self.content] ) @@ -67,6 +69,7 @@ def generate(self) -> str: return result def _generate_fragments(self, fragments: List) -> str: + """Generate the HTML fragments.""" results: List[str] = [] for fragment in fragments: results.append("") @@ -79,6 +82,7 @@ def _generate_fragments(self, fragments: List) -> str: return result def _generate_html(self, string: str) -> str: + """Generate the HTML document.""" lines = string.splitlines() body = [""] + lines + [""] html = [""] + body + [""] @@ -86,6 +90,7 @@ def _generate_html(self, string: str) -> str: return "\n".join(html) def _generate_header(self, string: str) -> str: + """Generate the header for the HTML document.""" lines = string.splitlines() version = self.version @@ -106,6 +111,7 @@ def _generate_header(self, string: str) -> str: return "\n".join(lines) def _add_byte_counts(self, content: str) -> str: + """Add byte counts to the HTML content.""" # Check current_values = self._get_byte_values(content) if all((i is not None and i != -1) for i in current_values.values()): @@ -147,6 +153,7 @@ def _add_byte_counts(self, content: str) -> str: return self._add_byte_counts(result) def _get_byte_values(self, content: str) -> dict: + """Get the byte values from the HTML content.""" re_StartHTML = re.compile(r"StartHTML:(\d+)", flags=re.MULTILINE) StartHTML = int( re_StartHTML.findall(content)[0] @@ -185,6 +192,7 @@ def _get_byte_values(self, content: str) -> dict: } def _update_byte_counts(self, content: B) -> B: + """Update the byte counts in the HTML content.""" data: str if isinstance(content, bytes): data = content.decode(encoding=HTML_ENCODING) From cc1f1d56a515f4d1d15bf556f9ad43987fed7953 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:21:16 -0500 Subject: [PATCH 44/47] docs: update docstring --- src/clipboard/clipboard.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/clipboard/clipboard.py b/src/clipboard/clipboard.py index ef36d38..2b82aaf 100644 --- a/src/clipboard/clipboard.py +++ b/src/clipboard/clipboard.py @@ -152,10 +152,19 @@ def get_formats() -> List[int]: return available_formats def get_clipboard( - self, format: Optional[ClipboardFormatType] = None + self, + format: Optional[ClipboardFormatType] = None, ) -> Optional[Union[str, bytes]]: """Get data from clipboard, returning None if nothing is on it. + **Requires valid HTML content to work as intended.** + + Parameters + ---------- + format : Optional[ClipboardFormatType] + The format of the clipboard data. + If None, the default format is used. + Raises ------ FormatNotSupportedError From dcd016499dc4e361d7b6710d012848a357234a23 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:21:24 -0500 Subject: [PATCH 45/47] chore: remove dep --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 72d5acf..b467f35 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ name = "clip-util" description = "Clipboard utilities for use with Python." version = "0.1.25" requires-python = ">=3.9" -dependencies = ["lxml"] +dependencies = [] license = {file = "LICENSE"} authors = [ { name = "Kyle L. Davis", email = "aceofspades5757.github@gmail.com" }, From b8715321875fd10d90d5064b1b34cbb0e412fbf5 Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:22:00 -0500 Subject: [PATCH 46/47] chore: run formatters --- src/clipboard/constants.py | 1 + src/clipboard/formats.py | 7 ++++++- src/clipboard/html_clipboard.py | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/clipboard/constants.py b/src/clipboard/constants.py index 98812c1..8475901 100644 --- a/src/clipboard/constants.py +++ b/src/clipboard/constants.py @@ -1,3 +1,4 @@ """Constants""" + UTF_ENCODING: str = "UTF-16" HTML_ENCODING: str = "UTF-8" diff --git a/src/clipboard/formats.py b/src/clipboard/formats.py index aa7aeda..9ea1841 100644 --- a/src/clipboard/formats.py +++ b/src/clipboard/formats.py @@ -1,4 +1,5 @@ """Clipboard Formats""" + import ctypes from enum import Enum from enum import EnumMeta @@ -12,6 +13,7 @@ class ExtendedEnum(EnumMeta): """Extended Enum Meta Class""" + def __contains__(cls, item: Any): return any( [ @@ -24,6 +26,7 @@ def __contains__(cls, item: Any): class ClipboardFormat(Enum, metaclass=ExtendedEnum): """Clipboard Formats""" + # Constants CF_TEXT = 1 CF_UNICODETEXT = 13 @@ -81,7 +84,9 @@ def get_format_name(format_code: int) -> Optional[str]: None if the format is not found. """ # Built-In - if format_code in ClipboardFormat.values: # pylint: disable=unsupported-membership-test + if ( + format_code in ClipboardFormat.values + ): # pylint: disable=unsupported-membership-test return ClipboardFormat(format_code).name buffer_size = 256 diff --git a/src/clipboard/html_clipboard.py b/src/clipboard/html_clipboard.py index 499c20d..11bb2c7 100644 --- a/src/clipboard/html_clipboard.py +++ b/src/clipboard/html_clipboard.py @@ -1,4 +1,5 @@ """Code for handling HTML clipboard data.""" + import re from typing import List from typing import Optional From d725b4d1668bc6e1a4e91d1330494347976795bb Mon Sep 17 00:00:00 2001 From: AceofSpades5757 <10341888+AceofSpades5757@users.noreply.github.com> Date: Tue, 30 Apr 2024 22:23:54 -0500 Subject: [PATCH 47/47] docs: reorganize badges --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1c7ebed..aa5c60a 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,12 @@ -[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) -[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) - [![PyPI](https://img.shields.io/pypi/v/clip-util?color=darkred)](https://pypi.org/project/clip-util/) +[![Read the Docs](https://img.shields.io/readthedocs/clip-util)](https://clip-util.readthedocs.io/en/latest/) +[![Tests](https://github.com/AceofSpades5757/clip-util/actions/workflows/test.yml/badge.svg)](https://github.com/AceofSpades5757/clip-util/actions/workflows/test.yml) + [![PyPI - Python Version](https://img.shields.io/pypi/pyversions/clip-util?label=Python%20Version&logo=python&logoColor=yellow)](https://pypi.org/project/clip-util/) [![PyPI - License](https://img.shields.io/pypi/l/clip-util?color=green)](https://github.com/AceofSpades5757/clip-util/blob/main/LICENSE) -[![Tests](https://github.com/AceofSpades5757/clip-util/actions/workflows/test.yml/badge.svg)](https://github.com/AceofSpades5757/clip-util/actions/workflows/test.yml) - -[![Read the Docs](https://img.shields.io/readthedocs/clip-util)](https://clip-util.readthedocs.io/en/latest/) +[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) +[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) # Description