From: Martin Mares Date: Wed, 24 Aug 2022 11:29:31 +0000 (+0200) Subject: Python: Cleaned up the Python library X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=refs%2Fheads%2Fxft;hp=710ad3ca3a6abb36370cc12c33fa8bf0dc55a04b;p=osdd.git Python: Cleaned up the Python library - both the library module and its packaging live in python-osd/ - fixed a typo - added "import osd" to example usage - added dependency on python-xlib --- diff --git a/python-osd/osd/__init__.py b/python-osd/osd/__init__.py new file mode 100755 index 0000000..492e47c --- /dev/null +++ b/python-osd/osd/__init__.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 +""" + On-Screen Display Python Client Library + (c) 2022 Jiri Kalvoda + + +Basic usage: +============ + +import osd +osd.notify("first line", "second line") + +Each argument is either a single string (a notification line) +or a pair of strings ("property", "value"). For available +properties consult OSD Daemon documentation. These arguments +are position sensitive. + +Properties may also be added as named parameters. For example: + osd.notify("Hello world", color="red") + +Due to the notation of OSD parameters and Python limitations of named +arguments, all occurrences of '_' in the argument name are be replaced by '-'. + +Named arguments are treated as positional arguments placed before all others, +in unspecified relative order. This behavior was chosen as in most cases, these +arguments will affect all notification lines. If you need more control, use the +positional variant. + +Therefore, these two invocations are equivalent: + osd.notify("a line", outline_color="red") + osd.notify(("outline-color", "red"), "a line") + +Connection +========== + +If you don't want to send the notification to the default display, +you can create new connection class via `osd.new_connection(DISPLAY_NAME)` +or using `osd.Display` constructor which takes an instance of `Xlib.display.Display`. + +The instance of `Connection` class has the same `notify` method. + +You can reassign a default connection into `osd.default_connection`. + +Errors +====== + +If there is a problem with connection to the X server, library raises the corresponding +Xlib exceptions. + +If a message contains any forbidden characters such as new line, +OSDArgumentError will be raised. The `notify_sanitized` method +filters out forbidden characters, therefore it never raises OSDArgumentError. +""" + +import Xlib +from Xlib.display import Display +import Xlib.X +from Xlib.xobject.drawable import Window + +from typing import Callable, List, Tuple, Optional, Union + + +class OSDArgumentError(RuntimeError): + pass + + +class Connection(): + """ See module documentation. """ + display: Display + root_window: Window + osd_queue_atom: int + + def __init__(self, display: Display): + self.display = display + self.root_window = self.display.screen().root + self.osd_queue_atom = self.display.get_atom('OSD_QUEUE') + + def _send_raw(self, val: str) -> None: + self.root_window.change_property( + self.osd_queue_atom, + property_type=Xlib.Xatom.STRING, + format=8, + data=val.encode(), + mode=Xlib.X.PropModeAppend + ) + self.display.flush() + + def notify_with_error_handler(self, error_handler: Callable[[str, str, bool], str], *args: Union[str, Tuple[str, str]], **kwargs: str) -> None: + lines: List[str] = [] + + def add_line(key, val): + def remove_char(s, ch, is_key): + out = [] + for i in s: + if i == ch: + out.append(error_handler(s, ch, is_key)) + else: + out.append(i) + return "".join(out) + + key = remove_char(remove_char(str(key), "\n", True), ":", True) + val = remove_char(str(val), "\n", False) + + lines.append(f"{key}:{val}") + + for key, val in kwargs.items(): + add_line(key.replace("_", "-"), val) + for x in args: + if isinstance(x, tuple): + key, val = x + add_line(key, val) + else: + add_line("", x) + msg = "\n".join(lines) + "\n\n" + self._send_raw(msg) + + def notify(self, *args: Union[str, Tuple[str, str]], **kwargs: str) -> None: + def error_handler(s, ch, is_key): + raise OSDArgumentError(f"{'Key' if is_key else 'Value'} {repr(s)} contain forbidden character {repr(ch)}.") + self.notify_with_error_handler(error_handler, *args, **kwargs) + + def notify_sanitized(self, *args: Union[str, Tuple[str, str]], **kwargs: str) -> None: + def error_handler(s, ch, is_key): + return "" + self.notify_with_error_handler(error_handler, *args, **kwargs) + + +def new_connection(display_name: Optional[str] = None) -> Connection: + """ See module documentation. """ + return Connection(Display(display_name)) + + +default_connection: Optional[Connection] = None + + +def _default_or_new_connection() -> Connection: + global default_connection + if default_connection is None: + default_connection = new_connection() + return default_connection + + +def notify(*args: Union[str, Tuple[str, str]], **kwargs: str) -> None: + """ See module documentation. """ + _default_or_new_connection().notify(*args, **kwargs) + + +def notify_sanitized(*args: Union[str, Tuple[str, str]], **kwargs: str) -> None: + """ See module documentation. """ + _default_or_new_connection().notify_sanitized(*args, **kwargs) diff --git a/python-osd/pyproject.toml b/python-osd/pyproject.toml new file mode 100644 index 0000000..638dd9c --- /dev/null +++ b/python-osd/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" diff --git a/python-osd/setup.cfg b/python-osd/setup.cfg new file mode 100644 index 0000000..496963c --- /dev/null +++ b/python-osd/setup.cfg @@ -0,0 +1,26 @@ +[metadata] +name = osd +version = 0.0.1 +author = Jiri Kalvoda +author_email = jirikalvoda@kam.mff.cuni.cz +description = On-Screen Display Client +long_description_content_type = text/plain +classifiers = + Operating System :: OS Independent + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + +[options] +packages = osd +python_requires = >=3.7 +zip_safe = False +install_requires = + argparse >= 0.21 + typing >= 3.0 + python-xlib + +[bdist_wheel] +universal = 1 diff --git a/python_client_lib.py b/python_client_lib.py deleted file mode 100755 index e6e94e8..0000000 --- a/python_client_lib.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python3 -""" - On-Screen Display Python Client Library - (c) 2022 Jiri Kalvoda - - -Basic usage: -============ - -osd.notify("first line", "second line") - -Each argument is either a single string (a notification line) -or a pair of strings ("property", "value"). For available -properties consult OSD Daemon documentation. These arguments -are position sensitive. - -Properties may also be added as named parameters. For example: - osd.notify("Hello world", color="red") - -Due to the notation of OSD parameters and Python limitations of named -arguments, all occurrences of '_' in the argument name are be replaced by '-'. - -Named arguments are treated as positional arguments placed before all others, -in unspecified relative order. This behavior was chosen as in most cases, these -arguments will affect all notification lines. If you need more control, use the -positional variant. - -Therefore, these two invocations are equivalent: - osd.notify("a line", outline_color="red") - osd.notify(("outline-color", "red"), "a line") - -Connection -========== - -If you don't want to send the notification to the default display, -you can create new connection class via `osd.new_connection(DISPLAY_NAME)` -or using `osd.Display` constructor which takes an instance of `Xlib.display.Display`. - -The instance of `Connection` class has the same `notify` method. - -You can reassign a default connection into `osd.default_connection`. - -Errors -====== - -If there is a problem with connection to the X server, library raises the corresponding -Xlib exceptions. - -If a message contains any forbidden characters such as new line, -OSDArgumentError will be raised. The `notify_sanitized` method -filters out forbidden characters, therefore it never raises OSDArgumentError. -""" - -import Xlib -from Xlib.display import Display -import Xlib.X -from Xlib.xobject.drawable import Window - -from typing import Callable, List, Tuple, Optional, Union - - -class OSDArgumentError(RuntimeError): - pass - - -class Connection(): - """ See module documentation. """ - display: Display - root_window: Window - osd_queue_atom: int - - def __init__(self, display: Display): - self.display = display - self.root_window = self.display.screen().root - self.osd_queue_atom = self.display.get_atom('OSD_QUEUE') - - def _send_raw(self, val: str) -> None: - self.root_window.change_property( - self.osd_queue_atom, - property_type=Xlib.Xatom.STRING, - format=8, - data=val.encode(), - mode=Xlib.X.PropModeAppend - ) - self.display.flush() - - def notify_with_error_handler(self, error_handler: Callable[[str, str, bool], str], *args: Union[str, Tuple[str, str]], **kwargs: str) -> None: - lines: List[str] = [] - - def add_line(key, val): - def remove_char(s, ch, is_key): - out = [] - for i in s: - if i == ch: - out.append(error_handler(s, ch, is_key)) - else: - out.append(i) - return "".join(out) - - key = remove_char(remove_char(str(key), "\n", True), ":", True) - val = remove_char(str(val), "\n", False) - - lines.append(f"{key}:{val}") - - for key, val in kwargs.items(): - add_line(key.replace("_", "-"), val) - for x in args: - if isinstance(x, tuple): - key, val = x - add_line(key, val) - else: - add_line("", x) - msg = "\n".join(lines) + "\n\n" - self._send_raw(msg) - - def notify(self, *args: Union[str, Tuple[str, str]], **kwargs: str) -> None: - def error_handler(s, ch, is_key): - raise OSDArgumentError(f"{'Key' if is_key else 'Value'} {repr(s)} contain forbidden character {repr(ch)}.") - self.notify_with_error_handler(error_handler, *args, **kwargs) - - def notify_sanitized(self, *args: Union[str, Tuple[str, str]], **kwargs: str) -> None: - def error_handler(s, ch, is_key): - return "" - self.notify_with_error_handler(error_handler, *args, **kwargs) - - -def new_connection(display_name: Optional[str] = None) -> Connection: - """ See module documentation. """ - return Connection(Display(display_name)) - - -default_connection: Optional[Connection] = None - - -def _default_or_new_connection() -> Connection: - global default_connection - if default_connection is None: - default_connection = new_connection() - return default_connection - - -def notify(*args: Union[str, Tuple[str, str]], **kwargs: str) -> None: - """ See module documentation. """ - _default_or_new_connection().notify(*args, **kwargs) - - -def notify_sanitized(*args: Union[str, Tuple[str, str]], **kwargs: str) -> None: - """ See module documentation. """ - _default_or_new_connection().notify_sanitized(*args, **kwargs) diff --git a/python_client_pip_package/pyproject.toml b/python_client_pip_package/pyproject.toml deleted file mode 100644 index 638dd9c..0000000 --- a/python_client_pip_package/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["setuptools>=61.0"] -build-backend = "setuptools.build_meta" diff --git a/python_client_pip_package/setup.cfg b/python_client_pip_package/setup.cfg deleted file mode 100644 index fc3bb0a..0000000 --- a/python_client_pip_package/setup.cfg +++ /dev/null @@ -1,31 +0,0 @@ -[metadata] -name = osd -version = 0.0.1 -author = Jiri Kalvoda -author_email = jirikalvoda@kam.mff.cuni.cz -description = On-Screen Disply Client -long_description_content_type = text/plain -classifiers = - Operating System :: OS Independent - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - -[options] -package_dir = - = src -packages = osd -python_requires = >=3.7 -zip_safe = False -install_requires = - argparse >= 0.21 - typing >= 3.0 - -[options.packages.find] -where = src - -[bdist_wheel] -universal = 1 - diff --git a/python_client_pip_package/src/osd/__init__.py b/python_client_pip_package/src/osd/__init__.py deleted file mode 120000 index 91530bb..0000000 --- a/python_client_pip_package/src/osd/__init__.py +++ /dev/null @@ -1 +0,0 @@ -../../../python_client_lib.py \ No newline at end of file