Skip to content

Commit

Permalink
gtk not running as root anymore
Browse files Browse the repository at this point in the history
  • Loading branch information
sezanzeb authored and sezanzeb committed Mar 21, 2021
1 parent 3c97fd3 commit 5e4ae66
Show file tree
Hide file tree
Showing 44 changed files with 2,440 additions and 1,420 deletions.
3 changes: 3 additions & 0 deletions .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ branch = True
source = /usr/lib/python3.9/site-packages/keymapper
concurrency = multiprocessing
debug = multiproc
omit =
# not used currently due to problems
/usr/lib/python3.9/site-packages/keymapper/ipc/socket.py
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ Please install the newest version from source to see if the problem has already

If a button on your device doesn't show up in the gui, verify that the button is reporting an event via `sudo evtest`. If not, key-mapper won't be able to map that button.

If yes, please run `sudo systemctl stop key-mapper && sudo key-mapper-gtk -d`, reproduce the problem and then share the logs.
If yes, please run `sudo systemctl stop key-mapper && key-mapper-gtk -d`, reproduce the problem and then share the logs.
77 changes: 67 additions & 10 deletions bin/key-mapper-control
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@
import os
import grp
import sys
from argparse import ArgumentParser
import argparse
import subprocess

from keymapper.logger import logger
from keymapper.logger import logger, update_verbosity, log_info
from keymapper.config import config
from keymapper.daemon import get_dbus_interface
from keymapper.daemon import Daemon
from keymapper.state import system_mapping
from keymapper.getdevices import get_devices
from keymapper.paths import USER
Expand All @@ -41,6 +42,10 @@ STOP = 'stop'
STOP_ALL = 'stop-all'
HELLO = 'hello'

# internal stuff that the gui uses
START_DAEMON = 'start-daemon'
HELPER = 'helper'


def run(cmd):
"""Run and log a command."""
Expand All @@ -59,9 +64,16 @@ def group_exists(name):
return False


COMMANDS = [AUTOLOAD, START, STOP, HELLO, STOP_ALL]

INTERNALS = [START_DAEMON, HELPER]


