Skip to content

Commit ca5cf77

Browse files
Merge pull request #1496 from benoit-pierre/log_dbus_with_libdbus
oslayer/log_dbus: use `libdbus` directly
2 parents b58eed2 + e0a0378 commit ca5cf77

File tree

7 files changed

+122
-31
lines changed

7 files changed

+122
-31
lines changed

linux/README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
To be able to setup a complete development environment, you'll need to manually
66
install some system libraries (including the development version of your
77
distribution corresponding packages):
8-
- [`hidapi` package](https://pypi.org/project/hidapi/) (Treal support) needs
9-
`libusb` (1.0) and `libudev`.
10-
- [`dbus-python` package](https://pypi.org/project/dbus-python/) (log /
11-
notifications support) needs `libdbus`.
8+
- Treal support: `libusb` (1.0) and `libudev` are needed by
9+
the [`hidapi` package](https://pypi.org/project/hidapi/).
10+
- log / notifications support: `libdbus` is needed.
1211

1312
For the rest of the steps, follow the [developer guide](../doc/developer_guide.md).

linux/appimage/Dockerfile

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,10 +138,11 @@ RUN ./install.sh \
138138
libp11-kit0 \
139139
;
140140

141-
# Install dbus-python build dependencies.
141+
# Install log_dbus dependencies.
142+
# Note: we install the `libdbus-1-dev` because the `libdbus-1.so`
143+
# symlink is not part of the base `libdbus-1-3` package…
142144
RUN ./install.sh \
143145
libdbus-1-dev \
144-
libdbus-glib-1-dev \
145146
;
146147

147148
# Install PyQt5 (minimal) dependencies.

linux/appimage/build.sh

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,8 @@ python='appdir_python'
177177
# Install boostrap requirements.
178178
get_base_devel
179179

180-
# Install dbus-python before running linuxdeploy,
181-
# as there are no manylinux wheels available, so
182-
# we need to ensure its system dependencies are
183-
# included in the AppImage.
184-
install_wheels -c reqs/constraints.txt dbus-python
180+
# Copy log_dbus system dependencies.
181+
run cp -Lv /usr/lib/x86_64-linux-gnu/libdbus-1.so "$appdir/usr/lib"
185182

186183
# Trim the fat, first pass.
187184
run cp linux/appimage/blacklist.txt "$builddir/blacklist.txt"

news.d/feature/1496.linux.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve D-Bus logger implementation.

plover/oslayer/log_dbus.py

Lines changed: 113 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,141 @@
1-
1+
from contextlib import contextmanager
2+
import ctypes.util
23
import os
34
import logging
45

5-
import dbus
6-
76
from plover import log, __name__ as __software_name__
87
from plover.oslayer.config import ASSETS_DIR
98

109

11-
APPNAME = __software_name__.capitalize()
12-
APPICON = os.path.join(ASSETS_DIR, 'plover.png')
13-
SERVICE = 'org.freedesktop.Notifications'
14-
INTERFACE = '/org/freedesktop/Notifications'
10+
APPNAME = ctypes.c_char_p(__software_name__.capitalize().encode())
11+
APPICON = ctypes.c_char_p(os.path.join(ASSETS_DIR, 'plover.png').encode())
12+
SERVICE = ctypes.c_char_p(b'org.freedesktop.Notifications')
13+
INTERFACE = ctypes.c_char_p(b'/org/freedesktop/Notifications')
14+
15+
NOTIFY_URGENCY_LOW = ctypes.c_uint8(0)
16+
NOTIFY_URGENCY_NORMAL = ctypes.c_uint8(1)
17+
NOTIFY_URGENCY_CRITICAL = ctypes.c_uint8(2)
18+
19+
DBUS_BUS_SESSION = ctypes.c_uint(0)
20+
21+
DBUS_TYPE_ARRAY = ctypes.c_int(ord('a'))
22+
DBUS_TYPE_BYTE = ctypes.c_int(ord('y'))
23+
DBUS_TYPE_DICT_ENTRY = ctypes.c_int(ord('e'))
24+
DBUS_TYPE_INT32 = ctypes.c_int(ord('i'))
25+
DBUS_TYPE_STRING = ctypes.c_int(ord('s'))
26+
DBUS_TYPE_UINT32 = ctypes.c_int(ord('u'))
27+
DBUS_TYPE_VARIANT = ctypes.c_int(ord('v'))
28+
29+
30+
def ctypes_func(library, signature):
31+
restype, func_name, *argtypes = signature.split()
32+
func = getattr(library, func_name)
33+
setattr(func, 'argtypes', [
34+
ctypes.POINTER(getattr(ctypes, 'c_' + t[1:]))
35+
if t.startswith('&') else getattr(ctypes, 'c_' + t)
36+
for t in argtypes
37+
])
38+
setattr(func, 'restype',
39+
None if restype == 'void' else
40+
getattr(ctypes, 'c_' + restype))
41+
return func
1542

1643

1744
class DbusNotificationHandler(logging.Handler):
1845
""" Handler using DBus notifications to show messages. """
1946

2047
def __init__(self):
2148
super().__init__()
22-
self._bus = dbus.SessionBus()
23-
self._proxy = self._bus.get_object(SERVICE, INTERFACE)
24-
self._notify = dbus.Interface(self._proxy, SERVICE)
2549
self.setLevel(log.WARNING)
2650
self.setFormatter(log.NoExceptionTracebackFormatter('<b>%(levelname)s:</b> %(message)s'))
2751

52+
libname = ctypes.util.find_library('dbus-1')
53+
if libname is None:
54+
raise FileNotFoundError('dbus-1 library')
55+
library = ctypes.cdll.LoadLibrary(libname)
56+
57+
bus_get = ctypes_func(library, 'void_p dbus_bus_get uint void_p')
58+
message_new = ctypes_func(library, 'void_p dbus_message_new_method_call char_p char_p char_p char_p')
59+
message_unref = ctypes_func(library, 'void dbus_message_unref void_p')
60+
iter_init_append = ctypes_func(library, 'void dbus_message_iter_init_append void_p void_p')
61+
iter_append_basic = ctypes_func(library, 'bool dbus_message_iter_append_basic void_p int void_p')
62+
iter_open_container = ctypes_func(library, 'bool dbus_message_iter_open_container void_p int char_p void_p')
63+
iter_close_container = ctypes_func(library, 'bool dbus_message_iter_close_container void_p void_p')
64+
connection_send = ctypes_func(library, 'bool dbus_connection_send void_p void_p &uint32')
65+
66+
# Need message + container + dict_entry + variant = 4 iterators.
67+
self._iter_stack = [ctypes.create_string_buffer(128) for __ in range(4)]
68+
self._iter_stack_index = 0
69+
70+
@contextmanager
71+
def open_container(kind, signature):
72+
parent_iter = self._iter_stack[self._iter_stack_index]
73+
sub_iter = self._iter_stack[self._iter_stack_index + 1]
74+
if not iter_open_container(parent_iter, kind, signature, sub_iter):
75+
raise MemoryError
76+
self._iter_stack_index += 1
77+
try:
78+
yield
79+
finally:
80+
if not iter_close_container(parent_iter, sub_iter):
81+
raise MemoryError
82+
self._iter_stack_index -= 1
83+
84+
def append_basic(kind, value):
85+
if not iter_append_basic(self._iter_stack[self._iter_stack_index], kind, ctypes.byref(value)):
86+
raise MemoryError
87+
88+
bus = bus_get(DBUS_BUS_SESSION, None)
89+
90+
actions_signature = ctypes.c_char_p(b's')
91+
hints_signature = ctypes.c_char_p(b'{sv}')
92+
notify_str = ctypes.c_char_p(b'Notify')
93+
urgency_signature = ctypes.c_char_p(b'y')
94+
urgency_str = ctypes.c_char_p(b'urgency')
95+
zero = ctypes.c_uint(0)
96+
97+
def notify(body, urgency, timeout):
98+
message = message_new(SERVICE, INTERFACE, SERVICE, notify_str)
99+
try:
100+
iter_init_append(message, self._iter_stack[self._iter_stack_index])
101+
# app_name
102+
append_basic(DBUS_TYPE_STRING, APPNAME)
103+
# replaces_id
104+
append_basic(DBUS_TYPE_UINT32, zero)
105+
# app_icon
106+
append_basic(DBUS_TYPE_STRING, APPICON)
107+
# summary
108+
append_basic(DBUS_TYPE_STRING, APPNAME)
109+
# body
110+
append_basic(DBUS_TYPE_STRING, body)
111+
# actions
112+
with open_container(DBUS_TYPE_ARRAY, actions_signature):
113+
pass
114+
# hints
115+
with open_container(DBUS_TYPE_ARRAY, hints_signature), open_container(DBUS_TYPE_DICT_ENTRY, None):
116+
append_basic(DBUS_TYPE_STRING, urgency_str)
117+
with open_container(DBUS_TYPE_VARIANT, urgency_signature):
118+
append_basic(DBUS_TYPE_BYTE, urgency)
119+
# expire_timeout
120+
append_basic(DBUS_TYPE_INT32, timeout)
121+
connection_send(bus, message, None)
122+
finally:
123+
message_unref(message)
124+
125+
self._notify = notify
126+
28127
def emit(self, record):
29128
level = record.levelno
30129
message = self.format(record)
31130
if message.endswith('\n'):
32131
message = message[:-1]
33132
if level <= log.INFO:
34133
timeout = 10
35-
urgency = 0
134+
urgency = NOTIFY_URGENCY_LOW
36135
elif level <= log.WARNING:
37136
timeout = 15
38-
urgency = 1
137+
urgency = NOTIFY_URGENCY_NORMAL
39138
else:
40139
timeout = 0
41-
urgency = 2
42-
self._notify.Notify(APPNAME, 0, APPICON, # app_name, replaces_id, app_icon
43-
APPNAME, message, '', # actions
44-
{ 'urgency': dbus.Byte(urgency) },
45-
timeout * 1000)
46-
140+
urgency = NOTIFY_URGENCY_CRITICAL
141+
self._notify(ctypes.c_char_p(message.encode()), urgency, ctypes.c_int(timeout * 1000))

reqs/constraints.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ click-default-group==1.2.2
1515
cmarkgfm==0.6.0
1616
colorama==0.4.4
1717
cryptography==35.0.0
18-
dbus-python==1.2.18
1918
dmgbuild==1.5.2
2019
docutils==0.18
2120
ds-store==1.3.0

reqs/dist_extra_log.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
dbus-python>=1.2.4; "linux" in sys_platform
21

32
# vim: ft=cfg commentstring=#\ %s list

0 commit comments

Comments
 (0)