Skip to content

Commit

Permalink
Merge pull request #1496 from benoit-pierre/log_dbus_with_libdbus
Browse files Browse the repository at this point in the history
oslayer/log_dbus: use `libdbus` directly
  • Loading branch information
benoit-pierre authored May 14, 2022
2 parents b58eed2 + e0a0378 commit ca5cf77
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 31 deletions.
7 changes: 3 additions & 4 deletions linux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@
To be able to setup a complete development environment, you'll need to manually
install some system libraries (including the development version of your
distribution corresponding packages):
- [`hidapi` package](https://pypi.org/project/hidapi/) (Treal support) needs
`libusb` (1.0) and `libudev`.
- [`dbus-python` package](https://pypi.org/project/dbus-python/) (log /
notifications support) needs `libdbus`.
- Treal support: `libusb` (1.0) and `libudev` are needed by
the [`hidapi` package](https://pypi.org/project/hidapi/).
- log / notifications support: `libdbus` is needed.

For the rest of the steps, follow the [developer guide](../doc/developer_guide.md).
5 changes: 3 additions & 2 deletions linux/appimage/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,11 @@ RUN ./install.sh \
libp11-kit0 \
;

# Install dbus-python build dependencies.
# Install log_dbus dependencies.
# Note: we install the `libdbus-1-dev` because the `libdbus-1.so`
# symlink is not part of the base `libdbus-1-3` package…
RUN ./install.sh \
libdbus-1-dev \
libdbus-glib-1-dev \
;

# Install PyQt5 (minimal) dependencies.
Expand Down
7 changes: 2 additions & 5 deletions linux/appimage/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -177,11 +177,8 @@ python='appdir_python'
# Install boostrap requirements.
get_base_devel

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

# Trim the fat, first pass.
run cp linux/appimage/blacklist.txt "$builddir/blacklist.txt"
Expand Down
1 change: 1 addition & 0 deletions news.d/feature/1496.linux.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Improve D-Bus logger implementation.
131 changes: 113 additions & 18 deletions plover/oslayer/log_dbus.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,141 @@

from contextlib import contextmanager
import ctypes.util
import os
import logging

import dbus

from plover import log, __name__ as __software_name__
from plover.oslayer.config import ASSETS_DIR


APPNAME = __software_name__.capitalize()
APPICON = os.path.join(ASSETS_DIR, 'plover.png')
SERVICE = 'org.freedesktop.Notifications'
INTERFACE = '/org/freedesktop/Notifications'
APPNAME = ctypes.c_char_p(__software_name__.capitalize().encode())
APPICON = ctypes.c_char_p(os.path.join(ASSETS_DIR, 'plover.png').encode())
SERVICE = ctypes.c_char_p(b'org.freedesktop.Notifications')
INTERFACE = ctypes.c_char_p(b'/org/freedesktop/Notifications')

NOTIFY_URGENCY_LOW = ctypes.c_uint8(0)
NOTIFY_URGENCY_NORMAL = ctypes.c_uint8(1)
NOTIFY_URGENCY_CRITICAL = ctypes.c_uint8(2)

DBUS_BUS_SESSION = ctypes.c_uint(0)

DBUS_TYPE_ARRAY = ctypes.c_int(ord('a'))
DBUS_TYPE_BYTE = ctypes.c_int(ord('y'))
DBUS_TYPE_DICT_ENTRY = ctypes.c_int(ord('e'))
DBUS_TYPE_INT32 = ctypes.c_int(ord('i'))
DBUS_TYPE_STRING = ctypes.c_int(ord('s'))
DBUS_TYPE_UINT32 = ctypes.c_int(ord('u'))
DBUS_TYPE_VARIANT = ctypes.c_int(ord('v'))


def ctypes_func(library, signature):
restype, func_name, *argtypes = signature.split()
func = getattr(library, func_name)
setattr(func, 'argtypes', [
ctypes.POINTER(getattr(ctypes, 'c_' + t[1:]))
if t.startswith('&') else getattr(ctypes, 'c_' + t)
for t in argtypes
])
setattr(func, 'restype',
None if restype == 'void' else
getattr(ctypes, 'c_' + restype))
return func


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

def __init__(self):
super().__init__()
self._bus = dbus.SessionBus()
self._proxy = self._bus.get_object(SERVICE, INTERFACE)
self._notify = dbus.Interface(self._proxy, SERVICE)
self.setLevel(log.WARNING)
self.setFormatter(log.NoExceptionTracebackFormatter('<b>%(levelname)s:</b> %(message)s'))

libname = ctypes.util.find_library('dbus-1')
if libname is None:
raise FileNotFoundError('dbus-1 library')
library = ctypes.cdll.LoadLibrary(libname)

bus_get = ctypes_func(library, 'void_p dbus_bus_get uint void_p')
message_new = ctypes_func(library, 'void_p dbus_message_new_method_call char_p char_p char_p char_p')
message_unref = ctypes_func(library, 'void dbus_message_unref void_p')
iter_init_append = ctypes_func(library, 'void dbus_message_iter_init_append void_p void_p')
iter_append_basic = ctypes_func(library, 'bool dbus_message_iter_append_basic void_p int void_p')
iter_open_container = ctypes_func(library, 'bool dbus_message_iter_open_container void_p int char_p void_p')
iter_close_container = ctypes_func(library, 'bool dbus_message_iter_close_container void_p void_p')
connection_send = ctypes_func(library, 'bool dbus_connection_send void_p void_p &uint32')

# Need message + container + dict_entry + variant = 4 iterators.
self._iter_stack = [ctypes.create_string_buffer(128) for __ in range(4)]
self._iter_stack_index = 0

@contextmanager
def open_container(kind, signature):
parent_iter = self._iter_stack[self._iter_stack_index]
sub_iter = self._iter_stack[self._iter_stack_index + 1]
if not iter_open_container(parent_iter, kind, signature, sub_iter):
raise MemoryError
self._iter_stack_index += 1
try:
yield
finally:
if not iter_close_container(parent_iter, sub_iter):
raise MemoryError
self._iter_stack_index -= 1

def append_basic(kind, value):
if not iter_append_basic(self._iter_stack[self._iter_stack_index], kind, ctypes.byref(value)):
raise MemoryError

bus = bus_get(DBUS_BUS_SESSION, None)

actions_signature = ctypes.c_char_p(b's')
hints_signature = ctypes.c_char_p(b'{sv}')
notify_str = ctypes.c_char_p(b'Notify')
urgency_signature = ctypes.c_char_p(b'y')
urgency_str = ctypes.c_char_p(b'urgency')
zero = ctypes.c_uint(0)

def notify(body, urgency, timeout):
message = message_new(SERVICE, INTERFACE, SERVICE, notify_str)
try:
iter_init_append(message, self._iter_stack[self._iter_stack_index])
# app_name
append_basic(DBUS_TYPE_STRING, APPNAME)
# replaces_id
append_basic(DBUS_TYPE_UINT32, zero)
# app_icon
append_basic(DBUS_TYPE_STRING, APPICON)
# summary
append_basic(DBUS_TYPE_STRING, APPNAME)
# body
append_basic(DBUS_TYPE_STRING, body)
# actions
with open_container(DBUS_TYPE_ARRAY, actions_signature):
pass
# hints
with open_container(DBUS_TYPE_ARRAY, hints_signature), open_container(DBUS_TYPE_DICT_ENTRY, None):
append_basic(DBUS_TYPE_STRING, urgency_str)
with open_container(DBUS_TYPE_VARIANT, urgency_signature):
append_basic(DBUS_TYPE_BYTE, urgency)
# expire_timeout
append_basic(DBUS_TYPE_INT32, timeout)
connection_send(bus, message, None)
finally:
message_unref(message)

self._notify = notify

def emit(self, record):
level = record.levelno
message = self.format(record)
if message.endswith('\n'):
message = message[:-1]
if level <= log.INFO:
timeout = 10
urgency = 0
urgency = NOTIFY_URGENCY_LOW
elif level <= log.WARNING:
timeout = 15
urgency = 1
urgency = NOTIFY_URGENCY_NORMAL
else:
timeout = 0
urgency = 2
self._notify.Notify(APPNAME, 0, APPICON, # app_name, replaces_id, app_icon
APPNAME, message, '', # actions
{ 'urgency': dbus.Byte(urgency) },
timeout * 1000)

urgency = NOTIFY_URGENCY_CRITICAL
self._notify(ctypes.c_char_p(message.encode()), urgency, ctypes.c_int(timeout * 1000))
1 change: 0 additions & 1 deletion reqs/constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ click-default-group==1.2.2
cmarkgfm==0.6.0
colorama==0.4.4
cryptography==35.0.0
dbus-python==1.2.18
dmgbuild==1.5.2
docutils==0.18
ds-store==1.3.0
Expand Down
1 change: 0 additions & 1 deletion reqs/dist_extra_log.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
dbus-python>=1.2.4; "linux" in sys_platform

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

0 comments on commit ca5cf77

Please sign in to comment.