forked from drath/libratbag
Merge pull request #1 from whot/python-bindings
Add a python module for ratbagd, replace ratbagctl with a python version
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -26,6 +26,7 @@ libtool
|
||||
*.m4
|
||||
!m4/attributes.m4
|
||||
ratbagctl
|
||||
ratbagd
|
||||
./ratbagd
|
||||
stamp-*
|
||||
ratbagd.service
|
||||
ratbagctl
|
||||
|
||||
28
Makefile.am
28
Makefile.am
@@ -54,23 +54,6 @@ libshared_la_CFLAGS = \
|
||||
libshared_la_LIBADD = \
|
||||
-lm
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ratbagctl
|
||||
|
||||
bin_PROGRAMS += \
|
||||
ratbagctl
|
||||
|
||||
ratbagctl_SOURCES = \
|
||||
src/ratbagctl.c
|
||||
|
||||
ratbagctl_CFLAGS = \
|
||||
$(AM_CFLAGS) \
|
||||
$(LIBSYSTEMD_CFLAGS)
|
||||
|
||||
ratbagctl_LDADD = \
|
||||
libshared.la \
|
||||
$(LIBSYSTEMD_LIBS)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ratbagd
|
||||
|
||||
@@ -105,3 +88,14 @@ $(unit_DATA): $(unit_DATA).in
|
||||
|
||||
EXTRA_DIST += ratbagd.service.in
|
||||
BUILT_SOURCES += ratbagd.service
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# python module
|
||||
dist_pkgpython_DATA = \
|
||||
python/ratbagd/__init__.py
|
||||
|
||||
bin_SCRIPTS = \
|
||||
python/ratbagctl
|
||||
|
||||
python/ratbagctl: python/ratbagctl.py
|
||||
$(AM_V_GEN)$(SED) -e "s/__VERSION__/$(PACKAGE_VERSION)/" < $^ > $@
|
||||
|
||||
@@ -34,6 +34,7 @@ AC_PROG_CC_C99
|
||||
|
||||
AC_PATH_PROG([M4], [m4])
|
||||
AC_PATH_PROG([XSLTPROC], [xsltproc])
|
||||
AM_PATH_PYTHON
|
||||
|
||||
AS_IF([! ln --relative --help > /dev/null 2>&1], [AC_MSG_ERROR([*** ln doesn't support --relative ***])])
|
||||
|
||||
|
||||
184
python/ratbagctl.py
Executable file
184
python/ratbagctl.py
Executable file
@@ -0,0 +1,184 @@
|
||||
#!/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.description))
|
||||
|
||||
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)
|
||||
|
||||
print("{} - {}".format(d.id, d.description))
|
||||
print(" SVG: {}".format(d.svg))
|
||||
print(" Capabilities: {}".format(", ".join(d.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.description))
|
||||
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.description))
|
||||
print(" Report Rate: {}Hz".format(r.report_rate))
|
||||
if "separate-xy-resolution" in r.capabilities:
|
||||
print(" Resolution: {}x{}dpi".format(r.resolution))
|
||||
else:
|
||||
print(" Resolution: {}dpi".format(r.resolution[0]))
|
||||
|
||||
print(" Capabilities: {}".format(", ".join(r.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.description))
|
||||
|
||||
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:])
|
||||
334
python/ratbagd/__init__.py
Normal file
334
python/ratbagd/__init__.py
Normal file
@@ -0,0 +1,334 @@
|
||||
# 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)
|
||||
self._proxy = Gio.DBusProxy.new_sync(self._dbus,
|
||||
Gio.DBusProxyFlags.NONE,
|
||||
None,
|
||||
'org.freedesktop.ratbag1',
|
||||
object_path,
|
||||
'org.freedesktop.ratbag1.{}'.format(interface),
|
||||
None)
|
||||
if self._proxy.get_name_owner() == 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.
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
Represents a ratbagd device.
|
||||
"""
|
||||
def __init__(self, object_path):
|
||||
_RatbagdDBus.__init__(self, "Device", object_path)
|
||||
self._devnode = self.dbus_property("Id")
|
||||
self._description = self.dbus_property("Description")
|
||||
self._svg = self.dbus_property("Svg")
|
||||
|
||||
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")
|
||||
|
||||
caps = [ "switchable-resolution",
|
||||
"switchable-profile",
|
||||
"button-keys",
|
||||
"button-macros",
|
||||
"default-profile"]
|
||||
self._caps = []
|
||||
for c in caps:
|
||||
cname = "Cap" + "".join([n.capitalize() for n in c.split("-")])
|
||||
if self.dbus_property(cname):
|
||||
self._caps.append(c)
|
||||
|
||||
@property
|
||||
def profiles(self):
|
||||
"""
|
||||
A list of RatbagdProfile objects provided by this device.
|
||||
"""
|
||||
return self._profiles
|
||||
|
||||
@property
|
||||
def description(self):
|
||||
"""
|
||||
The device name, usually provided by the kernel.
|
||||
"""
|
||||
return self._description
|
||||
|
||||
@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 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 a list of identifier strings.
|
||||
Capabilities not present on the device are not in the list. Thus use
|
||||
if "capability" is in deivce.caps:
|
||||
do something
|
||||
|
||||
Capabilities supported: "switchable-resolution",
|
||||
"switchable-profile", "button-keys", "button-macros",
|
||||
"default-profile"
|
||||
"""
|
||||
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):
|
||||
"""
|
||||
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
|
||||
|
||||
caps = [ "individual-report-rate",
|
||||
"separate-xy-resolution" ]
|
||||
self._caps = []
|
||||
for c in caps:
|
||||
cname = "Cap" + "".join([n.capitalize() for n in c.split("-")])
|
||||
if self.dbus_property(cname):
|
||||
self._caps.append(c)
|
||||
|
||||
@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 of identifier strings.
|
||||
Capabilities not present on the device are not in the list. Thus use
|
||||
if "capability" is in resolution.caps:
|
||||
do something
|
||||
|
||||
Capabilities supported: "individual-report-rate",
|
||||
"separate-xy-resolution"
|
||||
"""
|
||||
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", "")
|
||||
1164
src/ratbagctl.c
1164
src/ratbagctl.c
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user