def main(options, daemon):
"""Do the stuff that the executable is supposed to do."""
# `main` is a function so that I can import it and test it
"""Do the stuff that the executable is supposed to do.
Is a function so that I can import it and test it
"""
if options.config_dir is not None:
path = os.path.abspath(os.path.expanduser(os.path.join(
options.config_dir,
Expand All @@ -85,7 +97,7 @@ def main(options, daemon):
print('\n'.join(system_mapping.list_names()))
sys.exit(0)

if options.command not in [AUTOLOAD, START, STOP, HELLO, STOP_ALL]:
if options.command not in COMMANDS:
logger.error('Unknown command "%s"', options.command)

if daemon is None:
Expand Down Expand Up @@ -114,7 +126,10 @@ def main(options, daemon):
logger.error('--preset missing')
sys.exit(1)

logger.info('Starting injection: "%s", "%s"', options.device, options.preset)
logger.info(
'Starting injection: "%s", "%s"',
options.device, options.preset
)
daemon.start_injecting(options.device, options.preset)

if options.command == STOP:
Expand All @@ -132,8 +147,28 @@ def main(options, daemon):
logger.info('Daemon answered with "%s"', response)


def internals(options):
"""Methods that are needed to get the gui to work and that require root.
key-mapper-control should be started with sudo or pkexec for this.
"""
if options.command == HELPER:
cmd = ['key-mapper-helper']
elif options.command == START_DAEMON:
cmd = ['key-mapper-service', '--hide-info']
else:
return

if options.debug:
cmd.append('-d')

# Popen makes os.system of the main process continue with stuff while
# cmd runs in the background.
subprocess.Popen(cmd)


if __name__ == '__main__':
parser = ArgumentParser()
parser = argparse.ArgumentParser()
parser.add_argument(
'--command', action='store', dest='command',
help='start, stop, autoload, hello or stop-all',
Expand Down Expand Up @@ -171,9 +206,31 @@ if __name__ == '__main__':
help='Print all available names for the mapping',
default=False
)
parser.add_argument(
'-d', '--debug', action='store_true', dest='debug',
help='Displays additional debug information',
default=False
)
parser.add_argument(
'-v', '--version', action='store_true', dest='version',
help='Print the version and exit', default=False
)

options = parser.parse_args(sys.argv[1:])

daemon = get_dbus_interface(fallback=False)
if options.version:
log_info()
sys.exit(0)

update_verbosity(options.debug)

logger.debug('Call for "%s"', sys.argv)

config.load_config()

main(options, daemon)
if options.command in INTERNALS:
options.debug = True
internals(options)
else:
daemon = Daemon.connect(fallback=False)
main(options, daemon)
5 changes: 4 additions & 1 deletion bin/key-mapper-gtk
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,14 @@ if __name__ == '__main__':

options = parser.parse_args(sys.argv[1:])
update_verbosity(options.debug)
log_info()
log_info('key-mapper-gtk')

# import key-mapper stuff after setting the log verbosity
from keymapper.gui.window import Window
from keymapper.daemon import Daemon
from keymapper.daemon import config

config.load_config()

window = Window()

Expand Down
4 changes: 0 additions & 4 deletions bin/key-mapper-gtk-pkexec

This file was deleted.

58 changes: 58 additions & 0 deletions bin/key-mapper-helper
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# key-mapper - GUI for device specific keyboard mappings
# Copyright (C) 2020 sezanzeb <proxima@sezanzeb.de>
#
# This file is part of key-mapper.
#
# key-mapper is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# key-mapper is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with key-mapper. If not, see <https://www.gnu.org/licenses/>.


"""Starts the root helper."""


import os
import sys
import atexit
import signal
from argparse import ArgumentParser

from keymapper.logger import update_verbosity


if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument(
'-d', '--debug', action='store_true', dest='debug',
help='Displays additional debug information', default=False
)

options = parser.parse_args(sys.argv[1:])

update_verbosity(options.debug)

# import key-mapper stuff after setting the log verbosity
from keymapper.gui.helper import RootHelper

def on_exit():
"""Don't remain idle and alive when the GUI exits via ctrl+c."""
# makes no sense to me, but after the keyboard interrupt it is still
# waiting for an event to complete (`S` in `ps ax`), even when using
# sys.exit
os.kill(os.getpid(), signal.SIGKILL)

atexit.register(on_exit)

helper = RootHelper()
helper.run()
37 changes: 11 additions & 26 deletions bin/key-mapper-service
Original file line number Diff line number Diff line change
Expand Up @@ -23,49 +23,34 @@


import sys
import atexit
from argparse import ArgumentParser

import gi
gi.require_version('GLib', '2.0')
from gi.repository import GLib
from pydbus import SystemBus

from keymapper.logger import update_verbosity, log_info, \
add_filehandler, logger
from keymapper.permissions import can_read_devices
add_filehandler


if __name__ == '__main__':
parser = ArgumentParser()
parser.add_argument(
'-d', '--debug', action='store_true', dest='debug',
help='Displays additional debug information',
default=False
help='Displays additional debug information', default=False
)
parser.add_argument(
'--hide-info', action='store_true', dest='hide_info',
help='Don\'t display version information', default=False
)

options = parser.parse_args(sys.argv[1:])

update_verbosity(options.debug)

# import key-mapper stuff after setting the log verbosity
from keymapper.daemon import Daemon, BUS_NAME
from keymapper.daemon import Daemon

add_filehandler()
log_info()

can_read_devices()
if not options.hide_info:
log_info('key-mapper-service')

bus = SystemBus()
loop = GLib.MainLoop()
daemon = Daemon()

try:
bus.publish(BUS_NAME, daemon)
except RuntimeError as error:
logger.error('Is the service is already running? %s', str(error))
sys.exit(1)

atexit.register(daemon.stop_all)

loop.run()
daemon.publish()
daemon.run()
2 changes: 1 addition & 1 deletion data/key-mapper.desktop
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Type=Application
Name=key-mapper
Icon=/usr/share/key-mapper/key-mapper.svg
Exec=key-mapper-gtk-pkexec
Exec=key-mapper-gtk
Terminal=false
Categories=Settings
Comment=GUI for device specific key mappings
8 changes: 4 additions & 4 deletions data/key-mapper.policy
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@
<message>Authentication is required to discover and read devices.</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin</allow_active>
<allow_inactive>auth_admin_keep</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/bin/key-mapper-gtk</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">true</annotate>
<annotate key="org.freedesktop.policykit.exec.path">/usr/bin/key-mapper-control</annotate>
<annotate key="org.freedesktop.policykit.exec.allow_gui">false</annotate>
</action>
</policyconfig>
5 changes: 3 additions & 2 deletions keymapper/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ def callback(parent, child, chunk):
if resolved is None and self.fallback is not None:
resolved = self.fallback._resolve(path, callback)
if resolved is None:
resolved = self._resolve(path, callback, INITIAL_CONFIG)
# don't create new empty stuff in INITIAL_CONFIG with _resolve
initial_copy = copy.deepcopy(INITIAL_CONFIG)
resolved = self._resolve(path, callback, initial_copy)

if resolved is None and log_unknown:
logger.error('Unknown config key "%s"', path)
Expand Down Expand Up @@ -187,7 +189,6 @@ def __init__(self):
os.rename(os.path.join(CONFIG_PATH, 'config'), self.path)

super().__init__()
self.load_config()

def set_autoload_preset(self, device, preset):
"""Set a preset to be automatically applied on start.
Expand Down
Loading

0 comments on commit 5e4ae66

Please sign in to comment.