Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

oslayer/log_dbus: use libdbus directly #1496

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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