]> mj.ucw.cz Git - osdd.git/commitdiff
Add python library
authorJiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>
Wed, 24 Aug 2022 10:41:12 +0000 (12:41 +0200)
committerMartin Mares <mj@ucw.cz>
Wed, 24 Aug 2022 11:23:20 +0000 (13:23 +0200)
python_client_lib.py [new file with mode: 0755]
python_client_pip_package/pyproject.toml [new file with mode: 0644]
python_client_pip_package/setup.cfg [new file with mode: 0644]
python_client_pip_package/src/osd/__init__.py [new symlink]

diff --git a/python_client_lib.py b/python_client_lib.py
new file mode 100755 (executable)
index 0000000..e6e94e8
--- /dev/null
@@ -0,0 +1,149 @@
+#!/usr/bin/env python3
+"""
+                  On-Screen Display Python Client Library
+             (c) 2022 Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>
+
+
+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
new file mode 100644 (file)
index 0000000..638dd9c
--- /dev/null
@@ -0,0 +1,3 @@
+[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
new file mode 100644 (file)
index 0000000..fc3bb0a
--- /dev/null
@@ -0,0 +1,31 @@
+[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
new file mode 120000 (symlink)
index 0000000..91530bb
--- /dev/null
@@ -0,0 +1 @@
+../../../python_client_lib.py
\ No newline at end of file