3 On-Screen Display Python Client Library
4 (c) 2022 Jiri Kalvoda <jirikalvoda@kam.mff.cuni.cz>
10 osd.notify("first line", "second line")
12 Each argument is either a single string (a notification line)
13 or a pair of strings ("property", "value"). For available
14 properties consult OSD Daemon documentation. These arguments
15 are position sensitive.
17 Properties may also be added as named parameters. For example:
18 osd.notify("Hello world", color="red")
20 Due to the notation of OSD parameters and Python limitations of named
21 arguments, all occurrences of '_' in the argument name are be replaced by '-'.
23 Named arguments are treated as positional arguments placed before all others,
24 in unspecified relative order. This behavior was chosen as in most cases, these
25 arguments will affect all notification lines. If you need more control, use the
28 Therefore, these two invocations are equivalent:
29 osd.notify("a line", outline_color="red")
30 osd.notify(("outline-color", "red"), "a line")
35 If you don't want to send the notification to the default display,
36 you can create new connection class via `osd.new_connection(DISPLAY_NAME)`
37 or using `osd.Display` constructor which takes an instance of `Xlib.display.Display`.
39 The instance of `Connection` class has the same `notify` method.
41 You can reassign a default connection into `osd.default_connection`.
46 If there is a problem with connection to the X server, library raises the corresponding
49 If a message contains any forbidden characters such as new line,
50 OSDArgumentError will be raised. The `notify_sanitized` method
51 filters out forbidden characters, therefore it never raises OSDArgumentError.
55 from Xlib.display import Display
57 from Xlib.xobject.drawable import Window
59 from typing import Callable, List, Tuple, Optional, Union
62 class OSDArgumentError(RuntimeError):
67 """ See module documentation. """
72 def __init__(self, display: Display):
73 self.display = display
74 self.root_window = self.display.screen().root
75 self.osd_queue_atom = self.display.get_atom('OSD_QUEUE')
77 def _send_raw(self, val: str) -> None:
78 self.root_window.change_property(
80 property_type=Xlib.Xatom.STRING,
83 mode=Xlib.X.PropModeAppend
87 def notify_with_error_handler(self, error_handler: Callable[[str, str, bool], str], *args: Union[str, Tuple[str, str]], **kwargs: str) -> None:
90 def add_line(key, val):
91 def remove_char(s, ch, is_key):
95 out.append(error_handler(s, ch, is_key))
100 key = remove_char(remove_char(str(key), "\n", True), ":", True)
101 val = remove_char(str(val), "\n", False)
103 lines.append(f"{key}:{val}")
105 for key, val in kwargs.items():
106 add_line(key.replace("_", "-"), val)
108 if isinstance(x, tuple):
113 msg = "\n".join(lines) + "\n\n"
116 def notify(self, *args: Union[str, Tuple[str, str]], **kwargs: str) -> None:
117 def error_handler(s, ch, is_key):
118 raise OSDArgumentError(f"{'Key' if is_key else 'Value'} {repr(s)} contain forbidden character {repr(ch)}.")
119 self.notify_with_error_handler(error_handler, *args, **kwargs)
121 def notify_sanitized(self, *args: Union[str, Tuple[str, str]], **kwargs: str) -> None:
122 def error_handler(s, ch, is_key):
124 self.notify_with_error_handler(error_handler, *args, **kwargs)
127 def new_connection(display_name: Optional[str] = None) -> Connection:
128 """ See module documentation. """
129 return Connection(Display(display_name))
132 default_connection: Optional[Connection] = None
135 def _default_or_new_connection() -> Connection:
136 global default_connection
137 if default_connection is None:
138 default_connection = new_connection()
139 return default_connection
142 def notify(*args: Union[str, Tuple[str, str]], **kwargs: str) -> None:
143 """ See module documentation. """
144 _default_or_new_connection().notify(*args, **kwargs)
147 def notify_sanitized(*args: Union[str, Tuple[str, str]], **kwargs: str) -> None:
148 """ See module documentation. """
149 _default_or_new_connection().notify_sanitized(*args, **kwargs)