pax_global_header00006660000000000000000000000064131155331670014517gustar00rootroot0000000000000052 comment=932cf905ef9765051ce22b153a846154442b4078 ratbagd-0.4/000077500000000000000000000000001311553316700127665ustar00rootroot00000000000000ratbagd-0.4/.gitignore000066400000000000000000000001211311553316700147500ustar00rootroot00000000000000*.a *.log *.o *.stamp *.swp *.trs *~ .dirstamp .libs/ /*.tar.xz tags __pycache__ ratbagd-0.4/COPYING000066400000000000000000000021701311553316700140210ustar00rootroot00000000000000Copyright © 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. ratbagd-0.4/README.md000066400000000000000000000144751311553316700142600ustar00rootroot00000000000000ratbagd ======= 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 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 - 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 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 dbus/org.freedesktop.ratbag1.service /etc/dbus-1/system-services/org.freedesktop.ratbag1.conf sudo cp 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. 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=no 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: [...] ratbagd-0.4/dbus/000077500000000000000000000000001311553316700137235ustar00rootroot00000000000000ratbagd-0.4/dbus/org.freedesktop.ratbag1.conf000066400000000000000000000012711311553316700212140ustar00rootroot00000000000000 ratbagd-0.4/dbus/org.freedesktop.ratbag1.service.in000066400000000000000000000002011311553316700223240ustar00rootroot00000000000000[D-BUS Service] Name=org.freedesktop.ratbag1 Exec=@bindir@/ratbagd User=root SystemdService=dbus-org.freedesktop.ratbag1.service ratbagd-0.4/doc/000077500000000000000000000000001311553316700135335ustar00rootroot00000000000000ratbagd-0.4/doc/ratbagctl.1000066400000000000000000000032751311553316700155670ustar00rootroot00000000000000.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). ratbagd-0.4/doc/ratbagd.8000066400000000000000000000010431311553316700152260ustar00rootroot00000000000000.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). ratbagd-0.4/meson.build000066400000000000000000000074401311553316700151350ustar00rootroot00000000000000project('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-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')) #### python bindings #### py3_mod = import('python3') py3 = py3_mod.find_python() py3_dep = dependency('python3', required : true) pysrc_ratbagd = [ 'python/ratbagd/__init__.py' ] py3_purelib = py3_mod.sysconfig_path('purelib') install_data(pysrc_ratbagd, install_dir : py3_purelib + '/ratbagd') config_ratbagctl = configuration_data() config_ratbagctl.set('version', meson.project_version()) configure_file(input : 'python/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) ratbagd-0.4/meson_options.txt000066400000000000000000000004031311553316700164200ustar00rootroot00000000000000option('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]') ratbagd-0.4/python/000077500000000000000000000000001311553316700143075ustar00rootroot00000000000000ratbagd-0.4/python/ratbagctl.in000077500000000000000000000165761311553316700166240ustar00rootroot00000000000000#!/usr/bin/env python # # 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 ratbagd import * import sys import argparse 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): 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:]) ratbagd-0.4/python/ratbagd/000077500000000000000000000000001311553316700157135ustar00rootroot00000000000000ratbagd-0.4/python/ratbagd/__init__.py000066400000000000000000000251231311553316700200270ustar00rootroot00000000000000# 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 gi.repository import GLib from gi.repository import Gio 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", "") ratbagd-0.4/ratbagd.service.in000066400000000000000000000003341311553316700163610ustar00rootroot00000000000000[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 ratbagd-0.4/src/000077500000000000000000000000001311553316700135555ustar00rootroot00000000000000ratbagd-0.4/src/org.freedesktop.ratbag1.conf000066400000000000000000000007161311553316700210510ustar00rootroot00000000000000 ratbagd-0.4/src/ratbagd-button.c000066400000000000000000000361671311553316700166530ustar00rootroot00000000000000/*** 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); } ratbagd-0.4/src/ratbagd-device.c000066400000000000000000000341321311553316700165650ustar00rootroot00000000000000/*** 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; int r; r = sd_bus_message_open_container(reply, 'a', "u"); if (r < 0) return r; cap = RATBAG_DEVICE_CAP_SWITCHABLE_RESOLUTION; if (ratbag_device_has_capability(lib_device, cap)) { r = sd_bus_message_append(reply, "u", cap); if (r < 0) return r; } cap = RATBAG_DEVICE_CAP_SWITCHABLE_PROFILE; if (ratbag_device_has_capability(lib_device, cap)) { r = sd_bus_message_append(reply, "u", cap); if (r < 0) return r; } cap = RATBAG_DEVICE_CAP_BUTTON_MACROS; if (ratbag_device_has_capability(lib_device, cap)) { r = sd_bus_message_append(reply, "u", cap); if (r < 0) return r; } cap = RATBAG_DEVICE_CAP_DEFAULT_PROFILE; if (ratbag_device_has_capability(lib_device, cap)) { r = sd_bus_message_append(reply, "u", cap); if (r < 0) return r; } cap = RATBAG_DEVICE_CAP_QUERY_CONFIGURATION; 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); } 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]); } } 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; } ratbagd-0.4/src/ratbagd-profile.c000066400000000000000000000347001311553316700167670ustar00rootroot00000000000000/*** 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; }; 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_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_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("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; 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; 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)); } } *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); 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->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; } ratbagd-0.4/src/ratbagd-resolution.c000066400000000000000000000161341311553316700175330ustar00rootroot00000000000000/*** 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; } ratbagd-0.4/src/ratbagd.c000066400000000000000000000255711311553316700153370ustar00rootroot00000000000000/*** 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; } ratbagd-0.4/src/ratbagd.h000066400000000000000000000120771311553316700153410ustar00rootroot00000000000000#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; 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); 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); /* * 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); 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; }; ratbagd-0.4/src/shared-macro.h000066400000000000000000000162601311553316700163000ustar00rootroot00000000000000#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; } ratbagd-0.4/src/shared-rbtree.c000066400000000000000000000421111311553316700164470ustar00rootroot00000000000000/*** 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); } ratbagd-0.4/src/shared-rbtree.h000066400000000000000000000060051311553316700164560ustar00rootroot00000000000000#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