diff --git a/ratbagd/.gitignore b/ratbagd/.gitignore new file mode 100644 index 00000000..a4eed7cd --- /dev/null +++ b/ratbagd/.gitignore @@ -0,0 +1,12 @@ +*.a +*.log +*.o +*.stamp +*.swp +*.trs +*~ +.dirstamp +.libs/ +/*.tar.xz +tags +__pycache__ diff --git a/ratbagd/COPYING b/ratbagd/COPYING new file mode 100644 index 00000000..6f3e9ba2 --- /dev/null +++ b/ratbagd/COPYING @@ -0,0 +1,21 @@ +Copyright © 2015 David Herrmann +Copyright © 2016 Red hat, Inc + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice (including the next +paragraph) shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/ratbagd/README.md b/ratbagd/README.md new file mode 100644 index 00000000..d9f54dae --- /dev/null +++ b/ratbagd/README.md @@ -0,0 +1,170 @@ +ratbagd +======= + +ratbagd is a system daemon to introspect and modify configurable mice. + +ratbagd uses libratbag to access mice and exports the features over a DBus +API to unprivileged proceses. ratbagd needs permissions to access device +nodes (primarily /dev/hidraw nodes) and usually needs to run as root. + +ratbagd is a relatively thin wrapper arond libratbag. If a device is not +detected or does not function as expected, the issue is usually in +libratbag. libratbag can be found at + https://github.com/libratbag/libratbag + +The DBus-Interface +------------------ + +Note: **the DBus interface is subject to change** + +Interfaces: +- org.freedesktop.ratbag1.Manager +- org.freedesktop.ratbag1.Device +- org.freedesktop.ratbag1.Profile +- org.freedesktop.ratbag1.Resolution +- org.freedesktop.ratbag1.Button +- org.freedesktop.ratbag1.Led + +The **org.freedesktop.ratbag1.Manager** interface provides: +- Properties: + - Devices -> array of object paths with interface Device +- Signals: + - DeviceNew -> new device available, carries object path + - DeviceRemoved -> device removed, carries object path + +The **org.freedesktop.ratbag1.Device** interface provides: +- Properties: + - Id -> unique ID of this device + - Capabilities -> array of uints with the capabilities enum from libratbag + - Description -> device name + - Svg -> device SVG name (file only) + - SvgPath -> device SVG name (full absolute path) + - Profiles -> array of object paths with interface Profile + - ActiveProfile -> index of the currently active profile in Profiles +- Methods: + - GetProfileByIndex(uint) -> returns the object path for the given index + +The **org.freedesktop.ratbag1.Profile** interface provides: +- Properties: + - Index -> index of the profile + - Resolutions -> array of object paths with interface Resolution + - Buttons -> array of object paths with interface Button + - Leds -> array of object paths with interface Led + - ActiveResolution -> index of the currently active resolution in Resolutions + - DefaultResolution -> index of the default resolution in Resolutions +- Methods: + - SetActive(void) -> set this profile to be the active profile + - GetResolutionByIndex(uint) -> returns the object path for the given index +- Signals: + - ActiveProfileChanged -> active profile changed, carries index of the new active profile + +The **org.freedesktop.ratbag1.Resolution** interface provides: +- Properties: + - Index -> index of the resolution + - Capabilities -> array of uints with the capabilities enum from libratbag + - XResolution -> uint for the x resolution assigned to this entry + - YResolution -> uint for the y resolution assigned to this entry + - ReportRate -> uint for the report rate in Hz assigned to this entry +- Methods: + - SetResolution(uint, uint) -> x/y resolution to assign + - SetReportRate(uint) -> uint for the report rate to assign + - SetDefault -> set this resolution to be the default +- Signals: + - ActiveResolutionChanged -> active resolution changed, carries index of the new active resolution + - DefaultResolutionChanged -> default resolution changed, carries index of the new default resolution + +The **org.freedesktop.ratbag1.Button** interface provides: +- Properties: + - Index -> index of the button + - Type -> string describing the button type + - ButtonMapping -> uint of the current button mapping (if mapping to button) + - SpecialMapping -> string of the current special mapping (if mapped to special) + - KeyMapping -> array of uints, first entry is the keycode, other entries, if any, are modifiers (if mapped to key) + - ActionType -> string describing the action type of the button ("none", "button", "key", "special", "macro", "unknown"). This decides which \*Mapping property has a value + - ActionTypes -> array of strings, possible values for ActionType +- Methods: + - SetButtonMapping(uint) -> set the button mapping to the given button + - SetSpecialMapping(string) -> set the button mapping to the given special entry + - SetKeyMapping(uint[]) -> set the key mapping, first entry is the keycode, other entries, if any, are modifier keycodes + - Disable(void) -> disable this button + +The **org.freedesktop.ratbag1.Led** interface provides: +- Properties: + - Index -> index of the LED + - Mode -> uint mapping to the mode enum from libratbag + - Type -> string describing the LED type + - Color -> uint triplet (RGB) of the LED's color + - EffectRate -> the effect rate in Hz, possible values are in the range 100 - 20000 + - Brightness -> the brightness of the LED, possible values are in the range 0 - 255 +- Methods: + - SetMode(uint) -> set the mode to the given mode enum value from libratbag + - SetColor((uuu)) -> set the color to the given uint triplet (RGB) + - SetEffectRate(uint) -> set the effect rate in Hz, possible values are in the range 100 - 20000 + - SetBrightness(int) -> set the brightness, possible values are in the range 0 - 255 + +For easier debugging, objects paths are constructed from the device. e.g. +`/org/freedesktop/ratbag/button/event5/p0/b10` is the button interface for +button 10 on profile 0 on event5. The naming is subject to change. Do not +rely on a constructed object path in your application. + +Running ratbagd +--------------- + +ratbagd is intended to run as dbus-activated systemd service. This requires +installation of the following files: + + sudo cp dbus/org.freedesktop.ratbag1.conf /etc/dbus-1/system.d/org.freedesktop.ratbag1.conf + sudo cp builddir/org.freedesktop.ratbag1.service /etc/dbus-1/system-services/org.freedesktop.ratbag1.conf + sudo cp builddir/ratbagd.service /etc/systemd/system/ratbagd.service + +The files are installed into the prefix by `ninja install`, see also the +configure-time options `-Dsystemd-unit-dir` and `-Ddbus-root-dir` (see +"Compiling ratbagd" below). Developers are encouraged to simply symlink to the +files in the git repository. If you used any other build directory than +`builddir`, adjust accordingly. + +For the files to take effect, you should run + + sudo systemctl daemon-reload + sudo systemctl reload dbus.service + +And finally, to enable the service: + + sudo systemctl enable ratbagd.service + +This places the required symlink into the systemd directory so that dbus +activation is possible. + +Compiling ratbagd +----------------- + +ratbagd uses the meson build system (see http://mesonbuild.com) which in +turn uses ninja to invoke the compiler (`ninja` may be `ninja-build` on your +distribution). From a fresh git checkout, run the following commands to init +the repository: + + meson builddir --prefix=/usr/ + +And to build or re-build after code-changes, run: + + ninja -C builddir + sudo ninja -C builddir install + +Note: `builddir` is the build output directory and can be changed to any +other directory name. To set configure-time options, use e.g. + + mesonconf builddir -Denable-documentation=false + +Run `mesonconf builddir` to list the options. + +License +------- + +ratbagd is licensed under the MIT license. + +> Permission is hereby granted, free of charge, to any person obtaining a +> copy of this software and associated documentation files (the "Software"), +> to deal in the Software without restriction, including without limitation +> the rights to use, copy, modify, merge, publish, distribute, sublicense, +> and/or sell copies of the Software, and to permit persons to whom the +> Software is furnished to do so, subject to the following conditions: [...] diff --git a/ratbagd/dbus/org.freedesktop.ratbag1.conf b/ratbagd/dbus/org.freedesktop.ratbag1.conf new file mode 100644 index 00000000..f4edc1e1 --- /dev/null +++ b/ratbagd/dbus/org.freedesktop.ratbag1.conf @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + diff --git a/ratbagd/dbus/org.freedesktop.ratbag1.service.in b/ratbagd/dbus/org.freedesktop.ratbag1.service.in new file mode 100644 index 00000000..8cad1c6f --- /dev/null +++ b/ratbagd/dbus/org.freedesktop.ratbag1.service.in @@ -0,0 +1,5 @@ +[D-BUS Service] +Name=org.freedesktop.ratbag1 +Exec=@bindir@/ratbagd +User=root +SystemdService=dbus-org.freedesktop.ratbag1.service diff --git a/ratbagd/doc/ratbagctl.1 b/ratbagd/doc/ratbagctl.1 new file mode 100644 index 00000000..90d1bc37 --- /dev/null +++ b/ratbagd/doc/ratbagctl.1 @@ -0,0 +1,75 @@ +.TH ratbagctl 1 "Apr 13, 2016" ratbagctl +.SH NAME +ratbagctl \- inspect and modify configurable mice +.SH SYNOPSIS +.B ratbagctl +.RB [ \-h ] +.RB [ \-V ] +.br +.B ratbagctl list-devices +.br +.B ratbagctl show-device +.RI < device > +.br +.B ratbagctl show-profile +.RI < device "> <" profile > +.br +.B ratbagctl show-resolution +.RI < device "> <" profile "> <" resolution > +.br +.B ratbagctl show-button +.RI < device "> <" profile "> <" button > +.SH DESCRIPTION +.SS Common options +.TP +.BR \-h +displays a short help message (it can be used with any form of the +.B ratbagctl +command). +.TP +.BR \-V +displays the version of +.B ratbagctl +and exits. +.SS Listing devices +.TP +.B list-devices +lists all identified devices on the system. +.SS Displaying a device +.TP +.BR show-device " <" \fIdevice\fP > +displays the device information for the given +.I device +(as identified by +.BR list-devices ). +.SS Displaying profiles +.TP +.IR \fBshow-profile\fP " <" device "> <" profile > +displays the profile information for the given +.I device +and +.IR "profile index" . +.SS Displaying resolution information +.TP +.IR \fBshow-resolution\fP " <" device "> <" profile "> <" resolution > +displays the resolution information for the given +.IR device , +.IR "profile index" , +and +.IR "resolution index" . +.SS Displaying button information +.TP +.IR \fBshow-button\fP " <" device "> <" profile "> <" button > +displays the resolution information for the given +.IR device , +.IR "profile index" , +and +.IR "button index" . +.SH SEE ALSO +.BR ratbagd (8) +.SH AUTHORS +.B ratbagctl +was written by David Herrmann, Peter Hutterer and Benjamin Tissoires. +.PP +This manual page was written by Stephen Kitt for +the Debian GNU/Linux system (but may be used by others). diff --git a/ratbagd/doc/ratbagd.8 b/ratbagd/doc/ratbagd.8 new file mode 100644 index 00000000..fcbad3e9 --- /dev/null +++ b/ratbagd/doc/ratbagd.8 @@ -0,0 +1,19 @@ +.TH ratbagd 8 "Apr 13, 2016" ratbagd +.SH NAME +ratbagd \- system daemon to introspect and modify configurable mice +.SH SYNOPSIS +.B ratbagd +.RB [ \-\-verbose ] +.SH DESCRIPTION +.B ratbagd +starts the daemon. It shouldn't be invoked directly; +.B ratbagd +is normally started through DBus activation. +.SH SEE ALSO +.BR ratbagctl (1) +.SH AUTHORS +.B ratbagd +was written by David Herrmann, Peter Hutterer and Benjamin Tissoires. +.PP +This manual page was written by Stephen Kitt for +the Debian GNU/Linux system (but may be used by others). diff --git a/ratbagd/meson.build b/ratbagd/meson.build new file mode 100644 index 00000000..44f9d30c --- /dev/null +++ b/ratbagd/meson.build @@ -0,0 +1,141 @@ +project('ratbagd', 'c', + version : '0.4', + license : 'MIT/Expat', + meson_version : '>= 0.40.0', + default_options : [ 'c_std=gnu99', 'warning_level=2' ]) + +ratbagd_version = meson.project_version().split('.') + +# Compiler setup +cc = meson.get_compiler('c') +cflags = [ + '-Wno-inline', + '-Wundef', + '-Wformat=2', + '-Wformat-security', + '-Wformat-nonliteral', + '-Wlogical-op', + '-Wsign-compare', + '-Wmissing-include-dirs', + '-Wold-style-definition', + '-Wpointer-arith', + '-Winit-self', + '-Wdeclaration-after-statement', + '-Wfloat-equal', + '-Wsuggest-attribute=noreturn', + '-Wmissing-prototypes', + '-Wstrict-prototypes', + '-Wredundant-decls', + '-Wmissing-declarations', + '-Wmissing-noreturn', + '-Wendif-labels', + '-Wstrict-aliasing=2', + '-Wwrite-strings', + '-Wno-long-long', + '-Wno-overlength-strings', + '-Wno-unused-parameter', + '-Wno-missing-field-initializers', + '-Wno-unused-result', + '-Werror=overflow', + '-Wdate-time', + '-Wnested-externs', +] +add_project_arguments(cflags, language: 'c') +add_project_arguments('-D_GNU_SOURCE', language : 'c') + +# Initialize config.h, to be added to in the various options below, config.h +# is generated at the end of this file +config_h = configuration_data() + +# dependencies +pkgconfig = import('pkgconfig') +dep_udev = dependency('libudev') +dep_systemd = dependency('libsystemd', version : '>=227') +dep_libratbag = dependency('libratbag', version : '>= 0.6') +dep_lm = cc.find_library('m') + +libratbag_data_dir = dep_libratbag.get_pkgconfig_variable('pkgdatadir') +config_h.set_quoted('LIBRATBAG_DATA_DIR', libratbag_data_dir) + +#### libshared.a #### +src_libshared = [ + 'src/shared-macro.h', + 'src/shared-rbtree.h', + 'src/shared-rbtree.c', +] + +deps_libshared = [ + dep_lm, +] + +lib_libshared = static_library('shared', + src_libshared, + dependencies : deps_libshared +) +dep_libshared = declare_dependency(link_with: lib_libshared) + +#### ratbagd #### +src_ratbagd = [ + 'src/ratbagd.h', + 'src/ratbagd.c', + 'src/ratbagd-led.c', + 'src/ratbagd-button.c', + 'src/ratbagd-device.c', + 'src/ratbagd-profile.c', + 'src/ratbagd-resolution.c' +] + +deps_ratbagd = [ + dep_udev, + dep_systemd, + dep_libratbag, + dep_libshared, +] + +executable('ratbagd', + src_ratbagd, + dependencies : deps_ratbagd, + include_directories : include_directories('src'), + install : true, +) + +install_man('doc/ratbagd.8') + +#### unit file #### +unitdir = get_option('systemd-unit-dir') +if unitdir == '' + unitdir = get_option('libdir') + '/systemd/system' +endif + +config_bindir = configuration_data() +config_bindir.set('bindir', join_paths(get_option('prefix'), get_option('bindir'))) + +configure_file(input : 'ratbagd.service.in', + output : 'ratbagd.service', + configuration : config_bindir, + install_dir : unitdir) + +dbusdir = get_option('dbus-root-dir') +if dbusdir == '' + dbusdir = join_paths(get_option('datadir'), 'dbus-1') +endif + +configure_file(input : 'dbus/org.freedesktop.ratbag1.service.in', + output : 'org.freedesktop.ratbag1.service', + configuration : config_bindir, + install_dir : join_paths(dbusdir, 'system-services')) +install_data('dbus/org.freedesktop.ratbag1.conf', + configuration : config_bindir, + install_dir : join_paths(dbusdir, 'system.d')) + +#### tools #### +config_ratbagctl = configuration_data() +config_ratbagctl.set('version', meson.project_version()) +configure_file(input : 'tools/ratbagctl.in', + output : 'ratbagctl', + configuration : config_ratbagctl, + install_dir : get_option('bindir')) +install_man('doc/ratbagctl.1') + +#### output files #### +configure_file(output: 'config.h', install: false, configuration: config_h) diff --git a/ratbagd/meson_options.txt b/ratbagd/meson_options.txt new file mode 100644 index 00000000..e7431145 --- /dev/null +++ b/ratbagd/meson_options.txt @@ -0,0 +1,9 @@ +option('systemd-unit-dir', + type : 'string', + default : '', + description : 'systemd unit directory [default=$libdir/systemd/system]') + +option('dbus-root-dir', + type : 'string', + default : '', + description : 'dbus service directory [default=$datadir/dbus-1]') diff --git a/ratbagd/ratbagd.service.in b/ratbagd/ratbagd.service.in new file mode 100644 index 00000000..e6f6eb9f --- /dev/null +++ b/ratbagd/ratbagd.service.in @@ -0,0 +1,11 @@ +[Unit] +Description=Daemon to introspect and modify configurable mice + +[Service] +Type=dbus +BusName=org.freedesktop.ratbag1 +ExecStart=@bindir@/ratbagd +Restart=on-abort + +[Install] +Alias=dbus-org.freedesktop.ratbag1.service diff --git a/ratbagd/src/org.freedesktop.ratbag1.conf b/ratbagd/src/org.freedesktop.ratbag1.conf new file mode 100644 index 00000000..cd7b8843 --- /dev/null +++ b/ratbagd/src/org.freedesktop.ratbag1.conf @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/ratbagd/src/ratbagd-button.c b/ratbagd/src/ratbagd-button.c new file mode 100644 index 00000000..4956fc42 --- /dev/null +++ b/ratbagd/src/ratbagd-button.c @@ -0,0 +1,567 @@ +/*** + This file is part of ratbagd. + + Copyright 2016 Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ratbagd.h" +#include "shared-macro.h" + +struct ratbagd_button { + struct ratbag_button *lib_button; + unsigned int index; + char *path; +}; + +static int ratbagd_button_get_type(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + const char *type = NULL; + enum ratbag_button_type t; + + t = ratbag_button_get_type(button->lib_button); + + switch(t) { + default: + log_error("Unknown button type %d\n", t); + /* fallthrough */ + case RATBAG_BUTTON_TYPE_UNKNOWN: + type = "unknown"; + break; + case RATBAG_BUTTON_TYPE_LEFT: + type = "left"; + break; + case RATBAG_BUTTON_TYPE_MIDDLE: + type = "middle"; + break; + case RATBAG_BUTTON_TYPE_RIGHT: + type = "right"; + break; + case RATBAG_BUTTON_TYPE_THUMB: + type = "thumb"; + break; + case RATBAG_BUTTON_TYPE_THUMB2: + type = "thumb2"; + break; + case RATBAG_BUTTON_TYPE_THUMB3: + type = "thumb3"; + break; + case RATBAG_BUTTON_TYPE_THUMB4: + type = "thumb4"; + break; + case RATBAG_BUTTON_TYPE_WHEEL_LEFT: + type = "wheel-left"; + break; + case RATBAG_BUTTON_TYPE_WHEEL_RIGHT: + type = "wheel-right"; + break; + case RATBAG_BUTTON_TYPE_WHEEL_CLICK: + type = "wheel-click"; + break; + case RATBAG_BUTTON_TYPE_WHEEL_UP: + type = "wheel-up"; + break; + case RATBAG_BUTTON_TYPE_WHEEL_DOWN: + type = "wheel-down"; + break; + case RATBAG_BUTTON_TYPE_WHEEL_RATCHET_MODE_SHIFT: + type = "wheel-ratchet_mode_shift"; + break; + case RATBAG_BUTTON_TYPE_EXTRA: + type = "extra"; + break; + case RATBAG_BUTTON_TYPE_SIDE: + type = "side"; + break; + case RATBAG_BUTTON_TYPE_PINKIE: + type = "pinkie"; + break; + case RATBAG_BUTTON_TYPE_PINKIE2: + type = "pinkie2"; + break; + case RATBAG_BUTTON_TYPE_RESOLUTION_CYCLE_UP: + type = "resolution-cycle-up"; + break; + case RATBAG_BUTTON_TYPE_RESOLUTION_UP: + type = "resolution-up"; + break; + case RATBAG_BUTTON_TYPE_RESOLUTION_DOWN: + type = "resolution-down"; + break; + case RATBAG_BUTTON_TYPE_PROFILE_CYCLE_UP: + type = "profile-cycle-up"; + break; + case RATBAG_BUTTON_TYPE_PROFILE_UP: + type = "profile-up"; + break; + case RATBAG_BUTTON_TYPE_PROFILE_DOWN: + type = "profile-down"; + break; + } + + return sd_bus_message_append(reply, "s", type); +} + +static int ratbagd_button_get_button(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + unsigned int b; + + b = ratbag_button_get_button(button->lib_button); + + return sd_bus_message_append(reply, "u", b); +} + +static int ratbagd_button_set_button(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + unsigned int map; + int r; + + r = sd_bus_message_read(m, "u", &map); + if (r < 0) + return r; + + if (map == 0 || map > 30) + return 0; + + r = ratbag_button_set_button(button->lib_button, map); + + if (r == 0) { + sd_bus *bus = sd_bus_message_get_bus(m); + sd_bus_emit_properties_changed(bus, + button->path, + "org.freedesktop.ratbag1.Button", + "ButtonMapping", + NULL); + } + + return sd_bus_reply_method_return(m, "u", r); +} + +static int ratbagd_button_get_special(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + const char *type; + enum ratbag_button_action_special special; + + special = ratbag_button_get_special(button->lib_button); + + switch(special) { + default: + log_error("Unknown special type %d\n", special); + /* fallthrough */ + case RATBAG_BUTTON_ACTION_SPECIAL_UNKNOWN: + type = "unknown"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_INVALID: + type = "n/a"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK: + type = "doubleclick"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT: + type = "wheel-left"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT: + type = "wheel-right"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP: + type = "wheel-up"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN: + type = "wheel-down"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_RATCHET_MODE_SWITCH: + type = "ratchet-mode-switch"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP: + type = "resolution-cycle-up"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP: + type = "resolution-up"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN: + type = "resolution-down"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE: + type = "resolution-alternate"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT: + type = "resolution-default"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP: + type = "profile-cycle-up"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP: + type = "profile-up"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN: + type = "profile-down"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE: + type = "second-mode"; + break; + case RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL: + type = "battery-level"; + break; + } + + return sd_bus_message_append(reply, "s", type); +} + +static int ratbagd_button_set_special(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + const char *s; + enum ratbag_button_action_special special; + int r; + + r = sd_bus_message_read(m, "s", &s); + if (r < 0) + return r; + + if (streq(s, "doubleclick")) + special = RATBAG_BUTTON_ACTION_SPECIAL_DOUBLECLICK; + else if (streq(s, "wheel-left")) + special = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT; + else if (streq(s, "wheel-right")) + special = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT; + else if (streq(s, "wheel-up")) + special = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_UP; + else if (streq(s, "wheel-down")) + special = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_DOWN; + else if (streq(s, "ratchet-mode-switch")) + special = RATBAG_BUTTON_ACTION_SPECIAL_RATCHET_MODE_SWITCH; + else if (streq(s, "resolution-cycle-up")) + special = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP; + else if (streq(s, "resolution-up")) + special = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP; + else if (streq(s, "resolution-down")) + special = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN; + else if (streq(s, "resolution-alternate")) + special = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE; + else if (streq(s, "resolution-default")) + special = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT; + else if (streq(s, "profile-cycle-up")) + special = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP; + else if (streq(s, "profile-up")) + special = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_UP; + else if (streq(s, "profile-down")) + special = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_DOWN; + else if (streq(s, "second-mode")) + special = RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE; + else if (streq(s, "battery-level")) + special = RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL; + else { + log_error("Unknown button special action %s\n", s); + return sd_bus_reply_method_return(m, "u", -1); + } + + r = ratbag_button_set_special(button->lib_button, special); + + if (r == 0) { + sd_bus *bus = sd_bus_message_get_bus(m); + sd_bus_emit_properties_changed(bus, + button->path, + "org.freedesktop.ratbag1.Button", + "SpecialMapping", + NULL); + } + + return sd_bus_reply_method_return(m, "u", r); +} + +static int ratbagd_button_get_key(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + unsigned int key; + unsigned int modifiers[10] = {0}; + size_t nmodifiers = ELEMENTSOF(modifiers); + size_t i; + int r; + + /* Return an array with the first element the key, the rest + are modifiers (or 0 if unset). If no key is set, the array is + just [0] */ + + r = sd_bus_message_open_container(reply, 'a', "u"); + if (r < 0) + return r; + + key = ratbag_button_get_key(button->lib_button, + modifiers, + &nmodifiers); + + r = sd_bus_message_append(reply, "u", key); + if (r < 0) + return r; + + if (key == 0) + nmodifiers = 0; + + for (i = 0; i < nmodifiers; i++) { + r = sd_bus_message_append(reply, "u", modifiers[0]); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int ratbagd_button_set_key(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + unsigned int key; + size_t nmodifiers = 10; + unsigned int modifiers[nmodifiers]; + int r; + + /* Expect an array with the first element the key, the rest + are modifiers (or 0 if unset). */ + + r = sd_bus_message_enter_container(m, 'a', "u"); + if (r < 0) + return r; + + r = sd_bus_message_read(m, "u", &key); + if (r < 0) + return r; + + nmodifiers = 0; + while ((r = sd_bus_message_read_basic(m, 'u', + &modifiers[nmodifiers++])) > 0) + ; + + r = sd_bus_message_exit_container(m); + if (r < 0) + return r; + + r = ratbag_button_set_key(button->lib_button, key, modifiers, nmodifiers); + + return sd_bus_reply_method_return(m, "u", r); +} + +static int ratbagd_button_get_action_type(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + const char *type; + + switch (ratbag_button_get_action_type(button->lib_button)) { + case RATBAG_BUTTON_ACTION_TYPE_NONE: + type = "none"; + break; + case RATBAG_BUTTON_ACTION_TYPE_BUTTON: + type = "button"; + break; + case RATBAG_BUTTON_ACTION_TYPE_KEY: + type = "key"; + break; + case RATBAG_BUTTON_ACTION_TYPE_SPECIAL: + type = "special"; + break; + case RATBAG_BUTTON_ACTION_TYPE_MACRO: + type = "macro"; + break; + case RATBAG_BUTTON_ACTION_TYPE_UNKNOWN: + type = "unknown"; + break; + } + + return sd_bus_message_append(reply, "s", type); +} + +static int ratbagd_button_get_action_types(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + const char *types[5] = {NULL}; + const char **t; + int idx = 0; + int r; + + r = sd_bus_message_open_container(reply, 'a', "s"); + if (r < 0) + return r; + + if (ratbag_button_has_action_type(button->lib_button, + RATBAG_BUTTON_ACTION_TYPE_BUTTON)) + types[idx++] = "button"; + if (ratbag_button_has_action_type(button->lib_button, + RATBAG_BUTTON_ACTION_TYPE_KEY)) + types[idx++] = "key"; + if (ratbag_button_has_action_type(button->lib_button, + RATBAG_BUTTON_ACTION_TYPE_SPECIAL)) + types[idx++] = "special"; + if (ratbag_button_has_action_type(button->lib_button, + RATBAG_BUTTON_ACTION_TYPE_MACRO)) + types[idx++] = "macro"; + + t = types; + while (*t) { + r = sd_bus_message_append(reply, "s", *t); + if (r < 0) + return r; + t++; + } + + return sd_bus_message_close_container(reply); +} + +static int ratbagd_button_disable(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_button *button = userdata; + int r; + + r = sd_bus_message_read(m, ""); + if (r < 0) + return r; + + r = ratbag_button_disable(button->lib_button); + + return sd_bus_reply_method_return(m, "u", r); +} + +const sd_bus_vtable ratbagd_button_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Index", "u", NULL, offsetof(struct ratbagd_button, index), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Type", "s", ratbagd_button_get_type, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ButtonMapping", "u", ratbagd_button_get_button, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("SpecialMapping", "s", ratbagd_button_get_special, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("KeyMapping", "au", ratbagd_button_get_key, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("ActionType", "s", ratbagd_button_get_action_type, 0, SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("ActionTypes", "as", ratbagd_button_get_action_types, 0, SD_BUS_VTABLE_PROPERTY_CONST), + + SD_BUS_METHOD("SetButtonMapping", "u", "u", ratbagd_button_set_button, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetSpecialMapping", "s", "u", ratbagd_button_set_special, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetKeyMapping", "au", "u", ratbagd_button_set_key, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("Disable", "", "u", ratbagd_button_disable, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END, +}; + +int ratbagd_button_new(struct ratbagd_button **out, + struct ratbagd_device *device, + struct ratbagd_profile *profile, + struct ratbag_button *lib_button, + unsigned int index) +{ + _cleanup_(ratbagd_button_freep) struct ratbagd_button *button = NULL; + char profile_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1], + button_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; + int r; + + assert(out); + assert(lib_button); + + button = calloc(1, sizeof(*button)); + if (!button) + return -ENOMEM; + + button->lib_button = lib_button; + button->index = index; + + sprintf(profile_buffer, "p%u", ratbagd_profile_get_index(profile)); + sprintf(button_buffer, "b%u", index); + r = sd_bus_path_encode_many(&button->path, + "/org/freedesktop/ratbag1/button/%/%/%", + ratbagd_device_get_name(device), + profile_buffer, + button_buffer); + if (r < 0) + return r; + + *out = button; + button = NULL; + return 0; +} + +const char *ratbagd_button_get_path(struct ratbagd_button *button) +{ + assert(button); + return button->path; +} + +struct ratbagd_button *ratbagd_button_free(struct ratbagd_button *button) +{ + if (!button) + return NULL; + + button->path = mfree(button->path); + button->lib_button = ratbag_button_unref(button->lib_button); + + return mfree(button); +} + diff --git a/ratbagd/src/ratbagd-device.c b/ratbagd/src/ratbagd-device.c new file mode 100644 index 00000000..3191604c --- /dev/null +++ b/ratbagd/src/ratbagd-device.c @@ -0,0 +1,583 @@ +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ratbagd.h" +#include "shared-macro.h" +#include "shared-rbtree.h" + +struct ratbagd_device { + struct ratbagd *ctx; + RBNode node; + char *name; + char *path; + struct ratbag_device *lib_device; + + sd_bus_slot *profile_vtable_slot; + sd_bus_slot *profile_enum_slot; + unsigned int n_profiles; + struct ratbagd_profile **profiles; +}; + +#define ratbagd_device_from_node(_ptr) \ + rbnode_of((_ptr), struct ratbagd_device, node) + +static int ratbagd_device_find_profile(sd_bus *bus, + const char *path, + const char *interface, + void *userdata, + void **found, + sd_bus_error *error) +{ + _cleanup_(freep) char *name = NULL; + struct ratbagd_device *device = userdata; + unsigned int index; + int r; + + r = sd_bus_path_decode_many(path, + "/org/freedesktop/ratbag1/profile/%/p%", + NULL, + &name); + if (r <= 0) + return r; + + r = safe_atou(name, &index); + if (r < 0) + return 0; + + if (index >= device->n_profiles || !device->profiles[index]) + return 0; + + *found = device->profiles[index]; + return 1; +} + +static int ratbagd_device_list_profiles(sd_bus *bus, + const char *path, + void *userdata, + char ***paths, + sd_bus_error *error) +{ + struct ratbagd_device *device = userdata; + struct ratbagd_profile *profile; + char **profiles; + unsigned int i; + + profiles = calloc(device->n_profiles + 1, sizeof(char *)); + if (!profiles) + return -ENOMEM; + + for (i = 0; i < device->n_profiles; ++i) { + profile = device->profiles[i]; + if (!profile) + continue; + + profiles[i] = strdup(ratbagd_profile_get_path(profile)); + if (!profiles[i]) + goto error; + } + + profiles[i] = NULL; + *paths = profiles; + return 1; + +error: + for (i = 0; profiles[i]; ++i) + free(profiles[i]); + free(profiles); + return -ENOMEM; +} + +static int ratbagd_device_get_device_name(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_device *device = userdata; + const char *name; + + name = ratbag_device_get_name(device->lib_device); + if (!name) { + log_error("Unable to fetch name for %s\n", + ratbagd_device_get_name(device)); + name = ""; + } + + return sd_bus_message_append(reply, "s", name); +} + +static int ratbagd_device_get_svg(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_device *device = userdata; + const char *svg; + + svg = ratbag_device_get_svg_name(device->lib_device); + if (!svg) { + log_error("Unable to fetch SVG for %s\n", + ratbagd_device_get_name(device)); + svg = ""; + } + + return sd_bus_message_append(reply, "s", svg); +} + +static int ratbagd_device_get_svg_path(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_device *device = userdata; + char svg_path[PATH_MAX] = {0}; + const char *svg; + + svg = ratbag_device_get_svg_name(device->lib_device); + if (!svg) { + log_error("Unable to fetch SVG for %s\n", + ratbagd_device_get_name(device)); + goto out; + } + + sprintf(svg_path, "%s/%s", LIBRATBAG_DATA_DIR, svg); + +out: + return sd_bus_message_append(reply, "s", svg_path); +} + +static int ratbagd_device_get_profiles(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_device *device = userdata; + struct ratbagd_profile *profile; + unsigned int i; + int r; + + r = sd_bus_message_open_container(reply, 'a', "o"); + if (r < 0) + return r; + + for (i = 0; i < device->n_profiles; ++i) { + profile = device->profiles[i]; + if (!profile) + continue; + + r = sd_bus_message_append(reply, + "o", + ratbagd_profile_get_path(profile)); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int ratbagd_device_get_active_profile(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_device *device = userdata; + struct ratbagd_profile *profile; + unsigned int i; + + for (i = 0; i < device->n_profiles; ++i) { + profile = device->profiles[i]; + if (!profile) + continue; + if (!ratbagd_profile_is_active(profile)) + continue; + + return sd_bus_message_append(reply, "u", i); + } + + log_error("Unable to find active profile for %s\n", device->name); + return sd_bus_message_append(reply, "u", 0); +} + +static int ratbagd_device_get_profile_by_index(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_device *device = userdata; + struct ratbagd_profile *profile; + unsigned int index; + int r; + + r = sd_bus_message_read(m, "u", &index); + if (r < 0) + return r; + + if (index >= device->n_profiles || !device->profiles[index]) + return -ENXIO; + + profile = device->profiles[index]; + return sd_bus_reply_method_return(m, "o", + ratbagd_profile_get_path(profile)); +} + +static int +ratbagd_device_get_capabilities(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_device *device = userdata; + struct ratbag_device *lib_device = device->lib_device; + enum ratbag_device_capability cap; + enum ratbag_device_capability caps[] = { + RATBAG_DEVICE_CAP_QUERY_CONFIGURATION, + RATBAG_DEVICE_CAP_RESOLUTION, + RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION, + RATBAG_DEVICE_CAP_PROFILE, + RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE, + RATBAG_DEVICE_CAP_DISABLE_PROFILE, + RATBAG_DEVICE_CAP_DEFAULT_PROFILE, + RATBAG_DEVICE_CAP_BUTTON, + RATBAG_DEVICE_CAP_BUTTON_KEY, + RATBAG_DEVICE_CAP_BUTTON_MACROS, + RATBAG_DEVICE_CAP_LED, + }; + int r; + size_t i; + + r = sd_bus_message_open_container(reply, 'a', "u"); + if (r < 0) + return r; + + for (i = 0; i < ELEMENTSOF(caps); i++) { + cap = caps[i]; + if (ratbag_device_has_capability(lib_device, cap)) { + r = sd_bus_message_append(reply, "u", cap); + if (r < 0) + return r; + } + } + + return sd_bus_message_close_container(reply); +} + +const sd_bus_vtable ratbagd_device_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Id", "s", NULL, offsetof(struct ratbagd_device, name), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Capabilities", "au", ratbagd_device_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Name", "s", ratbagd_device_get_device_name, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Svg", "s", ratbagd_device_get_svg, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SvgPath", "s", ratbagd_device_get_svg_path, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Profiles", "ao", ratbagd_device_get_profiles, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("ActiveProfile", "u", ratbagd_device_get_active_profile, 0, 0), + SD_BUS_METHOD("GetProfileByIndex", "u", "o", ratbagd_device_get_profile_by_index, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END, +}; + +int ratbagd_device_new(struct ratbagd_device **out, + struct ratbagd *ctx, + const char *name, + struct ratbag_device *lib_device) +{ + _cleanup_(ratbagd_device_freep) struct ratbagd_device *device = NULL; + struct ratbag_profile *profile; + unsigned int i; + int r; + + assert(out); + assert(ctx); + assert(name); + + device = calloc(1, sizeof(*device)); + if (!device) + return -ENOMEM; + + device->ctx = ctx; + rbnode_init(&device->node); + device->lib_device = ratbag_device_ref(lib_device); + + device->name = strdup(name); + if (!device->name) + return -ENOMEM; + + r = sd_bus_path_encode("/org/freedesktop/ratbag1/device", + device->name, + &device->path); + if (r < 0) + return r; + + + device->n_profiles = ratbag_device_get_num_profiles(device->lib_device); + device->profiles = calloc(device->n_profiles, sizeof(*device->profiles)); + if (!device->profiles) + return -ENOMEM; + + log_verbose("%s: \"%s\", %d profiles\n", + name, + ratbag_device_get_name(lib_device), + device->n_profiles); + + for (i = 0; i < device->n_profiles; ++i) { + profile = ratbag_device_get_profile(device->lib_device, i); + if (!profile) + continue; + + r = ratbagd_profile_new(&device->profiles[i], + device, + profile, + i); + if (r < 0) { + errno = -r; + log_error("Cannot allocate profile for '%s': %m\n", + device->name); + } + } + + *out = device; + device = NULL; + return 0; +} + +struct ratbagd_device *ratbagd_device_free(struct ratbagd_device *device) +{ + unsigned int i; + + if (!device) + return NULL; + + assert(!ratbagd_device_linked(device)); + + for (i = 0; i < device->n_profiles; ++i) + device->profiles[i] = ratbagd_profile_free(device->profiles[i]); + + device->profiles = mfree(device->profiles); + device->lib_device = ratbag_device_unref(device->lib_device); + device->path = mfree(device->path); + device->name = mfree(device->name); + + assert(!device->lib_device); /* ratbag yields !NULL if still pinned */ + + return mfree(device); +} + +const char *ratbagd_device_get_name(struct ratbagd_device *device) +{ + assert(device); + return device->name; +} + +const char *ratbagd_device_get_path(struct ratbagd_device *device) +{ + assert(device); + return device->path; +} + +unsigned int ratbagd_device_get_num_buttons(struct ratbagd_device *device) +{ + assert(device); + return ratbag_device_get_num_buttons(device->lib_device); +} + +unsigned int ratbagd_device_get_num_leds(struct ratbagd_device *device) +{ + assert(device); + return ratbag_device_get_num_leds(device->lib_device); +} + +bool ratbagd_device_linked(struct ratbagd_device *device) +{ + return device && rbnode_linked(&device->node); +} + +void ratbagd_device_link(struct ratbagd_device *device) +{ + _cleanup_(freep) char *prefix = NULL; + struct ratbagd_device *iter; + RBNode **node, *parent; + int r, v; + unsigned int i; + + assert(device); + assert(!ratbagd_device_linked(device)); + + /* find place to link it to */ + parent = NULL; + node = &device->ctx->device_map.root; + while (*node) { + parent = *node; + iter = ratbagd_device_from_node(parent); + v = strcmp(device->name, iter->name); + + /* if there's a duplicate, the caller screwed up */ + assert(v != 0); + + if (v < 0) + node = &parent->left; + else /* if (v > 0) */ + node = &parent->right; + } + + /* link into context */ + rbtree_add(&device->ctx->device_map, parent, node, &device->node); + ++device->ctx->n_devices; + + /* register profile interfaces */ + r = sd_bus_path_encode_many(&prefix, + "/org/freedesktop/ratbag1/profile/%", + device->name); + if (r >= 0) { + r = sd_bus_add_fallback_vtable(device->ctx->bus, + &device->profile_vtable_slot, + prefix, + "org.freedesktop.ratbag1.Profile", + ratbagd_profile_vtable, + ratbagd_device_find_profile, + device); + if (r >= 0) + r = sd_bus_add_node_enumerator(device->ctx->bus, + &device->profile_enum_slot, + prefix, + ratbagd_device_list_profiles, + device); + } + if (r < 0) { + errno = -r; + log_error("Cannot register profile interfaces for '%s': %m\n", + device->name); + return; + } + + for (i = 0; i < device->n_profiles; i++) { + r = ratbagd_profile_register_resolutions(device->ctx->bus, + device, + device->profiles[i]); + if (r < 0) { + log_error("Cannot register resolutions for '%s': %m\n", + device->name); + } + + r = ratbagd_profile_register_buttons(device->ctx->bus, + device, + device->profiles[i]); + if (r < 0) { + log_error("Cannot register buttons for '%s': %m\n", + device->name); + } + + r = ratbagd_profile_register_leds(device->ctx->bus, + device, + device->profiles[i]); + if (r < 0) { + log_error("Cannot register leds for '%s': %m\n", + device->name); + } + } +} + +void ratbagd_device_unlink(struct ratbagd_device *device) +{ + if (!ratbagd_device_linked(device)) + return; + + device->profile_enum_slot = sd_bus_slot_unref(device->profile_enum_slot); + device->profile_vtable_slot = sd_bus_slot_unref(device->profile_vtable_slot); + + /* unlink from context */ + --device->ctx->n_devices; + rbtree_remove(&device->ctx->device_map, &device->node); + rbnode_init(&device->node); +} + +struct ratbagd_device *ratbagd_device_lookup(struct ratbagd *ctx, + const char *name) +{ + struct ratbagd_device *device; + RBNode *node; + int v; + + assert(ctx); + assert(name); + + node = ctx->device_map.root; + while (node) { + device = ratbagd_device_from_node(node); + v = strcmp(name, device->name); + if (!v) + return device; + else if (v < 0) + node = node->left; + else /* if (v > 0) */ + node = node->right; + } + + return NULL; +} + +struct ratbagd_device *ratbagd_device_first(struct ratbagd *ctx) +{ + struct RBNode *node; + + assert(ctx); + + node = rbtree_first(&ctx->device_map); + return node ? ratbagd_device_from_node(node) : NULL; +} + +struct ratbagd_device *ratbagd_device_next(struct ratbagd_device *device) +{ + struct RBNode *node; + + assert(device); + + node = rbnode_next(&device->node); + return node ? ratbagd_device_from_node(node) : NULL; +} diff --git a/ratbagd/src/ratbagd-led.c b/ratbagd/src/ratbagd-led.c new file mode 100644 index 00000000..c0c958e6 --- /dev/null +++ b/ratbagd/src/ratbagd-led.c @@ -0,0 +1,271 @@ +/*** + This file is part of ratbagd. + + Copyright 2017 Jente Hidskes . + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include "config.h" + +#include +#include +#include +#include +#include "ratbagd.h" +#include "shared-macro.h" + +struct ratbagd_led { + struct ratbag_led *lib_led; + unsigned int index; + char *path; +}; + +static int ratbagd_led_get_mode(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_led *led = userdata; + enum ratbag_led_mode mode; + + mode = ratbag_led_get_mode(led->lib_led); + return sd_bus_message_append(reply, "u", mode); +} + +static int ratbagd_led_set_mode(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_led *led = userdata; + enum ratbag_led_mode mode; + int r; + + r = sd_bus_message_read(m, "u", &mode); + if (r < 0) + return r; + + r = ratbag_led_set_mode(led->lib_led, mode); + return sd_bus_reply_method_return(m, "u", r); +} + +static int ratbagd_led_get_type(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_led *led = userdata; + const char *type = NULL; + enum ratbag_led_type t; + + t = ratbag_led_get_type(led->lib_led); + + switch(t) { + default: + log_error("Unknown led type %d\n", t); + /* fallthrough */ + case RATBAG_LED_TYPE_UNKNOWN: + type = "unknown"; + break; + case RATBAG_LED_TYPE_LOGO: + type = "logo"; + break; + case RATBAG_LED_TYPE_SIDE: + type = "side"; + break; + } + + return sd_bus_message_append(reply, "s", type); +} + +static int ratbagd_led_get_color(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_led *led = userdata; + struct ratbag_color c; + + c = ratbag_led_get_color(led->lib_led); + return sd_bus_message_append(reply, "(uuu)", c.red, c.green, c.blue); +} + +static int ratbagd_led_set_color(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_led *led = userdata; + struct ratbag_color c; + int r; + + r = sd_bus_message_read(m, "(uuu)", &c.red, &c.green, &c.blue); + if (r < 0) + return r; + + r = ratbag_led_set_color(led->lib_led, c); + return sd_bus_reply_method_return(m, "u", r); +} + +static int ratbagd_led_get_effect_rate(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_led *led = userdata; + int rate; + + rate = ratbag_led_get_effect_rate(led->lib_led); + return sd_bus_message_append(reply, "i", rate); +} + +static int ratbagd_led_set_effect_rate(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_led *led = userdata; + unsigned int rate; + int r; + + r = sd_bus_message_read(m, "u", &rate); + if (r < 0) + return r; + + if (rate < 100 || rate > 20000) + return 0; + + r = ratbag_led_set_effect_rate(led->lib_led, rate); + return sd_bus_reply_method_return(m, "u", r); +} + +static int ratbagd_led_get_brightness(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_led *led = userdata; + unsigned int brightness; + + brightness = ratbag_led_get_brightness(led->lib_led); + return sd_bus_message_append(reply, "u", brightness); +} + +static int ratbagd_led_set_brightness(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_led *led = userdata; + unsigned int brightness; + int r; + + r = sd_bus_message_read(m, "u", &brightness); + if (r < 0) + return r; + + if (brightness > 255) + return 0; + + r = ratbag_led_set_brightness(led->lib_led, brightness); + return sd_bus_reply_method_return(m, "u", r); +} + +const sd_bus_vtable ratbagd_led_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Index", "u", NULL, offsetof(struct ratbagd_led, index), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Mode", "u", ratbagd_led_get_mode, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Type", "s", ratbagd_led_get_type, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Color", "(uuu)", ratbagd_led_get_color, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("EffectRate", "i", ratbagd_led_get_effect_rate, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Brightness", "u", ratbagd_led_get_brightness, 0, SD_BUS_VTABLE_PROPERTY_CONST), + + SD_BUS_METHOD("SetMode", "u", "u", ratbagd_led_set_mode, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetColor", "(uuu)", "u", ratbagd_led_set_color, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetEffectRate", "u", "u", ratbagd_led_set_effect_rate, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetBrightness", "u", "u", ratbagd_led_set_brightness, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END, +}; + +int ratbagd_led_new(struct ratbagd_led **out, + struct ratbagd_device *device, + struct ratbagd_profile *profile, + struct ratbag_led *lib_led, + unsigned int index) +{ + _cleanup_(ratbagd_led_freep) struct ratbagd_led *led = NULL; + char profile_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1], + led_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; + int r; + + assert(out); + assert(lib_led); + + led = calloc(1, sizeof(*led)); + if (!led) + return -ENOMEM; + + led->lib_led = lib_led; + led->index = index; + + sprintf(profile_buffer, "p%u", ratbagd_profile_get_index(profile)); + sprintf(led_buffer, "b%u", index); + r = sd_bus_path_encode_many(&led->path, + "/org/freedesktop/ratbag1/led/%/%/%", + ratbagd_device_get_name(device), + profile_buffer, + led_buffer); + if (r < 0) + return r; + + *out = led; + led = NULL; + return 0; +} + +const char *ratbagd_led_get_path(struct ratbagd_led *led) +{ + assert(led); + return led->path; +} + +struct ratbagd_led *ratbagd_led_free(struct ratbagd_led *led) +{ + if (!led) + return NULL; + + led->path = mfree(led->path); + led->lib_led = ratbag_led_unref(led->lib_led); + + return mfree(led); +} + diff --git a/ratbagd/src/ratbagd-profile.c b/ratbagd/src/ratbagd-profile.c new file mode 100644 index 00000000..45c6fd35 --- /dev/null +++ b/ratbagd/src/ratbagd-profile.c @@ -0,0 +1,740 @@ +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ratbagd.h" +#include "shared-macro.h" + +struct ratbagd_profile { + struct ratbagd_device *device; + struct ratbag_profile *lib_profile; + unsigned int index; + char *path; + + sd_bus_slot *resolution_vtable_slot; + sd_bus_slot *resolution_enum_slot; + unsigned int n_resolutions; + struct ratbagd_resolution **resolutions; + + sd_bus_slot *button_vtable_slot; + sd_bus_slot *button_enum_slot; + unsigned int n_buttons; + struct ratbagd_button **buttons; + + sd_bus_slot *led_vtable_slot; + sd_bus_slot *led_enum_slot; + unsigned int n_leds; + struct ratbagd_led **leds; +}; + +static int ratbagd_profile_find_resolution(sd_bus *bus, + const char *path, + const char *interface, + void *userdata, + void **found, + sd_bus_error *error) +{ + _cleanup_(freep) char *name = NULL; + struct ratbagd_profile *profile = userdata; + unsigned int index; + int r; + + r = sd_bus_path_decode_many(path, + "/org/freedesktop/ratbag1/resolution/%/p%/r%", + NULL, + NULL, + &name); + if (r <= 0) + return r; + + r = safe_atou(name, &index); + if (r < 0) + return 0; + + if (index >= profile->n_resolutions || !profile->resolutions[index]) + return 0; + + *found = profile->resolutions[index]; + return 1; +} + +static int ratbagd_profile_get_resolutions(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_profile *profile = userdata; + struct ratbagd_resolution *resolution; + unsigned int i; + int r; + + r = sd_bus_message_open_container(reply, 'a', "o"); + if (r < 0) + return r; + + for (i = 0; i < profile->n_resolutions; ++i) { + resolution = profile->resolutions[i]; + if (!resolution) + continue; + + r = sd_bus_message_append(reply, + "o", + ratbagd_resolution_get_path(resolution)); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int ratbagd_profile_get_buttons(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_profile *profile = userdata; + struct ratbagd_button *button; + unsigned int i; + int r; + + r = sd_bus_message_open_container(reply, 'a', "o"); + if (r < 0) + return r; + + for (i = 0; i < profile->n_buttons; ++i) { + button = profile->buttons[i]; + if (!button) + continue; + + r = sd_bus_message_append(reply, + "o", + ratbagd_button_get_path(button)); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int ratbagd_profile_get_leds(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_profile *profile = userdata; + struct ratbagd_led *led; + unsigned int i; + int r; + + r = sd_bus_message_open_container(reply, 'a', "o"); + if (r < 0) + return r; + + for (i = 0; i < profile->n_leds; ++i) { + led = profile->leds[i]; + if (!led) + continue; + + r = sd_bus_message_append(reply, + "o", + ratbagd_led_get_path(led)); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static int ratbagd_profile_get_active_resolution(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_profile *profile = userdata; + struct ratbagd_resolution *resolution; + unsigned int i; + + for (i = 0; i < profile->n_resolutions; ++i) { + resolution = profile->resolutions[i]; + if (!resolution) + continue; + if (!ratbagd_resolution_is_active(resolution)) + continue; + + return sd_bus_message_append(reply, "u", i); + } + + log_error("Unable to find active resolution for %s\n", + ratbagd_device_get_name(profile->device)); + return sd_bus_message_append(reply, "u", 0); +} + +static int ratbagd_profile_get_default_resolution(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_profile *profile = userdata; + struct ratbagd_resolution *resolution; + unsigned int i; + + for (i = 0; i < profile->n_resolutions; ++i) { + resolution = profile->resolutions[i]; + if (!resolution) + continue; + if (!ratbagd_resolution_is_default(resolution)) + continue; + + return sd_bus_message_append(reply, "u", i); + } + + log_error("Unable to find default resolution for %s\n", + ratbagd_device_get_name(profile->device)); + return sd_bus_message_append(reply, "u", 0); +} + +static int ratbagd_profile_find_button(sd_bus *bus, + const char *path, + const char *interface, + void *userdata, + void **found, + sd_bus_error *error) +{ + _cleanup_(freep) char *name = NULL; + struct ratbagd_profile *profile = userdata; + unsigned int index; + int r; + + r = sd_bus_path_decode_many(path, + "/org/freedesktop/ratbag1/button/%/p%/b%", + NULL, + NULL, + &name); + if (r <= 0) + return r; + + r = safe_atou(name, &index); + if (r < 0) + return 0; + + if (index >= profile->n_buttons || !profile->buttons[index]) + return 0; + + *found = profile->buttons[index]; + return 1; +} + +static int ratbagd_profile_find_led(sd_bus *bus, + const char *path, + const char *interface, + void *userdata, + void **found, + sd_bus_error *error) +{ + _cleanup_(freep) char *name = NULL; + struct ratbagd_profile *profile = userdata; + unsigned int index; + int r; + + r = sd_bus_path_decode_many(path, + "/org/freedesktop/ratbag1/led/%/p%/b%", + NULL, + NULL, + &name); + if (r <= 0) + return r; + + r = safe_atou(name, &index); + if (r < 0) + return 0; + + if (index >= profile->n_leds || !profile->leds[index]) + return 0; + + *found = profile->leds[index]; + return 1; +} + +static int ratbagd_profile_set_active(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_profile *profile = userdata; + int r; + + r = sd_bus_message_read(m, ""); + if (r < 0) + return r; + + (void) sd_bus_emit_signal(sd_bus_message_get_bus(m), + "/org/freedesktop/ratbag1", + "/org.freedesktop.ratbag1.Profile", + "ActiveProfileChanged", + "u", + profile->index); + + return sd_bus_reply_method_return(m, "u", + ratbag_profile_set_active(profile->lib_profile)); +} + +static int ratbagd_profile_get_resolution_by_index(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_profile *profile = userdata; + struct ratbagd_resolution *resolution; + unsigned int index; + int r; + + r = sd_bus_message_read(m, "u", &index); + if (r < 0) + return r; + + if (index >= profile->n_resolutions || !profile->resolutions[index]) + return 0; + + resolution = profile->resolutions[index]; + return sd_bus_reply_method_return(m, "o", + ratbagd_resolution_get_path(resolution)); +} + +const sd_bus_vtable ratbagd_profile_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Index", "u", NULL, offsetof(struct ratbagd_profile, index), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Resolutions", "ao", ratbagd_profile_get_resolutions, 0, 0), + SD_BUS_PROPERTY("Buttons", "ao", ratbagd_profile_get_buttons, 0, 0), + SD_BUS_PROPERTY("Leds", "ao", ratbagd_profile_get_leds, 0, 0), + SD_BUS_PROPERTY("ActiveResolution", "u", ratbagd_profile_get_active_resolution, 0, 0), + SD_BUS_PROPERTY("DefaultResolution", "u", ratbagd_profile_get_default_resolution, 0, 0), + SD_BUS_METHOD("SetActive", "", "u", ratbagd_profile_set_active, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("GetResolutionByIndex", "u", "o", ratbagd_profile_get_resolution_by_index, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_SIGNAL("ActiveProfileChanged", "u", 0), + SD_BUS_VTABLE_END, +}; + +int ratbagd_profile_new(struct ratbagd_profile **out, + struct ratbagd_device *device, + struct ratbag_profile *lib_profile, + unsigned int index) +{ + _cleanup_(ratbagd_profile_freep) struct ratbagd_profile *profile = NULL; + struct ratbag_resolution *resolution; + struct ratbag_button *button; + struct ratbag_led *led; + char index_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; + unsigned int i; + int r; + + assert(out); + assert(lib_profile); + + profile = calloc(1, sizeof(*profile)); + if (!profile) + return -ENOMEM; + + profile->device = device; + profile->lib_profile = lib_profile; + profile->index = index; + + sprintf(index_buffer, "p%u", index); + r = sd_bus_path_encode_many(&profile->path, + "/org/freedesktop/ratbag1/profile/%/%", + ratbagd_device_get_name(device), + index_buffer); + if (r < 0) + return r; + + profile->n_resolutions = ratbag_profile_get_num_resolutions(profile->lib_profile); + profile->resolutions = calloc(profile->n_resolutions, sizeof(*profile->resolutions)); + if (!profile->resolutions) + return -ENOMEM; + + profile->n_buttons = ratbagd_device_get_num_buttons(device); + profile->buttons = calloc(profile->n_buttons, sizeof(*profile->buttons)); + if (!profile->buttons) + return -ENOMEM; + + profile->n_leds = ratbagd_device_get_num_leds(device); + profile->leds = calloc(profile->n_leds, sizeof(*profile->leds)); + if (!profile->leds) + return -ENOMEM; + + for (i = 0; i < profile->n_resolutions; ++i) { + resolution = ratbag_profile_get_resolution(profile->lib_profile, i); + if (!resolution) + continue; + + r = ratbagd_resolution_new(&profile->resolutions[i], + device, + profile, + resolution, + i); + if (r < 0) { + errno = -r; + log_error("Cannot allocate resolution for '%s': %m\n", + ratbagd_device_get_name(device)); + } + } + + for (i = 0; i < profile->n_buttons; ++i) { + button = ratbag_profile_get_button(profile->lib_profile, i); + if (!button) + continue; + + r = ratbagd_button_new(&profile->buttons[i], + device, + profile, + button, + i); + if (r < 0) { + errno = -r; + log_error("Cannot allocate button for '%s': %m\n", + ratbagd_device_get_name(device)); + } + } + + for (i = 0; i < profile->n_leds; ++i) { + led = ratbag_profile_get_led(profile->lib_profile, i); + if (!led) + continue; + + r = ratbagd_led_new(&profile->leds[i], + device, + profile, + led, + i); + if (r < 0) { + errno = -r; + log_error("Cannot allocate led for '%s': %m\n", + ratbagd_device_get_name(device)); + } + } + + *out = profile; + profile = NULL; + return 0; +} + +struct ratbagd_profile *ratbagd_profile_free(struct ratbagd_profile *profile) +{ + unsigned int i; + + if (!profile) + return NULL; + + profile->resolution_vtable_slot = sd_bus_slot_unref(profile->resolution_vtable_slot); + profile->resolution_enum_slot = sd_bus_slot_unref(profile->resolution_enum_slot); + profile->button_vtable_slot = sd_bus_slot_unref(profile->button_vtable_slot); + profile->button_enum_slot = sd_bus_slot_unref(profile->button_enum_slot); + profile->led_vtable_slot = sd_bus_slot_unref(profile->led_vtable_slot); + profile->led_enum_slot = sd_bus_slot_unref(profile->led_enum_slot); + + for (i = 0; i< profile->n_leds; ++i) + ratbagd_led_free(profile->leds[i]); + for (i = 0; i< profile->n_buttons; ++i) + ratbagd_button_free(profile->buttons[i]); + for (i = 0; i< profile->n_resolutions; ++i) + ratbagd_resolution_free(profile->resolutions[i]); + + mfree(profile->leds); + mfree(profile->buttons); + mfree(profile->resolutions); + + profile->path = mfree(profile->path); + profile->lib_profile = ratbag_profile_unref(profile->lib_profile); + + return mfree(profile); +} + +const char *ratbagd_profile_get_path(struct ratbagd_profile *profile) +{ + assert(profile); + return profile->path; +} + +bool ratbagd_profile_is_active(struct ratbagd_profile *profile) +{ + assert(profile); + return ratbag_profile_is_active(profile->lib_profile) != 0; +} + +unsigned int ratbagd_profile_get_index(struct ratbagd_profile *profile) +{ + assert(profile); + return profile->index; +} + +static int ratbagd_profile_list_resolutions(sd_bus *bus, + const char *path, + void *userdata, + char ***paths, + sd_bus_error *error) +{ + struct ratbagd_profile *profile = userdata; + struct ratbagd_resolution *resolution; + char **resolutions; + unsigned int i; + + resolutions = calloc(profile->n_resolutions + 1, sizeof(char *)); + if (!resolutions) + return -ENOMEM; + + for (i = 0; i < profile->n_resolutions; ++i) { + resolution = profile->resolutions[i]; + if (!resolution) + continue; + + resolutions[i] = strdup(ratbagd_resolution_get_path(resolution)); + if (!resolutions[i]) + goto error; + } + + resolutions[i] = NULL; + *paths = resolutions; + return 1; + +error: + for (i = 0; resolutions[i]; ++i) + free(resolutions[i]); + free(resolutions); + return -ENOMEM; +} + + +int ratbagd_profile_register_resolutions(struct sd_bus *bus, + struct ratbagd_device *device, + struct ratbagd_profile *profile) +{ + _cleanup_(freep) char *prefix = NULL; + char index_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; + int r; + + sprintf(index_buffer, "p%u", profile->index); + + /* register resolution interfaces */ + r = sd_bus_path_encode_many(&prefix, + "/org/freedesktop/ratbag1/resolution/%/%", + ratbagd_device_get_name(device), + index_buffer); + + if (r >= 0) { + r = sd_bus_add_fallback_vtable(bus, + &profile->resolution_vtable_slot, + prefix, + "org.freedesktop.ratbag1.Resolution", + ratbagd_resolution_vtable, + ratbagd_profile_find_resolution, + profile); + if (r >= 0) + r = sd_bus_add_node_enumerator(bus, + &profile->resolution_enum_slot, + prefix, + ratbagd_profile_list_resolutions, + profile); + } + if (r < 0) { + errno = -r; + log_error("Cannot register resolutions for '%s': %m\n", + ratbagd_device_get_name(device)); + } + + return 0; +} + +static int ratbagd_profile_list_buttons(sd_bus *bus, + const char *path, + void *userdata, + char ***paths, + sd_bus_error *error) +{ + struct ratbagd_profile *profile = userdata; + struct ratbagd_button *button; + char **buttons; + unsigned int i; + + buttons = calloc(profile->n_buttons + 1, sizeof(char *)); + if (!buttons) + return -ENOMEM; + + for (i = 0; i < profile->n_buttons; ++i) { + button = profile->buttons[i]; + if (!button) + continue; + + buttons[i] = strdup(ratbagd_button_get_path(button)); + if (!buttons[i]) + goto error; + } + + buttons[i] = NULL; + *paths = buttons; + return 1; + +error: + for (i = 0; buttons[i]; ++i) + free(buttons[i]); + free(buttons); + return -ENOMEM; +} + +int ratbagd_profile_register_buttons(struct sd_bus *bus, + struct ratbagd_device *device, + struct ratbagd_profile *profile) +{ + _cleanup_(freep) char *prefix = NULL; + char index_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; + int r; + + sprintf(index_buffer, "p%u", profile->index); + + /* register button interfaces */ + r = sd_bus_path_encode_many(&prefix, + "/org/freedesktop/ratbag1/button/%/%", + ratbagd_device_get_name(device), + index_buffer); + + if (r >= 0) { + r = sd_bus_add_fallback_vtable(bus, + &profile->button_vtable_slot, + prefix, + "org.freedesktop.ratbag1.Button", + ratbagd_button_vtable, + ratbagd_profile_find_button, + profile); + if (r >= 0) + r = sd_bus_add_node_enumerator(bus, + &profile->button_enum_slot, + prefix, + ratbagd_profile_list_buttons, + profile); + } + if (r < 0) { + errno = -r; + log_error("Cannot register buttons for '%s': %m\n", + ratbagd_device_get_name(device)); + } + + return 0; +} + +static int ratbagd_profile_list_leds(sd_bus *bus, + const char *path, + void *userdata, + char ***paths, + sd_bus_error *error) +{ + struct ratbagd_profile *profile = userdata; + struct ratbagd_led *led; + char **leds; + unsigned int i; + + leds = calloc(profile->n_leds + 1, sizeof(char *)); + if (!leds) + return -ENOMEM; + + for (i = 0; i < profile->n_leds; ++i) { + led = profile->leds[i]; + if (!led) + continue; + + leds[i] = strdup(ratbagd_led_get_path(led)); + if (!leds[i]) + goto error; + } + + leds[i] = NULL; + *paths = leds; + return 1; + +error: + for (i = 0; leds[i]; ++i) + free(leds[i]); + free(leds); + return -ENOMEM; +} + +int ratbagd_profile_register_leds(struct sd_bus *bus, + struct ratbagd_device *device, + struct ratbagd_profile *profile) +{ + _cleanup_(freep) char *prefix = NULL; + char index_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; + int r; + + sprintf(index_buffer, "p%u", profile->index); + + /* register led interfaces */ + r = sd_bus_path_encode_many(&prefix, + "/org/freedesktop/ratbag1/led/%/%", + ratbagd_device_get_name(device), + index_buffer); + + if (r >= 0) { + r = sd_bus_add_fallback_vtable(bus, + &profile->led_vtable_slot, + prefix, + "org.freedesktop.ratbag1.Led", + ratbagd_led_vtable, + ratbagd_profile_find_led, + profile); + if (r >= 0) + r = sd_bus_add_node_enumerator(bus, + &profile->led_enum_slot, + prefix, + ratbagd_profile_list_leds, + profile); + } + if (r < 0) { + errno = -r; + log_error("Cannot register leds for '%s': %m\n", + ratbagd_device_get_name(device)); + } + + return 0; +} diff --git a/ratbagd/src/ratbagd-resolution.c b/ratbagd/src/ratbagd-resolution.c new file mode 100644 index 00000000..b2ea9af6 --- /dev/null +++ b/ratbagd/src/ratbagd-resolution.c @@ -0,0 +1,235 @@ +/*** + This file is part of ratbagd. + + Copyright 2016 Red Hat, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ratbagd.h" +#include "shared-macro.h" + +struct ratbagd_resolution { + struct ratbag_resolution *lib_resolution; + unsigned int index; + char *path; + unsigned int xres, yres; + unsigned int rate; +}; + +static int ratbagd_resolution_set_report_rate(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_resolution *resolution = userdata; + unsigned int rate; + int r; + + r = sd_bus_message_read(m, "u", &rate); + if (r < 0) + return r; + + /* basic sanity check */ + if (rate > 5000 || rate % 100) + return 0; + + r = ratbag_resolution_set_report_rate(resolution->lib_resolution, + rate); + if (r == 0) { + resolution->rate = rate; + } + return sd_bus_reply_method_return(m, "u", r); +} + +static int ratbagd_resolution_set_resolution(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_resolution *resolution = userdata; + unsigned int xres, yres; + int r; + + r = sd_bus_message_read(m, "uu", &xres, &yres); + if (r < 0) + return r; + + r = ratbag_resolution_set_dpi_xy(resolution->lib_resolution, + xres, yres); + if (r == 0) { + resolution->xres = xres; + resolution->yres = yres; + } + + (void) sd_bus_emit_signal(sd_bus_message_get_bus(m), + "/org/freedesktop/ratbag1", + "/org.freedesktop.ratbag1.Resolution", + "ActiveResolutionChanged", + "u", + resolution->index); + + return sd_bus_reply_method_return(m, "u", r); +} + +static int ratbagd_resolution_set_default(sd_bus_message *m, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_resolution *resolution = userdata; + int r; + + r = ratbag_resolution_set_default(resolution->lib_resolution); + + (void) sd_bus_emit_signal(sd_bus_message_get_bus(m), + "/org/freedesktop/ratbag1", + "/org.freedesktop.ratbag1.Resolution", + "DefaultResolutionChanged", + "u", + resolution->index); + + return sd_bus_reply_method_return(m, "u", r); +} + +static int +ratbagd_resolution_get_capabilities(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd_resolution *resolution = userdata; + struct ratbag_resolution *lib_resolution = resolution->lib_resolution; + enum ratbag_device_capability cap; + int r; + + r = sd_bus_message_open_container(reply, 'a', "u"); + if (r < 0) + return r; + + cap = RATBAG_RESOLUTION_CAP_INDIVIDUAL_REPORT_RATE; + if (ratbag_resolution_has_capability(lib_resolution, cap)) { + r = sd_bus_message_append(reply, "u", cap); + if (r < 0) + return r; + } + + cap = RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION; + if (ratbag_resolution_has_capability(lib_resolution, cap)) { + r = sd_bus_message_append(reply, "u", cap); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +const sd_bus_vtable ratbagd_resolution_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Index", "u", NULL, offsetof(struct ratbagd_resolution, index), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("Capabilities", "au", ratbagd_resolution_get_capabilities, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("XResolution", "u", NULL, offsetof(struct ratbagd_resolution, xres), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("YResolution", "u", NULL, offsetof(struct ratbagd_resolution, yres), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("ReportRate", "u", NULL, offsetof(struct ratbagd_resolution, rate), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_METHOD("SetReportRate", "u", "u", ratbagd_resolution_set_report_rate, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetResolution", "uu", "u", ratbagd_resolution_set_resolution, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD("SetDefault", "", "u", ratbagd_resolution_set_default, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_SIGNAL("ActiveResolutionChanged", "u", 0), + SD_BUS_SIGNAL("DefaultResolutionChanged", "u", 0), + SD_BUS_VTABLE_END, +}; + +int ratbagd_resolution_new(struct ratbagd_resolution **out, + struct ratbagd_device *device, + struct ratbagd_profile *profile, + struct ratbag_resolution *lib_resolution, + unsigned int index) +{ + _cleanup_(ratbagd_resolution_freep) struct ratbagd_resolution *resolution = NULL; + char profile_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1], + resolution_buffer[DECIMAL_TOKEN_MAX(unsigned int) + 1]; + int r; + + assert(out); + assert(lib_resolution); + + resolution = calloc(1, sizeof(*resolution)); + if (!resolution) + return -ENOMEM; + + resolution->lib_resolution = lib_resolution; + resolution->index = index; + resolution->xres = ratbag_resolution_get_dpi_x(lib_resolution); + resolution->yres = ratbag_resolution_get_dpi_y(lib_resolution); + resolution->rate = ratbag_resolution_get_report_rate(lib_resolution); + + sprintf(profile_buffer, "p%u", ratbagd_profile_get_index(profile)); + sprintf(resolution_buffer, "r%u", index); + r = sd_bus_path_encode_many(&resolution->path, + "/org/freedesktop/ratbag1/resolution/%/%/%", + ratbagd_device_get_name(device), + profile_buffer, + resolution_buffer); + if (r < 0) + return r; + + *out = resolution; + resolution = NULL; + return 0; +} + +const char *ratbagd_resolution_get_path(struct ratbagd_resolution *resolution) +{ + assert(resolution); + return resolution->path; +} + +struct ratbagd_resolution *ratbagd_resolution_free(struct ratbagd_resolution *resolution) +{ + if (!resolution) + return NULL; + + resolution->path = mfree(resolution->path); + resolution->lib_resolution = ratbag_resolution_unref(resolution->lib_resolution); + + return mfree(resolution); +} + +bool ratbagd_resolution_is_active(struct ratbagd_resolution *resolution) +{ + assert(resolution); + return ratbag_resolution_is_active(resolution->lib_resolution) != 0; +} + +bool ratbagd_resolution_is_default(struct ratbagd_resolution *resolution) +{ + assert(resolution); + return ratbag_resolution_is_default(resolution->lib_resolution) != 0; +} diff --git a/ratbagd/src/ratbagd.c b/ratbagd/src/ratbagd.c new file mode 100644 index 00000000..112b89ab --- /dev/null +++ b/ratbagd/src/ratbagd.c @@ -0,0 +1,489 @@ +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ratbagd.h" +#include "shared-macro.h" + +static bool verbose = false; + +void log_verbose(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + + if (verbose) + vprintf(fmt, args); + va_end(args); +} + +void log_error(const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + fprintf(stderr, "%s error: ", program_invocation_short_name); + vfprintf(stderr, fmt, args); + va_end(args); +} + +static int ratbagd_find_device(sd_bus *bus, + const char *path, + const char *interface, + void *userdata, + void **found, + sd_bus_error *error) +{ + _cleanup_(freep) char *name = NULL; + struct ratbagd *ctx = userdata; + struct ratbagd_device *device; + int r; + + r = sd_bus_path_decode_many(path, + "/org/freedesktop/ratbag1/device/%", + &name); + if (r <= 0) + return r; + + device = ratbagd_device_lookup(ctx, name); + if (!device) + return 0; + + *found = device; + return 1; +} + +static int ratbagd_list_devices(sd_bus *bus, + const char *path, + void *userdata, + char ***paths, + sd_bus_error *error) +{ + struct ratbagd *ctx = userdata; + struct ratbagd_device *device; + char **devices, **pos; + + devices = calloc(ctx->n_devices + 1, sizeof(char *)); + if (!devices) + return -ENOMEM; + + pos = devices; + + RATBAGD_DEVICE_FOREACH(device, ctx) { + *pos = strdup(ratbagd_device_get_path(device)); + if (!*pos) + goto error; + ++pos; + } + + *pos = NULL; + *paths = devices; + return 1; + +error: + for (pos = devices; *pos; ++pos) + free(*pos); + free(devices); + return -ENOMEM; +} + +static int ratbagd_get_devices(sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) +{ + struct ratbagd *ctx = userdata; + struct ratbagd_device *device; + int r; + + r = sd_bus_message_open_container(reply, 'a', "o"); + if (r < 0) + return r; + + RATBAGD_DEVICE_FOREACH(device, ctx) { + r = sd_bus_message_append(reply, + "o", + ratbagd_device_get_path(device)); + if (r < 0) + return r; + } + + return sd_bus_message_close_container(reply); +} + +static const sd_bus_vtable ratbagd_vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_PROPERTY("Devices", "ao", ratbagd_get_devices, 0, 0), + SD_BUS_SIGNAL("DeviceNew", "o", 0), + SD_BUS_SIGNAL("DeviceRemoved", "o", 0), + SD_BUS_VTABLE_END, +}; + +static void ratbagd_process_device(struct ratbagd *ctx, + struct udev_device *udevice) +{ + struct ratbag_device *lib_device; + struct ratbagd_device *device; + const char *name; + int r; + + /* + * TODO: libratbag should provide some mechanism to allow + * device-grouping, just like libinput does. If multiple input + * devices belong to the same virtual device, we should not add + * it multiple times. Instead, libratbag should group them and + * provide *us* a unique name that identifies the group, rather + * than taking a random input-device as tag. + */ + + name = udev_device_get_sysname(udevice); + if (!name || !startswith(name, "event")) + return; + + device = ratbagd_device_lookup(ctx, name); + + if (streq_ptr("remove", udev_device_get_action(udevice))) { + /* device was removed, unlink it and destroy our context */ + if (device) { + (void) sd_bus_emit_signal(ctx->bus, + "/org/freedesktop/ratbag1", + "org.freedesktop.ratbag1.Manager", + "DeviceRemoved", + "o", + ratbagd_device_get_path(device)); + ratbagd_device_unlink(device); + ratbagd_device_free(device); + } + } else if (device) { + /* device already known, refresh our view of the device */ + } else { + enum ratbag_error_code error; + + /* device unknown, create new one and link it */ + error = ratbag_device_new_from_udev_device(ctx->lib_ctx, + udevice, + &lib_device); + if (error != RATBAG_SUCCESS) + return; /* unsupported device */ + + r = ratbagd_device_new(&device, ctx, name, lib_device); + + /* the ratbagd_device takes its own reference, drop ours */ + ratbag_device_unref(lib_device); + + if (r < 0) { + log_error("Cannot track device '%s'\n", name); + return; + } + + ratbagd_device_link(device); + (void) sd_bus_emit_signal(ctx->bus, + "/org/freedesktop/ratbag1", + "org.freedesktop.ratbag1.Manager", + "DeviceNew", + "o", + ratbagd_device_get_path(device)); + } +} + +static int ratbagd_monitor_event(sd_event_source *source, + int fd, + uint32_t mask, + void *userdata) +{ + struct ratbagd *ctx = userdata; + struct udev_device *udevice; + + udevice = udev_monitor_receive_device(ctx->monitor); + if (!udevice) + return 0; + + ratbagd_process_device(ctx, udevice); + udev_device_unref(udevice); + + return 0; +} + +static int ratbagd_lib_open_restricted(const char *path, + int flags, + void *userdata) +{ + return open(path, flags, 0); +} + +static void ratbagd_lib_close_restricted(int fd, void *userdata) +{ + safe_close(fd); +} + +static const struct ratbag_interface ratbagd_lib_interface = { + .open_restricted = ratbagd_lib_open_restricted, + .close_restricted = ratbagd_lib_close_restricted, +}; + +static struct ratbagd *ratbagd_free(struct ratbagd *ctx) +{ + if (!ctx) + return NULL; + + ctx->bus = sd_bus_flush_close_unref(ctx->bus); + ctx->monitor_source = sd_event_source_unref(ctx->monitor_source); + ctx->monitor = udev_monitor_unref(ctx->monitor); + ctx->lib_ctx = ratbag_unref(ctx->lib_ctx); + ctx->event = sd_event_unref(ctx->event); + + assert(!ctx->device_map.root); + assert(!ctx->lib_ctx); /* ratbag returns non-NULL if still pinned */ + + return mfree(ctx); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd *, ratbagd_free); + +static int ratbagd_init_monitor(struct ratbagd *ctx) +{ + struct udev *udev; + int r; + + udev = udev_new(); + if (!udev) + return -ENOMEM; + + ctx->monitor = udev_monitor_new_from_netlink(udev, "udev"); + + /* we don't need the context to stay around; drop it */ + udev_unref(udev); + + if (!ctx->monitor) + return -ENOMEM; + + r = udev_monitor_filter_add_match_subsystem_devtype(ctx->monitor, + "input", + NULL); + if (r < 0) + return r; + + r = udev_monitor_enable_receiving(ctx->monitor); + if (r < 0) + return r; + + r = sd_event_add_io(ctx->event, + &ctx->monitor_source, + udev_monitor_get_fd(ctx->monitor), + EPOLLIN, + ratbagd_monitor_event, + ctx); + if (r < 0) + return r; + + return 0; +} + +static int ratbagd_new(struct ratbagd **out) +{ + _cleanup_(ratbagd_freep) struct ratbagd *ctx = NULL; + int r; + + ctx = calloc(1, sizeof(*ctx)); + if (!ctx) + return -ENOMEM; + + r = sd_event_default(&ctx->event); + if (r < 0) + return r; + + r = sd_event_set_watchdog(ctx->event, true); + if (r < 0) + return r; + + ctx->lib_ctx = ratbag_create_context(&ratbagd_lib_interface, ctx); + if (!ctx->lib_ctx) + return -ENOMEM; + + if (verbose) + ratbag_log_set_priority(ctx->lib_ctx, + RATBAG_LOG_PRIORITY_DEBUG); + + r = ratbagd_init_monitor(ctx); + if (r < 0) + return r; + + r = sd_bus_open_system(&ctx->bus); + if (r < 0) + return r; + + r = sd_bus_add_object_vtable(ctx->bus, + NULL, + "/org/freedesktop/ratbag1", + "org.freedesktop.ratbag1.Manager", + ratbagd_vtable, + ctx); + if (r < 0) + return r; + + r = sd_bus_add_fallback_vtable(ctx->bus, + NULL, + "/org/freedesktop/ratbag1/device", + "org.freedesktop.ratbag1.Device", + ratbagd_device_vtable, + ratbagd_find_device, + ctx); + if (r < 0) + return r; + + r = sd_bus_add_node_enumerator(ctx->bus, + NULL, + "/org/freedesktop/ratbag1/device", + ratbagd_list_devices, + ctx); + if (r < 0) + return r; + + r = sd_bus_request_name(ctx->bus, "org.freedesktop.ratbag1", 0); + if (r < 0) + return r; + + r = sd_bus_attach_event(ctx->bus, ctx->event, 0); + if (r < 0) + return r; + + *out = ctx; + ctx = NULL; + return 0; +} + +static int ratbagd_run_enumerate(struct ratbagd *ctx) +{ + struct udev_list_entry *list, *iter; + struct udev_enumerate *e; + struct udev *udev; + int r; + + udev = udev_monitor_get_udev(ctx->monitor); + + e = udev_enumerate_new(udev); + if (!e) + return -ENOMEM; + + r = udev_enumerate_add_match_subsystem(e, "input"); + if (r < 0) + goto exit; + + r = udev_enumerate_add_match_is_initialized(e); + if (r < 0) + goto exit; + + r = udev_enumerate_scan_devices(e); + if (r < 0) + goto exit; + + list = udev_enumerate_get_list_entry(e); + udev_list_entry_foreach(iter, list) { + struct udev_device *udevice; + const char *p; + + p = udev_list_entry_get_name(iter); + udevice = udev_device_new_from_syspath(udev, p); + if (udevice) + ratbagd_process_device(ctx, udevice); + udev_device_unref(udevice); + } + + r = 0; + +exit: + udev_enumerate_unref(e); + return r; +} + +static int ratbagd_run(struct ratbagd *ctx) +{ + int r; + + /* + * TODO: We should support exit-on-idle and bus-activation. Note that + * we don't store any state on our own, hence, all we have to do + * is to make sure we advertise device add/remove events via the + * bus (in case there is a listener). + * To track such events, we should store devices we already + * advertised in /run/ratbagd/devices/ and read it out on + * activation. + * Note that this feature requires udev-activation, which might + * not be possible, yet. + */ + + r = ratbagd_run_enumerate(ctx); + if (r < 0) + return r; + + return sd_event_loop(ctx->event); +} + +int main(int argc, char *argv[]) +{ + struct ratbagd *ctx = NULL; + int r; + + if (argc > 1) { + if (streq(argv[1], "--verbose")) { + verbose = true; + } else { + fprintf(stderr, "Usage: %s [--verbose]\n", + program_invocation_short_name); + r = -EINVAL; + goto exit; + } + } + + r = ratbagd_new(&ctx); + if (r < 0) + goto exit; + + r = ratbagd_run(ctx); + +exit: + ratbagd_free(ctx); + + if (r < 0) { + errno = -r; + log_error("Failed: %m\n"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/ratbagd/src/ratbagd.h b/ratbagd/src/ratbagd.h new file mode 100644 index 00000000..7a70f2af --- /dev/null +++ b/ratbagd/src/ratbagd.h @@ -0,0 +1,170 @@ +#pragma once + +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "shared-macro.h" +#include "shared-rbtree.h" + +struct ratbagd; +struct ratbagd_device; +struct ratbagd_profile; +struct ratbagd_resolution; +struct ratbagd_button; +struct ratbagd_led; + +void log_verbose(const char *fmt, ...) _printf_(1, 2); +void log_error(const char *fmt, ...) _printf_(1, 2); + +/* + * Profiles + */ + +extern const sd_bus_vtable ratbagd_profile_vtable[]; + +int ratbagd_profile_new(struct ratbagd_profile **out, + struct ratbagd_device *device, + struct ratbag_profile *lib_profile, + unsigned int index); +struct ratbagd_profile *ratbagd_profile_free(struct ratbagd_profile *profile); +const char *ratbagd_profile_get_path(struct ratbagd_profile *profile); +bool ratbagd_profile_is_active(struct ratbagd_profile *profile); +bool ratbagd_profile_is_default(struct ratbagd_profile *profile); +unsigned int ratbagd_profile_get_index(struct ratbagd_profile *profile); +int ratbagd_profile_register_resolutions(struct sd_bus *bus, + struct ratbagd_device *device, + struct ratbagd_profile *profile); +int ratbagd_profile_register_buttons(struct sd_bus *bus, + struct ratbagd_device *device, + struct ratbagd_profile *profile); +int ratbagd_profile_register_leds(struct sd_bus *bus, + struct ratbagd_device *device, + struct ratbagd_profile *profile); + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_profile *, ratbagd_profile_free); + +/* + * Resolutions + */ +extern const sd_bus_vtable ratbagd_resolution_vtable[]; + +int ratbagd_resolution_new(struct ratbagd_resolution **out, + struct ratbagd_device *device, + struct ratbagd_profile *profile, + struct ratbag_resolution *lib_resolution, + unsigned int index); +struct ratbagd_resolution *ratbagd_resolution_free(struct ratbagd_resolution *resolution); +const char *ratbagd_resolution_get_path(struct ratbagd_resolution *resolution); +bool ratbagd_resolution_is_active(struct ratbagd_resolution *resolution); +bool ratbagd_resolution_is_default(struct ratbagd_resolution *resolution); + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_resolution *, ratbagd_resolution_free); + +/* + * Buttons + */ +extern const sd_bus_vtable ratbagd_button_vtable[]; + +int ratbagd_button_new(struct ratbagd_button **out, + struct ratbagd_device *device, + struct ratbagd_profile *profile, + struct ratbag_button *lib_button, + unsigned int index); +struct ratbagd_button *ratbagd_button_free(struct ratbagd_button *button); +const char *ratbagd_button_get_path(struct ratbagd_button *button); + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_button *, ratbagd_button_free); + +/* + * Leds + */ +extern const sd_bus_vtable ratbagd_led_vtable[]; + +int ratbagd_led_new(struct ratbagd_led **out, + struct ratbagd_device *device, + struct ratbagd_profile *profile, + struct ratbag_led *lib_led, + unsigned int index); +struct ratbagd_led *ratbagd_led_free(struct ratbagd_led *led); +const char *ratbagd_led_get_path(struct ratbagd_led *led); + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_led *, ratbagd_led_free); + +/* + * Devices + */ + +extern const sd_bus_vtable ratbagd_device_vtable[]; + +int ratbagd_device_new(struct ratbagd_device **out, + struct ratbagd *ctx, + const char *name, + struct ratbag_device *lib_device); +struct ratbagd_device *ratbagd_device_free(struct ratbagd_device *device); +const char *ratbagd_device_get_name(struct ratbagd_device *device); +const char *ratbagd_device_get_path(struct ratbagd_device *device); +unsigned int ratbagd_device_get_num_buttons(struct ratbagd_device *device); +unsigned int ratbagd_device_get_num_leds(struct ratbagd_device *device); + +bool ratbagd_device_linked(struct ratbagd_device *device); +void ratbagd_device_link(struct ratbagd_device *device); +void ratbagd_device_unlink(struct ratbagd_device *device); + +DEFINE_TRIVIAL_CLEANUP_FUNC(struct ratbagd_device *, ratbagd_device_free); + +struct ratbagd_device *ratbagd_device_lookup(struct ratbagd *ctx, + const char *name); +struct ratbagd_device *ratbagd_device_first(struct ratbagd *ctx); +struct ratbagd_device *ratbagd_device_next(struct ratbagd_device *device); + +#define RATBAGD_DEVICE_FOREACH(_device, _ctx) \ + for ((_device) = ratbagd_device_first(_ctx); \ + (_device); \ + (_device) = ratbagd_device_next(_device)) + +/* + * Context + */ + +struct ratbagd { + sd_event *event; + struct ratbag *lib_ctx; + struct udev_monitor *monitor; + sd_event_source *monitor_source; + sd_bus *bus; + + RBTree device_map; + size_t n_devices; +}; diff --git a/ratbagd/src/shared-macro.h b/ratbagd/src/shared-macro.h new file mode 100644 index 00000000..490f20fc --- /dev/null +++ b/ratbagd/src/shared-macro.h @@ -0,0 +1,260 @@ +#pragma once + +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * We require: + * sizeof(void*) == sizeof(long) + * sizeof(long) == 4 || sizeof(long) == 8 + * sizeof(int) == 4 + * The linux kernel requires the same from the toolchain, so this should work + * just fine. + */ +#if __SIZEOF_POINTER__ != __SIZEOF_LONG__ +# error "sizeof(void*) != sizeof(long)" +#elif __SIZEOF_LONG__ != 4 && __SIZEOF_LONG__ != 8 +# error "sizeof(long) != 4 && sizeof(long) != 8" +#elif __SIZEOF_INT__ != 4 +# error "sizeof(int) != 4" +#endif + +/* + * Shortcuts for gcc attributes. See GCC manual for details. + */ +#define _alignas_(_x) __attribute__((aligned(__alignof(_x)))) +#define _alloc_(...) __attribute__((alloc_size(__VA_ARGS__))) +#define _cleanup_(_x) __attribute__((cleanup(_x))) +#define _const_ __attribute__((const)) +#define _deprecated_ __attribute__((deprecated)) +#define _hidden_ __attribute__((visibility("hidden"))) +#define _likely_(_x) (__builtin_expect(!!(_x), 1)) +#define _malloc_ __attribute__((malloc)) +#define _packed_ __attribute__((packed)) +#define _printf_(_a, _b) __attribute__((format (printf, _a, _b))) +#define _public_ __attribute__((visibility("default"))) +#define _pure_ __attribute__((pure)) +#define _sentinel_ __attribute__((sentinel)) +#define _unlikely_(_x) (__builtin_expect(!!(_x), 0)) +#define _unused_ __attribute__((unused)) +#define _weak_ __attribute__((weak)) +#define _weakref_(_x) __attribute__((weakref(#_x))) + +/* + * DECIMAL_TOKEN_MAX() - calculate maximum length of the decimal representation + * of an integer + * @_type: type of integer + */ +#define DECIMAL_TOKEN_MAX(_type) \ + (1 + (sizeof(_type) <= 1 ? 3 : \ + sizeof(_type) <= 2 ? 5 : \ + sizeof(_type) <= 4 ? 10 : \ + sizeof(_type) <= 8 ? 20 : \ + sizeof(int[-2 * (sizeof(_type) > 8)]))) + +/* + * PROTECT_ERRNO: make sure a function protects errno + */ +static inline void reset_errno(int *saved_errno) +{ + errno = *saved_errno; +} +#define PROTECT_ERRNO _cleanup_(reset_errno) _unused_ int saved_errno = errno + +/* + * DEFINE_TRIVIAL_CLEANUP_FUNC() - define helper suitable for _cleanup_() + * @_type: type of object to cleanup + * @_func: function to call on cleanup + */ +#define DEFINE_TRIVIAL_CLEANUP_FUNC(_type, _func) \ + static inline void _func##p(_type *_p) \ + { \ + if (*_p) \ + _func(*_p); \ + } \ + struct __useless_struct_to_allow_trailing_semicolon__ + +/* + * ELEMENTSOF() - number of array elements + * @_array: C array + * + * This calculates the number of elements (compared to the byte size) of + * the given C array. This returns (void) if the passed argument is not + * actually a valid C array (decided at compile time). + */ +#define ELEMENTSOF(_array) \ + __extension__ (__builtin_choose_expr( \ + !__builtin_types_compatible_p(typeof(_array), \ + typeof(&*(_array))), \ + sizeof(_array) / sizeof((_array)[0]), \ + (void)0)) + +/* + * negative_errno() - return negative errno + * + * This helper should be used to shut up gcc if you know 'errno' is + * negative. Instead of "return -errno;", use "return negative_errno();" + * It will suppress bogus gcc warnings in case it assumes 'errno' might + * be 0 and thus the caller's error-handling might not be triggered. + */ +static inline int negative_errno(void) +{ + return (errno <= 0) ? -EINVAL : -errno; +} + +/* + * mfree() - free memory + * @mem: memory to free + * + * This is basically the same as free(), but returns NULL. This makes free() + * follow the same style as all our other destructors. + */ +static inline void *mfree(void *mem) +{ + free(mem); + return NULL; +} + +/* + * safe_close() - safe variant of close(2) + * @fd: fd to close + * + * This is the same as close(2), but allows passing negative FDs, which makes + * it a no-op. Furthermore, this always returns -1. + */ +static inline int safe_close(int fd) +{ + if (fd >= 0) { + PROTECT_ERRNO; + close(fd); + } + return -1; +} + +/* + * Miscellaneous cleanup helpers + * All these helpers are suitable for use with _cleanup_(). They call the helper + * they're named after on the variable marked for cleanup. + */ +static inline void freep(void *p) +{ + free(*(void**)p); +} + +static inline void safe_closep(int *fd) +{ + safe_close(*fd); +} + +/* + * streq() - test whether two strings are equal + * @_a: string A + * @_b: string B + */ +#define streq(_a, _b) (!strcmp((_a), (_b))) + +/* + * streq_ptr() - test whether two strings are equal, considering NULL valid + * @a: string A or NULL + * @b: string B or NULL + */ +static inline bool streq_ptr(const char *a, const char *b) +{ + return (a && b) ? streq(a, b) : (!a && !b); +} + +/* + * startswith() - test prefix of a string + * @s: string to test + * @prefix: prefix to test for + * + * This returns a pointer to the first character in @s that follows @prefix. If + * @s does not start with @prefix, NULL is returned. + */ +static inline char *startswith(const char *s, const char *prefix) +{ + size_t l; + + l = strlen(prefix); + if (strncmp(s, prefix, l) == 0) + return (char*) s + l; + + return NULL; +} + +/* + * safe_atou() - safe variant of strtoul() + * @s: string to parse + * @ret_u: output storage for parsed integer + * + * This is a sane and safe variant of strtoul(), which rejects any invalid + * input, or trailing garbage. + * + * Return: 0 on success, negative error code on failure. + */ +static inline int safe_atou(const char *s, unsigned int *ret_u) +{ + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret_u); + + errno = 0; + l = strtoul(s, &x, 0); + if (!x || x == s || *x || errno) + return errno > 0 ? -errno : -EINVAL; + + if ((unsigned long)(unsigned int)l != l) + return -ERANGE; + + *ret_u = (unsigned int)l; + return 0; +} + +/* + * now() - returns current time in nano-seconds + * @clock: clock to use + */ +static inline uint64_t now(clockid_t clock) +{ + struct timespec spec = {}; + clock_gettime(clock, &spec); + return spec.tv_sec * 1000ULL * 1000ULL * 1000ULL + spec.tv_nsec; +} diff --git a/ratbagd/src/shared-rbtree.c b/ratbagd/src/shared-rbtree.c new file mode 100644 index 00000000..ca144284 --- /dev/null +++ b/ratbagd/src/shared-rbtree.c @@ -0,0 +1,455 @@ +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +/* + * Open Red-Black-Tree Implementation + * You're highly recommended to read a paper on rb-trees before reading this + * code. The reader is expected to be familiar with the different cases that + * might occur during insertion and removal of elements. The comments in this + * file do not contain a full prove for correctness. + */ + +#include "config.h" + +#include "shared-rbtree.h" + +RBNode *rbnode_leftmost(RBNode *n) { + if (n) + while (n->left) + n = n->left; + return n; +} + +RBNode *rbnode_rightmost(RBNode *n) { + if (n) + while (n->right) + n = n->right; + return n; +} + +RBNode *rbtree_first(RBTree *t) { + return rbnode_leftmost(t->root); +} + +RBNode *rbtree_last(RBTree *t) { + return rbnode_rightmost(t->root); +} + +RBNode *rbnode_next(RBNode *n) { + RBNode *p; + + if (!rbnode_linked(n)) + return NULL; + + if (n->right) + return rbnode_leftmost(n->right); + + while ((p = rbnode_parent(n)) && n == p->right) + n = p; + + return p; +} + +RBNode *rbnode_prev(RBNode *n) { + RBNode *p; + + if (!rbnode_linked(n)) + return NULL; + + if (n->left) + return rbnode_rightmost(n->left); + + while ((p = rbnode_parent(n)) && n == p->left) + n = p; + + return p; +} + +static inline void rbnode_reparent(RBNode *n, RBNode *p, unsigned long c) { + /* change color and/or parent of a node */ + rbtree_assert(!((unsigned long)p & 1)); + rbtree_assert(c < 2); + n->__parent_and_color = (RBNode*)((unsigned long)p | c); +} + +static inline void rbtree_reparent(RBTree *t, RBNode *p, RBNode *old, RBNode *new) { + /* change previous parent/root from old to new node */ + if (p) { + if (p->left == old) + p->left = new; + else + p->right = new; + } else { + t->root = new; + } +} + +static inline RBNode *rbtree_paint_one(RBTree *t, RBNode *n) { + RBNode *p, *g, *gg, *u, *x; + + /* + * Paint a single node according to RB-Tree rules. The node must + * already be linked into the tree and painted red. + * We repaint the node or rotate the tree, if required. In case a + * recursive repaint is required, the next node to be re-painted + * is returned. + * p: parent + * g: grandparent + * gg: grandgrandparent + * u: uncle + * x: temporary + */ + + /* node is red, so we can access the parent directly */ + p = n->__parent_and_color; + + if (!p) { + /* Case 1: + * We reached the root. Mark it black and be done. As all + * leaf-paths share the root, the ratio of black nodes on each + * path stays the same. */ + rbnode_reparent(n, p, RBNODE_BLACK); + n = NULL; + } else if (rbnode_black(p)) { + /* Case 2: + * The parent is already black. As our node is red, we did not + * change the number of black nodes on any path, nor do we have + * multiple consecutive red nodes. */ + n = NULL; + } else if (p == p->__parent_and_color->left) { /* parent is red, so grandparent exists */ + g = p->__parent_and_color; + gg = rbnode_parent(g); + u = g->right; + + if (u && rbnode_red(u)) { + /* Case 3: + * Parent and uncle are both red. We know the + * grandparent must be black then. Repaint parent and + * uncle black, the grandparent red and recurse into + * the grandparent. */ + rbnode_reparent(p, g, RBNODE_BLACK); + rbnode_reparent(u, g, RBNODE_BLACK); + rbnode_reparent(g, gg, RBNODE_RED); + n = g; + } else { + /* parent is red, uncle is black */ + + if (n == p->right) { + /* Case 4: + * We're the right child. Rotate on parent to + * become left child, so we can handle it the + * same as case 5. */ + x = n->left; + p->right = n->left; + n->left = p; + if (x) + rbnode_reparent(x, p, RBNODE_BLACK); + rbnode_reparent(p, n, RBNODE_RED); + p = n; + } + + /* 'n' is invalid from here on! */ + n = NULL; + + /* Case 5: + * We're the red left child or a red parent, black + * grandparent and uncle. Rotate on grandparent and + * switch color with parent. Number of black nodes on + * each path stays the same, but we got rid of the + * double red path. As the grandparent is still black, + * we're done. */ + x = p->right; + g->left = x; + p->right = g; + if (x) + rbnode_reparent(x, g, RBNODE_BLACK); + rbnode_reparent(p, gg, RBNODE_BLACK); + rbnode_reparent(g, p, RBNODE_RED); + rbtree_reparent(t, gg, g, p); + } + } else /* if (p == p->__parent_and_color->left) */ { /* same as above, but mirrored */ + g = p->__parent_and_color; + gg = rbnode_parent(g); + u = g->left; + + if (u && rbnode_red(u)) { + rbnode_reparent(p, g, RBNODE_BLACK); + rbnode_reparent(u, g, RBNODE_BLACK); + rbnode_reparent(g, gg, RBNODE_RED); + n = g; + } else { + if (n == p->left) { + x = n->right; + p->left = n->right; + n->right = p; + if (x) + rbnode_reparent(x, p, RBNODE_BLACK); + rbnode_reparent(p, n, RBNODE_RED); + p = n; + } + + n = NULL; + + x = p->left; + g->right = x; + p->left = g; + if (x) + rbnode_reparent(x, g, RBNODE_BLACK); + rbnode_reparent(p, gg, RBNODE_BLACK); + rbnode_reparent(g, p, RBNODE_RED); + rbtree_reparent(t, gg, g, p); + } + } + + return n; +} + +static inline void rbtree_paint(RBTree *t, RBNode *n) { + while (n) + n = rbtree_paint_one(t, n); +} + +void rbtree_add(RBTree *t, RBNode *p, RBNode **l, RBNode *n) { + n->__parent_and_color = p; + n->left = n->right = NULL; + *l = n; + + rbtree_paint(t, n); +} + +static inline RBNode *rbtree_rebalance_one(RBTree *t, RBNode *p, RBNode *n) { + RBNode *s, *x, *y, *g; + + /* + * Rebalance tree after a node was removed. This happens only if you + * remove a black node and one path is now left with an unbalanced + * number or black nodes. + * This function assumes all paths through p and n have one black node + * less than all other paths. If recursive fixup is required, the + * current node is returned. + */ + + if (n == p->left) { + s = p->right; + if (rbnode_red(s)) { + /* Case 3: + * We have a red node as sibling. Rotate it onto our + * side so we can later on turn it black. This way, we + * gain the additional black node in our path. */ + g = rbnode_parent(p); + x = s->left; + p->right = x; + s->left = p; + rbnode_reparent(x, p, RBNODE_BLACK); + rbnode_reparent(s, g, rbnode_color(p)); + rbnode_reparent(p, s, RBNODE_RED); + rbtree_reparent(t, g, p, s); + s = x; + } + + x = s->right; + if (!x || rbnode_black(x)) { + y = s->left; + if (!y || rbnode_black(y)) { + /* Case 4: + * Our sibling is black and has only black + * children. Flip it red and turn parent black. + * This way we gained a black node in our path, + * or we fix it recursively one layer up, which + * will rotate the red sibling as parent. */ + rbnode_reparent(s, p, RBNODE_RED); + if (rbnode_black(p)) + return p; + + rbnode_reparent(p, rbnode_parent(p), RBNODE_BLACK); + return NULL; + } + + /* Case 5: + * Left child of our sibling is red, right one is black. + * Rotate on parent so the right child of our sibling is + * now red, and we can fall through to case 6. */ + x = y->right; + s->left = y->right; + y->right = s; + p->right = y; + if (x) + rbnode_reparent(x, s, RBNODE_BLACK); + x = s; + s = y; + } + + /* Case 6: + * The right child of our sibling is red. Rotate left and flip + * colors, which gains us an additional black node in our path, + * that was previously on our sibling. */ + g = rbnode_parent(p); + y = s->left; + p->right = y; + s->left = p; + rbnode_reparent(x, s, RBNODE_BLACK); + if (y) + rbnode_reparent(y, p, rbnode_color(y)); + rbnode_reparent(s, g, rbnode_color(p)); + rbnode_reparent(p, s, RBNODE_BLACK); + rbtree_reparent(t, g, p, s); + } else /* if (!n || n == p->right) */ { /* same as above, but mirrored */ + s = p->left; + if (rbnode_red(s)) { + g = rbnode_parent(p); + x = s->right; + p->left = x; + s->right = p; + rbnode_reparent(x, p, RBNODE_BLACK); + rbnode_reparent(s, g, RBNODE_BLACK); + rbnode_reparent(p, s, RBNODE_RED); + rbtree_reparent(t, g, p, s); + s = x; + } + + x = s->left; + if (!x || rbnode_black(x)) { + y = s->right; + if (!y || rbnode_black(y)) { + rbnode_reparent(s, p, RBNODE_RED); + if (rbnode_black(p)) + return p; + + rbnode_reparent(p, rbnode_parent(p), RBNODE_BLACK); + return NULL; + } + + x = y->left; + s->right = y->left; + y->left = s; + p->left = y; + if (x) + rbnode_reparent(x, s, RBNODE_BLACK); + x = s; + s = y; + } + + g = rbnode_parent(p); + y = s->right; + p->left = y; + s->right = p; + rbnode_reparent(x, s, RBNODE_BLACK); + if (y) + rbnode_reparent(y, p, rbnode_color(y)); + rbnode_reparent(s, g, rbnode_color(p)); + rbnode_reparent(p, s, RBNODE_BLACK); + rbtree_reparent(t, g, p, s); + } + + return NULL; +} + +static inline void rbtree_rebalance(RBTree *t, RBNode *p) { + RBNode *n = NULL; + + while (p) { + n = rbtree_rebalance_one(t, p, n); + p = n ? rbnode_parent(n) : NULL; + } +} + +void rbtree_remove(RBTree *t, RBNode *n) { + RBNode *p, *s, *gc, *x, *next = NULL; + unsigned long c; + + /* + * To remove an interior node from a binary tree, we simply find its + * successor, swap both nodes and then remove the node. Therefore, the + * only interesting case is were the node to be removed has at most one + * child. + * p: parent + * s: successor + * gc: grand-...-child + * x: temporary + * next: next node to rebalance on + */ + + if (!n->left) { + /* Case 1: + * We have at most one child, which must be red. We're + * guaranteed to be black then. Replace our node with the child + * (in case it exists), but turn it black. + * If no child exists, we need to rebalance, in case we were + * black as our path now lost a black node. */ + p = rbnode_parent(n); + c = rbnode_color(n); + rbtree_reparent(t, p, n, n->right); + if (n->right) + rbnode_reparent(n->right, p, c); + else + next = (c == RBNODE_BLACK) ? p : NULL; + } else if (!n->right) { + /* case 1 mirrored (but n->left guaranteed non-NULL) */ + p = rbnode_parent(n); + c = rbnode_color(n); + rbnode_reparent(n->left, p, c); + rbtree_reparent(t, p, n, n->left); + } else { + /* Case 2: + * We're an interior node. Find our successor and swap it with + * our node. Then remove our node. For performance reasons we + * don't perform the full swap, but skip links that are about to + * be removed, anyway. */ + s = n->right; + if (!s->left) { + /* right child is next, no need to touch grandchild */ + p = s; + gc = s->right; + } else { + /* find successor and swap partially */ + s = rbnode_leftmost(s); + p = rbnode_parent(s); + + gc = s->right; + p->left = s->right; + s->right = n->right; + rbnode_reparent(n->right, s, rbnode_color(n->right)); + } + + /* node is partially swapped, now remove as in case 1 */ + + s->left = n->left; + rbnode_reparent(n->left, s, rbnode_color(n->left)); + + x = rbnode_parent(n); + c = rbnode_color(n); + rbtree_reparent(t, x, n, s); + if (gc) { + rbnode_reparent(s, x, c); + rbnode_reparent(gc, p, RBNODE_BLACK); + } else { + next = rbnode_black(s) ? p : NULL; + rbnode_reparent(s, x, c); + } + } + + if (next) + rbtree_rebalance(t, next); +} diff --git a/ratbagd/src/shared-rbtree.h b/ratbagd/src/shared-rbtree.h new file mode 100644 index 00000000..6ffba442 --- /dev/null +++ b/ratbagd/src/shared-rbtree.h @@ -0,0 +1,107 @@ +#pragma once + +/*** + This file is part of ratbagd. + + Copyright 2015 David Herrmann + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice (including the next + paragraph) shall be included in all copies or substantial portions of the + Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +***/ + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#ifdef RBTREE_DEBUG +# include +# define rbtree_assert(_x) assert(_x) +#else +# define rbtree_assert(_x) +#endif + +typedef struct RBTree RBTree; +typedef struct RBNode RBNode; + +enum { + RBNODE_RED = 0, + RBNODE_BLACK = 1, +}; + +struct RBTree { + RBNode *root; +}; + +struct RBNode { + RBNode *__parent_and_color; + RBNode *left; + RBNode *right; +}; + +#define rbnode_of(_ptr, _type, _member) \ + __extension__ ({ \ + const typeof( ((_type*)0)->_member ) *__ptr = (_ptr); \ + __ptr ? (_type*)( (char*)__ptr - offsetof(_type, _member) ) : NULL; \ + }) + +#define RBNODE_INIT(var) ((RBNode){ .__parent_and_color = (&var) }) + +static inline RBNode *rbnode_init(RBNode *n) { + *n = RBNODE_INIT(*n); + return n; +} + +static inline RBNode *rbnode_parent(RBNode *n) { + return (RBNode*)((unsigned long)n->__parent_and_color & ~1UL); +} + +static inline int rbnode_linked(RBNode *n) { + return n && n->__parent_and_color != n; +} + +static inline unsigned long rbnode_color(RBNode *n) { + return (unsigned long)n->__parent_and_color & 1UL; +} + +static inline int rbnode_red(RBNode *n) { + return rbnode_color(n) == RBNODE_RED; +} + +static inline int rbnode_black(RBNode *n) { + return rbnode_color(n) == RBNODE_BLACK; +} + +RBNode *rbnode_leftmost(RBNode *n); +RBNode *rbnode_rightmost(RBNode *n); + +RBNode *rbtree_first(RBTree *t); +RBNode *rbtree_last(RBTree *t); +RBNode *rbnode_next(RBNode *n); +RBNode *rbnode_prev(RBNode *n); + +void rbtree_add(RBTree *t, RBNode *p, RBNode **l, RBNode *n); +void rbtree_remove(RBTree *t, RBNode *n); + +#ifdef __cplusplus +} +#endif diff --git a/ratbagd/tools/ratbagctl.in b/ratbagd/tools/ratbagctl.in new file mode 100755 index 00000000..d409c254 --- /dev/null +++ b/ratbagd/tools/ratbagctl.in @@ -0,0 +1,517 @@ +#!/usr/bin/env python3 +# +# vim: set expandtab shiftwidth=4 tabstop=4: +# +# This file is part of ratbagd. +# +# Copyright 2016 Red Hat, Inc. +# +# Permission is hereby granted, free of charge, to any person obtaining a +# copy of this software and associated documentation files (the "Software"), +# to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, +# and/or sell copies of the Software, and to permit persons to whom the +# Software is furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice (including the next +# paragraph) shall be included in all copies or substantial portions of the +# Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +# DEALINGS IN THE SOFTWARE. + +from __future__ import print_function +from gi.repository import GLib +from gi.repository import Gio + +import sys +import argparse + +class RatbagdDBusUnavailable(BaseException): + """ + Signals DBus is unavailable or the ratbagd daemon is not available. + """ + pass + +class _RatbagdDBus(object): + def __init__(self, interface, object_path): + self._dbus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None) + if self._dbus is None: + raise RatbagdDBusUnavailable() + + try: + self._proxy = Gio.DBusProxy.new_sync(self._dbus, + Gio.DBusProxyFlags.NONE, + None, + 'org.freedesktop.ratbag1', + object_path, + 'org.freedesktop.ratbag1.{}'.format(interface), + None) + except GLib.GError: + raise RatbagdDBusUnavailable() + + if self._proxy.get_name_owner() is None: + raise RatbagdDBusUnavailable() + + def dbus_property(self, property): + p = self._proxy.get_cached_property(property) + if p != None: + return p.unpack() + return p + + def dbus_call(self, method, type, *value): + val = GLib.Variant("({})".format(type), value ) + self._proxy.call_sync(method, val, Gio.DBusCallFlags.NO_AUTO_START, 500, None) + +class Ratbagd(_RatbagdDBus): + """ + The ratbagd top-level object. Provides a list of devices available + through ratbagd, actual interaction with the devices is via the + RatbagdDevice, RatbagdProfile and RatbagdResolution objects. + + Throws RatbagdDBusUnavailable when the DBus service is not available. + """ + def __init__(self): + _RatbagdDBus.__init__(self, "Manager", '/org/freedesktop/ratbag1') + self._devices = [] + result = self.dbus_property("Devices") + if result != None: + self._devices = [RatbagdDevice(objpath) for objpath in result] + + @property + def devices(self): + """ + A list of RatbagdDevice objects supported by ratbagd. + """ + return self._devices + +class RatbagdDevice(_RatbagdDBus): + CAP_SWITCHABLE_RESOLUTION = 1 + CAP_SWITCHABLE_PROFILE = 2 + CAP_BUTTON_KEY = 3 + CAP_LED = 4 + CAP_BUTTON_MACROS = 5 + CAP_DEFAULT_PROFILE = 6 + CAP_QUERY_CONFIGURATION = 7 + CAP_DISABLE_PROFILE = 8 + + """ + Represents a ratbagd device. + """ + def __init__(self, object_path): + _RatbagdDBus.__init__(self, "Device", object_path) + self._devnode = self.dbus_property("Id") + self._name = self.dbus_property("Name") + self._svg = self.dbus_property("Svg") + self._svg_path = self.dbus_property("SvgPath") + + self._profiles = [] + self._active_profile = -1 + result = self.dbus_property("Profiles") + if result != None: + self._profiles = [RatbagdProfile(objpath) for objpath in result] + self._active_profile = self.dbus_property("ActiveProfile") + + self._caps = self.dbus_property("Capabilities") + + @property + def profiles(self): + """ + A list of RatbagdProfile objects provided by this device. + """ + return self._profiles + + @property + def name(self): + """ + The device name, usually provided by the kernel. + """ + return self._name + + @property + def svg(self): + """ + The SVG file name. This function returns the file name only, not the + absolute path to the file. + """ + return self._svg + + @property + def svg_path(self): + """ + The absolute SVG path. This function returns the full path to the + svg file. + """ + return self._svg_path + + @property + def id(self): + """ + A unique identifier for this device. + """ + return self._devnode + + @property + def active_profile(self): + """ + The currently active profile. This function returns a RatbagdProfile + or None if no active profile was found. + """ + if self._active_profile == -1: + return None + return self._profiles[self._active_profile] + + @property + def capabilities(self): + """ + Return the capabilities of this device as an array. + Capabilities not present on the device are not in the list. Thus use + e.g. + if RatbagdDevice.CAP_SWITCHABLE_RESOLUTION is in device.caps: + do something + """ + return self._caps + + def __eq__(self, other): + return other and self._objpath == other._objpath + +class RatbagdProfile(_RatbagdDBus): + """ + Represents a ratbagd profile + """ + def __init__(self, object_path): + _RatbagdDBus.__init__(self, "Profile", object_path) + self._objpath = object_path + self._index = self.dbus_property("Index") + + self._resolutions = [] + self._active_resolution_idx = -1 + self._default_resolution_idx = -1 + self._buttons = [] + + result = self.dbus_property("Resolutions") + if result != None: + self._resolutions = [RatbagdResolution(objpath) for objpath in result] + self._active_resolution_idx = self.dbus_property("ActiveResolution") + self._default_resolution_idx = self.dbus_property("DefaultResolution") + + result = self.dbus_property("Buttons") + if result != None: + self._buttons = [RatbagdButton(objpath) for objpath in result] + + @property + def index(self): + return self._index + + @property + def resolutions(self): + """ + A list of RatbagdResolution objects with this profile's resolutions. + """ + return self._resolutions + + @property + def active_resolution(self): + """ + The currently active resolution. This function returns a + RatbagdResolution object or None. + """ + if self._active_resolution_idx == -1: + return None + return self._resolutions[self._active_resolution_idx] + + @property + def default_resolution(self): + """ + The default resolution. This function returns a RatbagdResolution + object or None. + """ + if self._default_resolution_idx == -1: + return None + return self._resolutions[self._default_resolution_idx] + + @property + def buttons(self): + """ + A list of RatbagdButton objects with this profile's button + mappings. Note that the list of buttons differs between profiles but + the number of buttons is identical across profiles. + """ + return self._buttons + + def __eq__(self, other): + return self._objpath == other._objpath + +class RatbagdResolution(_RatbagdDBus): + CAP_INDIVIDUAL_REPORT_RATE = 1 + CAP_SEPARATE_XY_RESOLUTION = 2 + """ + Represents a ratbagd resolution. + """ + def __init__(self, object_path): + _RatbagdDBus.__init__(self, "Resolution", object_path) + self._index = self.dbus_property("Index") + self._xres = self.dbus_property("XResolution") + self._yres = self.dbus_property("YResolution") + self._rate = self.dbus_property("ReportRate") + self._objpath = object_path + + self._caps = self.dbus_property("Capabilities") + + @property + def resolution(self): + """Returns the tuple (xres, yres) with each resolution in DPI""" + return (self._xres, self._yres) + + @resolution.setter + def resolution(self, res): + return self.dbus_call("SetResolution", "uu", *res) + + @property + def report_rate(self): + """ + Returns the report rate in Hz. + """ + return self._rate + + @report_rate.setter + def report_rate(self, rate): + return self.dbus_call("SetReportRate", "u", rate) + + @property + def capabilities(self): + """ + Return the capabilities of this device as a list. + Capabilities not present on the device are not in the list. Thus use + e.g. + if RatbagdResolution.CAP_SEPARATE_XY_RESOLUTION is in resolution.caps: + do something + """ + return self._caps + + def __eq__(self, other): + return self._objpath == other._objpath + +class RatbagdButton(_RatbagdDBus): + """ + Represents a ratbagd button. + """ + def __init__(self, object_path): + _RatbagdDBus.__init__(self, "Button", object_path) + self._index = self.dbus_property("Index") + self._button = self.dbus_property("ButtonMapping") + + @property + def index(self): + return self._index + + @property + def button_type(self): + return self.dbus_property("Type") + + @property + def action_type(self): + return self.dbus_property("ActionType") + + @property + def special(self): + self._special = self.dbus_property("SpecialMapping") + return self._special + + @special.setter + def special(self, special): + return self.dbus_call("SetSpecialMapping", "s", special) + + @property + def key(self): + self._key = self.dbus_property("KeyMapping") + return self._key + + @key.setter + def key(self, key, modifiers): + return self.dbus_call("SetKeyMapping", "au", [key].append(modifiers)) + + @property + def button(self): + self._button = self.dbus_property("ButtonMapping") + return self._button + + @button.setter + def button(self, button): + return self.dbus_call("SetButtonMapping", "u", button) + + def disable(self): + return self.dbus_call("Disable", "") + +def list_devices(r, args): + for d in r.devices: + print("{:10s} {:32s}".format(d.id + ":", d.name)) + +def find_device(r, args): + dev = None + for d in r.devices: + if d.id == args.device: + dev = d + break; + if dev is None: + print("Unable to find device {}".format(args.device)) + sys.exit(1) + return dev + +def find_profile(r, args): + d = find_device(r, args) + try: + p = d.profiles[args.profile] + except IndexError: + print("Invalid profile index {}".format(args.profile)) + sys.exit(1) + return p, d + +def find_resolution(r, args): + p, d = find_profile(r, args) + try: + r = p.resolutions[args.resolution] + except IndexError: + print("Invalid resolution index {}".format(args.resolution)) + sys.exit(1) + return r, p, d + +def find_button(r, args): + p, d = find_profile(r, args) + try: + b = p.buttons[args.button] + except IndexError: + print("Invalid button index {}".format(args.button)) + sys.exit(1) + return b, p, d + +def show_device(r, args): + d = find_device(r, args) + + + caps = { RatbagdDevice.CAP_SWITCHABLE_RESOLUTION : "switchable-resolution", + RatbagdDevice.CAP_SWITCHABLE_PROFILE : "switchable-profile", + RatbagdDevice.CAP_BUTTON_KEY : "button-keys", + RatbagdDevice.CAP_BUTTON_MACROS : "button-macros", + RatbagdDevice.CAP_DEFAULT_PROFILE : "default-profile", + RatbagdDevice.CAP_QUERY_CONFIGURATION : "query-configuration", + RatbagdDevice.CAP_DISABLE_PROFILE : "disable-profile", + RatbagdDevice.CAP_LED : "led" } + capabilities = [caps[c] for c in d.capabilities] + + print("{} - {}".format(d.id, d.name)) + print(" SVG: {}".format(d.svg)) + print(" Capabilities: {}".format(", ".join(capabilities))) + print("Number of Profiles: {}".format(len(d.profiles))) + active = -1 + for i, p in enumerate(d.profiles): + if p == d.active_profile: + active = i + break + print(" Active Profile: {}".format(active)) + +def show_profile(r, args): + p, d = find_profile(r, args) + + print("Profile {} on {} ({})".format(args.profile, d.id, d.name)) + print(" Number of Buttons: {}".format(len(p.buttons))) + print("Number of Resolutions: {}".format(len(p.resolutions))) + active, default = -1, -1 + for i, r in enumerate(p.resolutions): + if p.active_resolution == r: + active = i + if p.default_resolution == r: + default = i + + print(" Active Resolution: {}".format(active)) + print(" Default Resolution: {}".format(default)) + +def show_resolution(r, args): + r, p, d = find_resolution(r, args) + + print("Resolution {} on Profile {} on {} ({})".format(args.resolution, + args.profile, + d.id, + d.name)) + print(" Report Rate: {}Hz".format(r.report_rate)) + if RatbagdResolution.CAP_SEPARATE_XY_RESOLUTION in r.capabilities: + print(" Resolution: {}x{}dpi".format(*r.resolution)) + else: + print(" Resolution: {}dpi".format(r.resolution[0])) + + + caps = { RatbagdResolution.CAP_INDIVIDUAL_REPORT_RATE : "individual-report-rate", + RatbagdResolution.CAP_SEPARATE_XY_RESOLUTION : "separate-xy-resolution" } + capabilities = [caps[c] for c in r.capabilities] + print(" Capabilities: {}".format(", ".join(capabilities))) + +def show_button(r, args): + b, p, d = find_button(r, args) + + print("Button {} on Profile {} on {} ({})".format(args.button, + args.profile, + d.id, + d.name)) + + print(" Type: {}".format(b.button_type)) + print(" Action Type: {}".format(b.action_type)) + + if b.action_type == "button": + print(" Button Mapping: {}".format(b.button)) + elif b.action_type == "key": + print(" Key Mapping: {}".format(b.key)) + elif b.action_type == "special": + print("Special Mapping: {}".format(b.special)) + +def make_parser(): + parser = argparse.ArgumentParser(description="Inspect and modify configurable mice") + parser.add_argument("-V", "--version", action="version", version="@version@") + subs = parser.add_subparsers(title="COMMANDS", help=None) + + list = subs.add_parser("list-devices", help="List available configurable mice") + list.set_defaults(func=list_devices) + + show = subs.add_parser("show-device", help="Show device information") + show.add_argument("device", metavar="event0", help="The device node") + show.set_defaults(func=show_device) + + profile = subs.add_parser("show-profile", help="Show profile information") + profile.add_argument("device", metavar="event0", help="The device node") + profile.add_argument("profile", metavar="0", help="The profile index", type=int) + profile.set_defaults(func=show_profile) + + resolution = subs.add_parser("show-resolution", help="Show resolution information") + resolution.add_argument("device", metavar="event0", help="The device node") + resolution.add_argument("profile", metavar="0", help="The profile index", type=int) + resolution.add_argument("resolution", metavar="0", help="The resolution index", type=int) + resolution.set_defaults(func=show_resolution) + + button = subs.add_parser("show-button", help="Show button information") + button.add_argument("device", metavar="event0", help="The device node") + button.add_argument("profile", metavar="0", help="The profile index", type=int) + button.add_argument("button", metavar="0", help="The button index", type=int) + button.set_defaults(func=show_button) + + return parser + +def main(argv): + if not argv: + argv = ["list-devices"] + cmd = make_parser().parse_args(argv) + + try: + r = Ratbagd() + if not r.devices: + print("No devices available.") + except RatbagdDBusUnavailable: + print("Unable to connect to ratbagd over dbus") + + cmd.func(r, cmd) + +if __name__ == "__main__": + main(sys.argv[1:])