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

feat(linux): Start dbus if not running #10863

Merged
merged 11 commits into from
Mar 26, 2024
Merged
2 changes: 2 additions & 0 deletions linux/debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Build-Depends:
python3-requests,
python3-sentry-sdk (>= 1.1),
python3-setuptools,
python3-xdg,
xserver-xephyr,
xvfb,
Standards-Version: 4.6.2
Expand Down Expand Up @@ -92,6 +93,7 @@ Depends:
python3-gi,
python3-packaging,
python3-sentry-sdk (>= 1.1),
python3-xdg,
${misc:Depends},
${python3:Depends},
Description: Keyman for Linux configuration
Expand Down
75 changes: 75 additions & 0 deletions linux/keyman-config/keyman_config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import atexit
import gettext
import logging
import os
import pathlib
import subprocess

from keyman_config.sentry_handling import SentryErrorHandling
from keyman_config.version import (
Expand Down Expand Up @@ -49,6 +53,77 @@ def initialize_sentry():
SentryErrorHandling().initialize_sentry()


class FileCleanup():
"""
Allow to register files that will be deleted when the process exits
"""
def __init__(self):
self._files_to_delete = {}
atexit.register(self.__cleanup)

def __cleanup(self):
for key in self._files_to_delete:
self._delete_file(self._files_to_delete[key])

def _delete_file(self, file):
try:
pathlib.Path(file).unlink()
except Exception:
return

def register(self, key, file):
if key in self._files_to_delete and self._files_to_delete[key] != file:
self._delete_file(self._files_to_delete[key])
self._files_to_delete[key] = file

def unregister(self, key):
if key in self._files_to_delete:
self._delete_file(self._files_to_delete[key])
self._files_to_delete.pop(key)

def get(self, key):
if key in self._files_to_delete:
return self._files_to_delete[key]
return None


file_cleanup = FileCleanup()
__DBUS_STARTED_FOR_SESSION = False


def get_dbus_started_for_session():
return __DBUS_STARTED_FOR_SESSION


def _set_dbus_started_for_session(value):
global __DBUS_STARTED_FOR_SESSION
__DBUS_STARTED_FOR_SESSION = value


def verify_dbus_running():
if not 'DBUS_SESSION_BUS_ADDRESS' in os.environ:
try:
# Seems dbus isn't running for the current user. Try to start it
# and set these environment variables
logging.info('Starting dbus with dbus-launch')
stdout = subprocess.run(
('dbus-launch', '--exit-with-session'),
stdout=subprocess.PIPE, check=False).stdout
_set_dbus_started_for_session(True)
lines = stdout.decode('utf-8').splitlines()
for line in lines:
equal_sign = line.find('=')
if equal_sign <= 0:
logging.warning('Got unexpected line from dbus-launch: %s', line)
continue
name = line[:equal_sign]
value = line[equal_sign+1:]
logging.debug('Setting environment %s=%s', name, value)
os.environ[name] = value
except Exception as e:
logging.error('Starting dbus-launch failed with %s', e)


def add_standard_arguments(parser):
if __pkgversion__:
versionstring = f"{__versionwithtag__} (package version {__pkgversion__})"
Expand Down
4 changes: 2 additions & 2 deletions linux/keyman-config/keyman_config/custom_keyboards.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def add(self, keyboard):
custom_keyboards.append(keyboard)
else:
custom_keyboards = [keyboard]
self.gsettings.set('additional-keyboards', custom_keyboards, 'as')
self.gsettings.set(GSETTINGS_ADDITIONAL_KEYBOARDS_KEY, custom_keyboards, 'as')

def remove(self, keyboard_path):
if not keyboard_path:
Expand All @@ -49,4 +49,4 @@ def remove(self, keyboard_path):
else:
custom_keyboards = []

self.gsettings.set('additional-keyboards', custom_keyboards, 'as')
self.gsettings.set(GSETTINGS_ADDITIONAL_KEYBOARDS_KEY, custom_keyboards, 'as')
47 changes: 38 additions & 9 deletions linux/keyman-config/keyman_config/gsettings.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
#!/usr/bin/python3
import configparser
import logging
import os
import subprocess
import sys

from xdg.BaseDirectory import xdg_config_home
from gi.repository import Gio # needs to come before gi.overrides.GLib!
from gi.overrides.GLib import Variant

from keyman_config import file_cleanup, get_dbus_started_for_session

class GSettings():
def __init__(self, schema_id):
"""
Expand Down Expand Up @@ -41,18 +45,18 @@ def _convert_variant_to_array(self, variant):
values.append((typeVariant.get_string(), idVariant.get_string()))
return values

def _convert_array_to_variant(self, array, type):
def _convert_array_to_variant(self, array, type_string):
if len(array) == 0:
return Variant(type, None)
return Variant(type_string, None)

if type == 'as':
if type_string == 'as':
return Variant('as', array)

assert type == 'a(ss)'
assert type_string == 'a(ss)'

children = []
for (type, id) in array:
typeVariant = Variant.new_string(type)
for (type_string, id) in array:
typeVariant = Variant.new_string(type_string)
idVariant = Variant.new_string(id)
child = Variant.new_tuple(typeVariant, idVariant)
children.append(child)
Expand Down Expand Up @@ -86,14 +90,39 @@ def get(self, key):
value = self._convert_variant_to_array(variant)
return value

def set(self, key, value, type) -> None:
def _get_schema_path(self):
return self.schema_id.replace('.', '/')

def _set_key_file(self, key, value, type_string):
dconf_config_dir = os.path.join(xdg_config_home, 'dconf')
os.makedirs(os.path.join(dconf_config_dir, 'user.d'), exist_ok=True)
keyfile = file_cleanup.get('keyfile')
if not keyfile:
keyfile = os.path.join(dconf_config_dir, 'user.d', 'keyman-settings')
file_cleanup.register('keyfile', keyfile)
keyman_settings = configparser.ConfigParser()
keyman_settings.read(keyfile, encoding='utf-8')
keyman_settings[self._get_schema_path()] = {}
keyman_settings[self._get_schema_path()][key] = f'@{type_string} {value}'
with open(keyfile, mode='w', encoding='utf-8') as configfile:
keyman_settings.write(configfile)
# Update dconf database immediately for current dbus session, i.e.
# current process, so that further reads and writes will work.
# Cleanup, i.e. removal of the settings file, will be done when
# the process exits. With the next login the user will see the
# updated values.
subprocess.run(['dconf', 'update', dconf_config_dir], check=False)

def set(self, key, value, type_string) -> None:
if self.is_sudo:
variant = str(value)
subprocess.run(
['sudo', '-H', '-u', os.environ.get('SUDO_USER'),
f"DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/{os.environ.get('SUDO_UID')}/bus",
'gsettings', 'set', self.schema_id, key, variant])
'gsettings', 'set', self.schema_id, key, variant], check=False)
elif get_dbus_started_for_session():
self._set_key_file(key, value, type_string)
else:
variant = self._convert_array_to_variant(value, type)
variant = self._convert_array_to_variant(value, type_string)
self.schema.set_value(key, variant)
self.schema.sync()
2 changes: 1 addition & 1 deletion linux/keyman-config/keyman_config/ibus_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ def _start_ibus_daemon(realuser):
if parse_version(_get_ibus_version()) >= parse_version('1.5.28'):
# IBus ~1.5.28 added the `start` command, so we use that if possible
# and let IBus deal with the necessary parameters
args = ['ibus', 'start']
args = ['ibus', 'start', '-d']
else:
# If IBus is too old we have to start ibus-daemon directly and pass
# what we think are the correct parameters
Expand Down
1 change: 0 additions & 1 deletion linux/keyman-config/keyman_config/install_kmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from keyman_config.gnome_keyboards_util import (GnomeKeyboardsUtil,
get_ibus_keyboard_id,
is_gnome_shell)
from keyman_config.gsettings import GSettings
from keyman_config.ibus_util import get_ibus_bus, install_to_ibus, restart_ibus
from keyman_config.kmpmetadata import KMFileTypes, get_metadata
from keyman_config.kvk2ldml import convert_kvk_to_ldml, output_ldml
Expand Down
3 changes: 2 additions & 1 deletion linux/keyman-config/km-config
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ gi.require_version('Gtk', '3.0')

from gi.repository import Gtk

from keyman_config import __versionwithtag__, __pkgversion__, add_standard_arguments, initialize_logging, initialize_sentry
from keyman_config import __versionwithtag__, __pkgversion__, add_standard_arguments, initialize_logging, initialize_sentry, verify_dbus_running
from keyman_config.handle_install import download_and_install_package
from keyman_config.ibus_util import verify_ibus_daemon
from keyman_config.view_installed import ViewInstalledWindow
Expand Down Expand Up @@ -40,6 +40,7 @@ if __name__ == '__main__':

initialize_logging(args)
initialize_sentry()
verify_dbus_running()

logging.info('Keyman version %s %s', __versionwithtag__, __pkgversion__)

Expand Down
3 changes: 2 additions & 1 deletion linux/keyman-config/km-kvk2ldml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import logging
import os
import sys

from keyman_config import add_standard_arguments, initialize_logging, initialize_sentry
from keyman_config import add_standard_arguments, initialize_logging, initialize_sentry, verify_dbus_running
from keyman_config.kvk2ldml import parse_kvk_file, print_kvk, convert_ldml, output_ldml


Expand All @@ -23,6 +23,7 @@ def main():

initialize_logging(args)
initialize_sentry()
verify_dbus_running()

name, ext = os.path.splitext(args.kvkfile)
# Check if input file extension is kvk
Expand Down
3 changes: 2 additions & 1 deletion linux/keyman-config/km-package-get
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import argparse
import os

from keyman_config import add_standard_arguments, initialize_logging, initialize_sentry
from keyman_config import add_standard_arguments, initialize_logging, initialize_sentry, verify_dbus_running
from keyman_config.get_kmp import get_kmp, keyman_cache_dir


Expand All @@ -16,6 +16,7 @@ def main():

initialize_logging(args)
initialize_sentry()
verify_dbus_running()

get_kmp(args.id)
if os.path.exists(os.path.join(keyman_cache_dir(), 'kmpdirlist')):
Expand Down
5 changes: 3 additions & 2 deletions linux/keyman-config/km-package-install
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import packaging.version
import requests
import requests_cache

from keyman_config import KeymanApiUrl, add_standard_arguments, initialize_logging, initialize_sentry, secure_lookup
from keyman_config import KeymanApiUrl, add_standard_arguments, initialize_logging, initialize_sentry, secure_lookup, verify_dbus_running
from keyman_config.fcitx_util import is_fcitx_running
from keyman_config.get_kmp import get_keyboard_data, get_kmp, keyman_cache_dir
from keyman_config.ibus_util import IbusDaemon, verify_ibus_daemon
Expand Down Expand Up @@ -126,8 +126,9 @@ def main():

initialize_logging(args)
initialize_sentry()
verify_dbus_running()

if (not is_fcitx_running()) and (verify_ibus_daemon(False) != IbusDaemon.RUNNING):
if (not is_fcitx_running()) and (verify_ibus_daemon(True) != IbusDaemon.RUNNING):
logging.error("km-package-install: error: ibus-daemon is not running. You might have to reboot or logout and login again.")
sys.exit(4)

Expand Down
3 changes: 2 additions & 1 deletion linux/keyman-config/km-package-list-installed
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import argparse
import os

from keyman_config import add_standard_arguments, initialize_logging, initialize_sentry
from keyman_config import add_standard_arguments, initialize_logging, initialize_sentry, verify_dbus_running
from keyman_config.get_kmp import get_keyman_dir, InstallLocation
from keyman_config.list_installed_kmp import get_installed_kmp

Expand All @@ -20,6 +20,7 @@ def main():

initialize_logging(args)
initialize_sentry()
verify_dbus_running()

_all = not args.user and not args.shared and not args.os
if args.user or _all:
Expand Down
3 changes: 2 additions & 1 deletion linux/keyman-config/km-package-uninstall
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import argparse

from keyman_config import add_standard_arguments, initialize_logging, initialize_sentry
from keyman_config import add_standard_arguments, initialize_logging, initialize_sentry, verify_dbus_running
from keyman_config.uninstall_kmp import uninstall_kmp

def main():
Expand All @@ -15,6 +15,7 @@ def main():

initialize_logging(args)
initialize_sentry()
verify_dbus_running()

uninstall_kmp(args.id, args.shared)

Expand Down
1 change: 1 addition & 0 deletions linux/keyman-config/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
'pillow',
'PyGObject',
'python-magic',
'pyxdg',
'qrcode',
'requests-cache',
'requests',
Expand Down