From 03def72547cbdb981c1780e44b8ce5449aeb23b9 Mon Sep 17 00:00:00 2001 From: Martin Rys Date: Sat, 21 Sep 2024 23:25:53 +0200 Subject: [PATCH] Assortment of linter and typing changes. Also replaces all instances of old alias IOError with OSError. --- ruff.toml | 10 ++-- scc/drivers/ds5drv.py | 4 +- scc/drivers/evdevdrv.py | 4 +- scc/drivers/hiddrv.py | 22 +++----- scc/drivers/sc_by_bt.py | 4 +- scc/gui/app.py | 53 ++++++++++--------- scc/gui/creg/dialog.py | 4 +- scc/gui/userdata_manager.py | 60 +++++++++++----------- scc/lib/daemon.py | 2 +- scc/lib/eudevmonitor.py | 16 +++--- scc/lib/hidparse.py | 17 +++--- scc/lib/hidraw.py | 71 +++++++++++++------------ scc/osd/__init__.py | 100 +++++++++++++++++------------------- scc/osd/area.py | 40 +++++++-------- scc/osd/menu.py | 6 +-- scc/profile.py | 4 +- scc/scripts.py | 6 +-- 17 files changed, 203 insertions(+), 220 deletions(-) diff --git a/ruff.toml b/ruff.toml index c6cacf6b..4092bee1 100644 --- a/ruff.toml +++ b/ruff.toml @@ -17,7 +17,11 @@ indent-style = 'tab' [lint] select = ['ALL'] ignore = [ - 'W191', # We use tabs for indents, disabling this atrocious PEP 8 recommendation - 'D206', # ^ - 'ERA001' # Test for commented out code, but it has way too many false positives, so disable + 'W191', # We use tabs for indents, disabling this atrocious PEP 8 recommendation + 'D206', # ^ + 'D401', # non-imperative-mood - Wants docstrings in imperative language but it's really not foolproof, disable + 'ERA001', # Test for commented out code, but it has way too many false positives, so disable + 'FBT001', # boolean-type-hint-positional-argument - Allow positional booleans in functions, it's not really that much of an issue + 'FBT002', # boolean-default-value-positional-argument - ^ + 'FBT003', # boolean-positional-value-in-call - ^ ] diff --git a/scc/drivers/ds5drv.py b/scc/drivers/ds5drv.py index 60865ca8..2a603706 100644 --- a/scc/drivers/ds5drv.py +++ b/scc/drivers/ds5drv.py @@ -1099,7 +1099,7 @@ def _gyro_input(self, *a): new_state = new_state._replace( **{axis: int(event.value * factor)} ) - except IOError: + except OSError: # Errors here are not even reported, evdev class handles important ones return @@ -1140,7 +1140,7 @@ def _touchpad_input(self, *a): buttons=b, cpad_x=0, cpad_y=0 ) - except IOError: + except OSError: # Errors here are not even reported, evdev class handles important ones return diff --git a/scc/drivers/evdevdrv.py b/scc/drivers/evdevdrv.py index 08f828e8..8676b006 100644 --- a/scc/drivers/evdevdrv.py +++ b/scc/drivers/evdevdrv.py @@ -219,7 +219,7 @@ def input(self, *a): new_state = new_state._replace(buttons=b, **{ axis : value }) else: new_state = new_state._replace(**{ axis : value }) - except IOError as e: + except OSError as e: # TODO: Maybe check e.errno to determine exact error # all of them are fatal for now log.error(e) @@ -462,7 +462,7 @@ def make_new_device(self, factory, evdevdevice: InputDevice, *userdata): """ try: controller = factory(self.daemon, evdevdevice, *userdata) - except IOError as e: + except OSError as e: print("Failed to open device:", str(e), file=sys.stderr) return None if controller: diff --git a/scc/drivers/hiddrv.py b/scc/drivers/hiddrv.py index 46ed0666..f13dd12f 100644 --- a/scc/drivers/hiddrv.py +++ b/scc/drivers/hiddrv.py @@ -2,6 +2,7 @@ Borrows bit of code and configuration from evdevdrv. """ +from __future__ import annotations import ctypes import json import logging @@ -254,7 +255,7 @@ def __init__(self, device, daemon: "SCCDaemon", handle, config_file, config, tes self._ready = True - def _load_hid_descriptor(self, config, max_size, vid, pid, test_mode): + def _load_hid_descriptor(self, config, max_size, vid: int, pid: int, test_mode): hid_descriptor = HIDController.find_sys_devices_descriptor(vid, pid) if hid_descriptor is None: hid_descriptor = self.handle.getRawDescriptor( @@ -265,9 +266,7 @@ def _load_hid_descriptor(self, config, max_size, vid, pid, test_mode): def _build_button_map(self, config): - """ - Returns button map readed from configuration, in format situable - for HIDDecoder.buttons.button_map field. + """Return button map readed from configuration, in format situable for HIDDecoder.buttons.button_map field. Generates default if config is not available. """ @@ -302,10 +301,7 @@ def button_to_bit(sc): def _build_axis_maping(self, axis, config, mode = AxisMode.AXIS): - """ - Converts configuration mapping for _one_ axis to value situable - for self._decoder.axes field. - """ + """Convert configuration mapping for _one_ axis to value situable for self._decoder.axes field.""" axis_config = config.get("axes", {}).get(str(int(axis))) if axis_config: try: @@ -438,10 +434,8 @@ def _build_hid_decoder(self, data, config, max_size): @staticmethod - def find_sys_devices_descriptor(vid, pid): - """ - Finds, loads and returns HID descriptor available somewhere deep in - /sys/devices structure. + def find_sys_devices_descriptor(vid: int, pid: int) -> str | None: + """Finds, loads and returns HID descriptor available somewhere deep in /sys/devices structure. Done by walking /sys/devices recursivelly, searching for file named 'report_descriptor' in subdirectory with name contining vid and pid. @@ -450,7 +444,7 @@ def find_sys_devices_descriptor(vid, pid): as some controllers are presenting descriptor that are completly broken and kernel already deals with it. """ - def recursive_search(pattern, path): + def recursive_search(pattern: str, path: str): for name in os.listdir(path): full_path = os.path.join(path, name) if name == "report_descriptor": @@ -464,7 +458,7 @@ def recursive_search(pattern, path): r = recursive_search(pattern, full_path) if r: return r - except IOError: + except OSError: pass return None diff --git a/scc/drivers/sc_by_bt.py b/scc/drivers/sc_by_bt.py index c9a9edc2..07715ea9 100644 --- a/scc/drivers/sc_by_bt.py +++ b/scc/drivers/sc_by_bt.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 -""" -SC Controller - Steam Controller Driver +"""SC Controller - Steam Controller Driver. Driver for Steam Controller over bluetooth (evdev) diff --git a/scc/gui/app.py b/scc/gui/app.py index 99588d34..3f0a551a 100644 --- a/scc/gui/app.py +++ b/scc/gui/app.py @@ -27,13 +27,18 @@ from scc.config import Config import scc.osd.menu_generators -import os, sys, platform, re, json, urllib, logging +import os +import sys +import platform +import re +import json +import urllib +import logging + log = logging.getLogger("App") class App(Gtk.Application, UserDataManager, BindingEditor): - """ - Main application / window. - """ + """Main application / window.""" HILIGHT_COLOR = "#FF00FF00" # ARGB OBSERVE_COLOR = "#FF60A0FF" # ARGB @@ -41,8 +46,8 @@ class App(Gtk.Application, UserDataManager, BindingEditor): RELEASE_URL = "https://github.com/C0rn3j/sc-controller/releases/tag/v%s" OSD_MODE_PROF_NAME = ".scc-osd.profile_editor" - def __init__(self, gladepath="/usr/share/scc", - imagepath="/usr/share/scc/images"): + def __init__(self, gladepath: str = "/usr/share/scc", + imagepath: str = "/usr/share/scc/images"): Gtk.Application.__init__(self, application_id="me.c0rn3j.scc", flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE | Gio.ApplicationFlags.NON_UNIQUE ) @@ -54,11 +59,11 @@ def __init__(self, gladepath="/usr/share/scc", # Setup DaemonManager self.dm = DaemonManager() self.dm.connect("alive", self.on_daemon_alive) - self.dm.connect('event', self.on_daemon_event_observer) + self.dm.connect("event", self.on_daemon_event_observer) self.dm.connect("controller-count-changed", self.on_daemon_ccunt_changed) self.dm.connect("dead", self.on_daemon_dead) self.dm.connect("error", self.on_daemon_error) - self.dm.connect('reconfigured', self.on_daemon_reconfigured), + self.dm.connect("reconfigured", self.on_daemon_reconfigured), self.dm.connect("version", self.on_daemon_version) # Load custom stuff load_custom_module(log, "gui") @@ -104,8 +109,8 @@ def setup_widgets(self): ps = self.add_switcher(12, 12) ps.set_allow_new(True) ps.set_profile(self.load_profile_selection()) - ps.connect('new-clicked', self.on_new_clicked) - ps.connect('save-clicked', self.on_save_clicked) + ps.connect("new-clicked", self.on_new_clicked) + ps.connect("save-clicked", self.on_save_clicked) # Drag&drop target self.builder.get_object("content").drag_dest_set(Gtk.DestDefaults.ALL, [ @@ -121,10 +126,10 @@ def setup_widgets(self): # Background self.background = ControllerImage(self) - self.background.connect('hover', self.on_background_area_hover) - self.background.connect('leave', self.on_background_area_hover, None) - self.background.connect('click', self.on_background_area_click) - self.background.connect('button-press-event', self.on_background_button_press) + self.background.connect("hover", self.on_background_area_hover) + self.background.connect("leave", self.on_background_area_hover, None) + self.background.connect("click", self.on_background_area_click) + self.background.connect("button-press-event", self.on_background_button_press) self.main_area.put(self.background, 0, 0) self.main_area.put(vbc, 0, 0) # (self.IMAGE_SIZE[0] / 2) - 90, self.IMAGE_SIZE[1] - 100) @@ -559,7 +564,7 @@ def undeletable_dialog(self, dlg, *a): def on_btNewProfile_clicked(self, *a): - """ Called when new profile name is set and OK is clicked """ + """Called when new profile name is set and OK is clicked.""" txNewProfile = self.builder.get_object("txNewProfile") rbNewProfile = self.builder.get_object("rbNewProfile") @@ -574,8 +579,8 @@ def on_btNewProfile_clicked(self, *a): def on_rbNewProfile_group_changed(self, *a): - """ - Called when user clicks 'Copy current profile' button. + """Called when user clicks 'Copy current profile' button. + If profile name was not changed by user before clicking it, it's automatically changed. """ @@ -593,10 +598,8 @@ def on_rbNewProfile_group_changed(self, *a): self.recursing = False - def on_profile_modified(self, update_ui=True): - """ - Called when selected profile is modified in memory. - """ + def on_profile_modified(self, update_ui: bool = True): + """Called when selected profile is modified in memory.""" if update_ui: self.profile_switchers[0].set_profile_modified(True, self.current.is_template) @@ -607,7 +610,7 @@ def on_profile_modified(self, update_ui=True): self.save_profile(self.current_file, self.current) - def on_profile_loaded(self, profile, giofile): + def on_profile_loaded(self, profile: Profile, giofile: Gio.File): self.current = profile self.current_file = giofile self.recursing = True @@ -620,7 +623,7 @@ def on_profile_loaded(self, profile, giofile): self.recursing = False - def on_profile_selected(self, ps, name, giofile): + def on_profile_selected(self, ps, name, giofile: Gio.File): if ps == self.profile_switchers[0]: self.load_profile(giofile) if ps.get_controller(): @@ -681,7 +684,7 @@ def on_switch_to_clicked(self, ps, *a): self.enable_test_mode() - def on_profile_saved(self, giofile, send=True): + def on_profile_saved(self, giofile: Gio.File, send: bool = True): """ Called when selected profile is saved to disk """ @@ -915,7 +918,7 @@ def on_daemon_ccunt_changed(self, daemon, count): self.controller_count = count - def new_profile(self, profile, name): + def new_profile(self, profile: Profile, name: str): filename = os.path.join(get_profiles_path(), name + ".sccprofile") self.current_file = Gio.File.new_for_path(filename) self.save_profile(self.current_file, profile) diff --git a/scc/gui/creg/dialog.py b/scc/gui/creg/dialog.py index 5f0cc0a0..4aa0b506 100644 --- a/scc/gui/creg/dialog.py +++ b/scc/gui/creg/dialog.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 -""" -SC-Controller - Controller Registration +"""SC-Controller - Controller Registration. Dialog that asks a lot of question to create configuration node in config file. Most "interesting" thing here may be that this works 100% independently from diff --git a/scc/gui/userdata_manager.py b/scc/gui/userdata_manager.py index 30e12cc9..a5ebab48 100644 --- a/scc/gui/userdata_manager.py +++ b/scc/gui/userdata_manager.py @@ -1,21 +1,26 @@ -#!/usr/bin/env python3 -""" -SC-Controller - Profile Manager +"""SC-Controller - Profile Manager. Simple class that manages stuff related to creating, loading, listing (...) of user-editable data - that are profiles, menus and controller-icons. Main App class interits from this. """ +import logging +import os + from gi.repository import Gtk, Gio, GLib -from scc.paths import get_menuicons_path, get_default_menuicons_path -from scc.paths import get_profiles_path, get_default_profiles_path -from scc.paths import get_menus_path, get_default_menus_path -from scc.profile import Profile + from scc.gui.parser import GuiActionParser +from scc.paths import ( + get_default_menuicons_path, + get_default_menus_path, + get_default_profiles_path, + get_menuicons_path, + get_menus_path, + get_profiles_path, +) +from scc.profile import Profile -import os -import logging log = logging.getLogger("UDataManager") class UserDataManager(object): @@ -23,17 +28,17 @@ class UserDataManager(object): def __init__(self): profiles_path = get_profiles_path() if not os.path.exists(profiles_path): - log.info("Creting profile directory '%s'" % (profiles_path,)) + log.info("Creating profile directory '%s'", profiles_path) os.makedirs(profiles_path) menus_path = get_menus_path() if not os.path.exists(menus_path): - log.info("Creting menu directory '%s'" % (menus_path,)) + log.info("Creating menu directory '%s'", menus_path) os.makedirs(menus_path) - def load_profile(self, giofile): - """ - Loads profile from 'giofile' into 'profile' object + def load_profile(self, giofile: Gio.File): + """Load profile from 'giofile' into 'profile' object. + Calls on_profiles_loaded when done """ # This may get asynchronous later, but that load runs under 1ms... @@ -42,9 +47,9 @@ def load_profile(self, giofile): self.on_profile_loaded(profile, giofile) - def save_profile(self, giofile, profile): - """ - Saves profile from 'profile' object into 'giofile'. + def save_profile(self, giofile: Gio.File, profile: Profile): + """Save profile from 'profile' object into 'giofile'. + Calls on_profile_saved when done """ # 1st check, if file is not in /usr/share. @@ -59,7 +64,7 @@ def save_profile(self, giofile, profile): self.on_profile_saved(giofile) - def _save_profile_local(self, giofile, profile): + def _save_profile_local(self, giofile: Gio.File, profile: Profile): filename = os.path.split(giofile.get_path())[-1] localpath = os.path.join(get_profiles_path(), filename) giofile = Gio.File.new_for_path(localpath) @@ -82,9 +87,7 @@ def load_menu_icons(self, category=None): def load_user_data(self, paths, pattern, category, callback): - """ - Loads data such as of profiles. Uses GLib to do it on background. - """ + """Load data such as of profiles. Uses GLib to do it in the background.""" if category: paths = [ os.path.join(p, category) for p in paths ] @@ -98,13 +101,13 @@ def load_user_data(self, paths, pattern, category, callback): pattern, Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, 1, None, self._on_user_data_loaded, - data, i, callback + data, i, callback, ) def _on_user_data_loaded(self, pdir, res, data, i, callback): - """ - Called when enumerate_children_async gets lists of files. + """Called when enumerate_children_async gets lists of files. + Usually called twice for default (system) and user directory. """ try: @@ -137,10 +140,7 @@ def _on_user_data_loaded(self, pdir, res, data, i, callback): def _sync_load(self, pdirs): - """ - Synchronous (= UI lagging) fallback method for those (hopefully) rare - cases when enumerate_children_finish returns nonsense. - """ + """Synchronous (= UI lagging) fallback method for those (hopefully) rare cases when enumerate_children_finish returns nonsense.""" files = {} for pdir in pdirs: for name in os.listdir(pdir.get_path()): @@ -160,9 +160,9 @@ def on_menuicons_loaded(self, icons): # Overriden by subclass pass - def on_profile_saved(self, giofile): # Overriden in App + def on_profile_saved(self, giofile: Gio.File): # Overriden in App pass - def on_profile_loaded(self, profile, giofile): # Overriden in App + def on_profile_loaded(self, profile: Profile, giofile: Gio.File): # Overriden in App pass diff --git a/scc/lib/daemon.py b/scc/lib/daemon.py index 77b2783e..d51a9135 100644 --- a/scc/lib/daemon.py +++ b/scc/lib/daemon.py @@ -90,7 +90,7 @@ def start(self): cmdline = open("/proc/%s/cmdline" % (pid,), "r").read().replace("\x00", " ").strip() if sys.argv[0] in cmdline: raise Exception("already running") - except IOError: + except OSError: # No such process pass except: diff --git a/scc/lib/eudevmonitor.py b/scc/lib/eudevmonitor.py index 98734950..5f0cec0e 100644 --- a/scc/lib/eudevmonitor.py +++ b/scc/lib/eudevmonitor.py @@ -225,7 +225,7 @@ class DeviceEvent(NamedTuple): subsystem: str devtype: str syspath: str - devnum: str + devnum: int class Monitor: """Monitor object that receives device events. @@ -307,11 +307,11 @@ def set_receive_buffer_size(self, size): return self - fileno = get_fd # python stuff likes this name better - start = enable_receiving # I like this name better + fileno = get_fd # python stuff likes this name better + start = enable_receiving # I like this name better - def receive_device(self) -> DeviceEvent: + def receive_device(self) -> DeviceEvent | None: if not self._monitor_started: self.enable_receiving() @@ -348,7 +348,11 @@ def receive_device(self) -> DeviceEvent: print(i) m = udev.monitor().match_subsystem("hidraw").start() + if m is None: + msg = "Expected to receive a Monitor device, not None." + raise RuntimeError(msg) + while True: dev = m.receive_device() - if dev: - print(os.major(dev.devnum), os.minor(dev.devnum), dev) + if dev is not None: + print(f"{os.major(dev.devnum)} {os.minor(dev.devnum)} {dev}") diff --git a/scc/lib/hidparse.py b/scc/lib/hidparse.py index 56999efc..73919f76 100644 --- a/scc/lib/hidparse.py +++ b/scc/lib/hidparse.py @@ -2,9 +2,9 @@ Based on - Pythonic binding for linux's hidraw ioctls - (https://github.com/vpelletier/python-hidraw) + (https://github.com/vpelletier/python-hidraw) - Winfred Lu's rd-parse.py - (http://winfred-lu.blogspot.sk/2014/02/usb-hid-report-descriptor-parser-in.html) + (http://winfred-lu.blogspot.sk/2014/02/usb-hid-report-descriptor-parser-in.html) Licensed under GPL 2.0 """ @@ -81,7 +81,7 @@ def __init__(self, value): self.value = value - def __repr__(self): + def __repr__(self) -> str: return "" % (self.value,) __str__ = __repr__ @@ -110,23 +110,18 @@ def enum_or_reserved(enum, value): def _ioctl(devfile, func, arg, mutate_flag=False): result = fcntl.ioctl(devfile, func, arg, mutate_flag) if result < 0: - raise IOError(result) + raise OSError(result) def get_device_info(devfile): - """ - Returns tuple of (bustype, vendor_id, product_id), where bustype is - instance of BusType enum. - """ + """Return tuple of (bustype, vendor_id, product_id), where bustype is instance of BusType enum.""" devinfo = _hidraw_devinfo() _ioctl(devfile, _HIDIOCGRAWINFO, devinfo, True) return (BusType(devinfo.bustype), devinfo.vendor, devinfo.product) def get_raw_report_descriptor(devfile): - """ - Returns raw HID report descriptor as list of bytes. - """ + """Return raw HID report descriptor as list of bytes.""" descriptor = _hidraw_report_descriptor() size = ctypes.c_uint() _ioctl(devfile, _HIDIOCGRDESCSIZE, size, True) diff --git a/scc/lib/hidraw.py b/scc/lib/hidraw.py index 7f2bb460..8f4d3b56 100644 --- a/scc/lib/hidraw.py +++ b/scc/lib/hidraw.py @@ -1,6 +1,7 @@ import ctypes -import collections import fcntl +from typing import NamedTuple + import ioctl_opt # input.h @@ -42,23 +43,32 @@ class _hidraw_devinfo(ctypes.Structure): HIDRAW_MAX_DEVICES = 64 HIDRAW_BUFFER_SIZE = 64 -DevInfo = collections.namedtuple('DevInfo', ['bustype', 'vendor', 'product']) +class DevInfo(NamedTuple): + """Device Info. + + - bustype: one of BUS_USB, BUS_HIL, BUS_BLUETOOTH or BUS_VIRTUAL + - vendor: device's vendor number + - product: device's product number +""" + + bustype: int + vendor: int + product: int class HIDRaw(object): - """ - Provides methods to access hidraw device's ioctls. - """ + """Provide methods to access hidraw device's ioctls.""" + def __init__(self, device): - """ - device (file, fileno) + """device (file, fileno). + A file object or a fileno of an open hidraw device node. """ self._device = device - def _ioctl(self, func, arg, mutate_flag=False): + def _ioctl(self, func, arg, mutate_flag: bool = False): result = fcntl.ioctl(self._device, func, arg, mutate_flag) if result < 0: - raise IOError(result) + raise OSError(result) def read(self, size): return self._device.read(size) @@ -66,42 +76,33 @@ def read(self, size): def write(self, buf): return self._device.write(buf) - def getRawReportDescriptor(self): - """ - Return a binary string containing the raw HID report descriptor. - """ + def getRawReportDescriptor(self) -> str: + """Return a binary string containing the raw HID report descriptor.""" descriptor = _hidraw_report_descriptor() size = ctypes.c_uint() self._ioctl(_HIDIOCGRDESCSIZE, size, True) descriptor.size = size self._ioctl(_HIDIOCGRDESC, descriptor, True) - return ''.join(chr(x) for x in descriptor.value[:size.value]) + return "".join(chr(x) for x in descriptor.value[:size.value]) # TODO: decode descriptor into a python object #def getReportDescriptor(self): - def getInfo(self): - """ - Returns a DevInfo instance, a named tuple with the following items: - - bustype: one of BUS_USB, BUS_HIL, BUS_BLUETOOTH or BUS_VIRTUAL - - vendor: device's vendor number - - product: device's product number - """ + def getInfo(self) -> DevInfo: + """Return a DevInfo instance.""" devinfo = _hidraw_devinfo() self._ioctl(_HIDIOCGRAWINFO, devinfo, True) return DevInfo(devinfo.bustype, devinfo.vendor, devinfo.product) - def getName(self, length=512): - """ - Returns device name as an unicode object. - """ + def getName(self, length:int = 512) -> str: + """Return device name as an unicode object.""" name = ctypes.create_string_buffer(length) self._ioctl(_HIDIOCGRAWNAME(length), name, True) - return name.value.decode('UTF-8') + return name.value.decode("UTF-8") + + def getPhysicalAddress(self, length:int = 512) -> str: + """Return device's physical address as a string. - def getPhysicalAddress(self, length=512): - """ - Returns device physical address as a string. See hidraw documentation for value signification, as it depends on device's bus type. """ @@ -109,10 +110,8 @@ def getPhysicalAddress(self, length=512): self._ioctl(_HIDIOCGRAWPHYS(length), name, True) return name.value - def sendFeatureReport(self, report, report_num=0): - """ - Send a feature report. - """ + def sendFeatureReport(self, report, report_num:int = 0) -> None: + """Send a feature report.""" length = len(report) + 1 buf = bytearray(length) buf[0] = report_num @@ -123,9 +122,9 @@ def sendFeatureReport(self, report, report_num=0): True, ) - def getFeatureReport(self, report_num=0, length=63): - """ - Receive a feature report. + def getFeatureReport(self, report_num:int = 0, length:int = 63) -> bytearray: + """Receive a feature report. + Blocks, unless you configured provided file (descriptor) to be non-blocking. """ diff --git a/scc/osd/__init__.py b/scc/osd/__init__.py index b50b5aa1..ee22b499 100644 --- a/scc/osd/__init__.py +++ b/scc/osd/__init__.py @@ -2,19 +2,22 @@ Common methods for OSD-related stuff """ -from scc.tools import set_logging_level +import argparse +import logging +import os +import traceback -from gi.repository import Gtk, Gdk, GLib, GObject, GdkX11 -from scc.constants import STICK_PAD_MIN, STICK_PAD_MAX +import cairo +from gi.repository import Gdk, GLib, GObject, Gtk + +from scc.config import Config +from scc.constants import STICK_PAD_MAX, STICK_PAD_MIN +from scc.controller import Controller +from scc.gui.daemon_manager import DaemonManager from scc.osd.timermanager import TimerManager from scc.paths import get_share_path -from scc.config import Config +from scc.tools import set_logging_level -import cairo -import os -import argparse -import traceback -import logging log = logging.getLogger("osd") @@ -34,9 +37,9 @@ class OSDWindow(Gtk.Window): """ EPILOG = "" - css_provider = None # Used by staticmethods + css_provider = None # Used by staticmethods - def __init__(self, wmclass, layer = None): + def __init__(self, wmclass, layer = None) -> None: Gtk.Window.__init__(self) OSDWindow._apply_css(Config()) @@ -76,7 +79,7 @@ def __init__(self, wmclass, layer = None): @staticmethod - def _apply_css(config): + def _apply_css(config: dict) -> None: if OSDWindow.css_provider: Gtk.StyleContext.remove_provider_for_screen( Gdk.Screen.get_default(), OSDWindow.css_provider) @@ -113,7 +116,7 @@ def _apply_css(config): Gtk.STYLE_PROVIDER_PRIORITY_USER) - def _add_arguments(self): + def _add_arguments(self) -> None: """Should be overriden AND called by child class.""" self.argparser.add_argument('-x', type=int, metavar="pixels", default=20, help="""horizontal position in pixels, from left side of screen. @@ -127,11 +130,8 @@ def _add_arguments(self): help="""display debug messages""") - def choose_controller(self, daemonmanager): - """ - Returns first available controller, or, if --controller argument - was specified, controller with matching ID. - """ + def choose_controller(self, daemonmanager: DaemonManager) -> Controller: + """Return first available controller, or, if --controller argument was specified, controller with a matching ID.""" if self.args.controller: self._controller = self.daemon.get_controller(self.args.controller) elif self.daemon.has_controller(): @@ -139,13 +139,13 @@ def choose_controller(self, daemonmanager): return self._controller - def get_controller(self): - """ Returns controller chosen by choose_controller """ + def get_controller(self) -> Controller: + """Return controller chosen by choose_controller.""" return self._controller - def parse_argumets(self, argv): - """ Returns True on success """ + def parse_argumets(self, argv) -> bool: + """Returns True on success.""" try: self.args = self.argparser.parse_args(argv[1:]) except SystemExit: @@ -173,10 +173,7 @@ def make_window_clicktrough(self): def get_active_screen_geometry(self): - """ - Returns geometry of active screen or None if active screen - cannot be determined. - """ + """Return geometry of active screen or None if active screen cannot be determined.""" screen = self.get_window().get_screen() active_window = screen.get_active_window() if active_window: @@ -187,7 +184,7 @@ def get_active_screen_geometry(self): def compute_position(self): - """ Adjusts position for currently active screen (display) """ + """Adjust position for currently active screen (display).""" x, y = self.position width, height = self.get_window_size() geometry = self.get_active_screen_geometry() @@ -208,7 +205,7 @@ def get_window_size(self): return self.get_window().get_width(), self.get_window().get_height() - def show(self): + def show(self) -> None: self.get_children()[0].show_all() self.realize() self.get_window().set_override_redirect(True) @@ -238,32 +235,32 @@ def show(self): self.make_window_clicktrough() - def on_controller_lost(self, *a): + def on_controller_lost(self, *a) -> None: log.error("Controller lost") self.quit(2) - def on_daemon_died(self, *a): + def on_daemon_died(self, *a) -> None: log.error("Daemon died") self.quit(2) - def on_failed_to_lock(self, error): + def on_failed_to_lock(self, error: str) -> None: log.error("Failed to lock input: %s", error) self.quit(3) - def get_exit_code(self): + def get_exit_code(self) -> int: return self.exit_code - def run(self): + def run(self) -> None: self.mainloop = GLib.MainLoop() self.show() self.mainloop.run() - def quit(self, code=-1): + def quit(self, code: int = -1) -> None: self.exit_code = code if self.mainloop: self.mainloop.quit() @@ -272,8 +269,8 @@ def quit(self, code=-1): class OSDCssMagic(dict): - """ - Basically, I reinvented templating. + """Basically, I reinvented templating. + This is passed to string.format, allowing to use some simple expressions in addition to normal %(placeholder)s. @@ -283,11 +280,11 @@ class OSDCssMagic(dict): %(background-10)s - color, 10 values darker """ - def __init__(self, dict_to_wrap): + def __init__(self, dict_to_wrap) -> None: self._dict = dict_to_wrap - def __getitem__(self, a): + def __getitem__(self, a) -> str: if "+" in a: key, number = a.rsplit("+", 1) rgba = parse_rgba(self[key]) @@ -314,25 +311,24 @@ def __getitem__(self, a): class StickController(GObject.GObject, TimerManager): - """ - Simple utility class that gets fed by with position and emits - 'direction' signal that can be used as input for menu navigation. + """Simple utility class that gets fed by with position and emits 'direction' signal that can be used as input for menu navigation. Signals: - direction(horisontal, vertical) + direction(horisontal, vertical) - Both values are one of -1, 0, 1 for left/none/right. + Both values are one of -1, 0, 1 for left/none/right. """ + __gsignals__ = { - "direction" : (GObject.SignalFlags.RUN_FIRST, None, (int, int)), + "direction": (GObject.SignalFlags.RUN_FIRST, None, (int, int)), } REPEAT_DELAY = 0.2 DIRECTION_TO_XY = { - 0 : (0, 0), - 4 : (1, 0), - 6 : (-1, 0), - 2 : (0, 1), - 8 : (0, -1), + 0: (0, 0), + 4: (1, 0), + 6: (-1, 0), + 2: (0, 1), + 8: (0, -1), } def __init__(self): @@ -367,9 +363,9 @@ def set_stick(self, *data): self._move() -def parse_rgba(col): - """ - Parses color specified by #RRGGBBAA string. +def parse_rgba(col: str) -> Gdk.RGBA: + """Parse color specified by a #RRGGBBAA string. + '#' and 'AA' is optional. """ # Because GTK can parse everything but theese :( diff --git a/scc/osd/area.py b/scc/osd/area.py index f3abbe60..630fe458 100644 --- a/scc/osd/area.py +++ b/scc/osd/area.py @@ -1,36 +1,34 @@ -#!/usr/bin/env python3 -""" -SC-Controller - OSD Menu +"""SC-Controller - OSD Menu. Displays border around area. """ -from scc.tools import _, set_logging_level +from __future__ import annotations + +import logging from gi.repository import Gtk, GLib, GdkX11 -from scc.constants import LEFT, RIGHT, STICK, STICK_PAD_MIN, STICK_PAD_MAX -from scc.tools import point_in_gtkrect -from scc.paths import get_share_path -from scc.lib import xwrappers as X -from scc.menu_data import MenuData + +from scc.constants import LEFT, RIGHT, STICK, STICK_PAD_MAX, STICK_PAD_MIN from scc.gui.daemon_manager import DaemonManager -from scc.osd.timermanager import TimerManager +from scc.lib import xwrappers as X from scc.osd import OSDWindow +from scc.osd.timermanager import TimerManager +from scc.tools import _ -import os, sys, json, logging log = logging.getLogger("osd.area") class Area(OSDWindow, TimerManager): BORDER_WIDTH = 2 - def __init__(self): + def __init__(self) -> None: OSDWindow.__init__(self, "osd-area") TimerManager.__init__(self) self.size = (100, 100) self.add(Gtk.Fixed()) - def _add_arguments(self): + def _add_arguments(self) -> None: OSDWindow._add_arguments(self) self.argparser.add_argument('--width', type=int, metavar="pixels", default=20, help="""area width in pixels""") @@ -38,7 +36,7 @@ def _add_arguments(self): help="""area height in pixels""") - def parse_argumets(self, argv): + def parse_argumets(self, argv) -> bool: if not OSDWindow.parse_argumets(self, argv): return False self.position = (self.position[0] - self.BORDER_WIDTH, @@ -48,21 +46,21 @@ def parse_argumets(self, argv): return True - def compute_position(self): + def compute_position(self) -> tuple[int, int]: # Overrides compute_position as Area is requested with exact position # on X screen. return self.position - def show(self): + def show(self) -> None: OSDWindow.show(self) self.realize() self.resize(*self.size) self.make_hole(self.BORDER_WIDTH) - def update(self, x, y, width, height): - """ Updates area size and position """ + def update(self, x: int, y: int, width: int, height: int) -> None: + """Update area size and position.""" self.position = x, y self.size = max(1, width), max(1, height) # Size can't be <1 or GTK will crash self.move(*self.position) @@ -70,9 +68,9 @@ def update(self, x, y, width, height): self.make_hole(self.BORDER_WIDTH) - def make_hole(self, border_width): - """ - Uses shape extension to create hole in window... + def make_hole(self, border_width: int) -> None: + """Use shape extension to create a hole in the window... + Area needs only border, rest should be transparent. """ width, height = self.size diff --git a/scc/osd/menu.py b/scc/osd/menu.py index 8ecabcba..b553a2e2 100644 --- a/scc/osd/menu.py +++ b/scc/osd/menu.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 -""" -SC-Controller - OSD Menu +"""SC-Controller - OSD Menu. Display menu that user can navigate through and prints chosen item id to stdout """ @@ -176,7 +174,7 @@ def parse_menu(self): try: self._menuid = self.args.items[0] self.items = MenuData.from_profile(self.args.from_profile, self._menuid) - except IOError: + except OSError: print('%s: error: profile file not found' % (sys.argv[0]), file=sys.stderr) return False except ValueError: diff --git a/scc/profile.py b/scc/profile.py index ac02a0df..a87d6b00 100644 --- a/scc/profile.py +++ b/scc/profile.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 -""" -SC-Controller - Profile +"""SC-Controller - Profile. Handles mapping profile stored in json file """ diff --git a/scc/scripts.py b/scc/scripts.py index 4f4a166a..7f1b5bd5 100644 --- a/scc/scripts.py +++ b/scc/scripts.py @@ -1,6 +1,4 @@ -#!/usr/bin/env python3 -""" -SC-Controller - Scripts +"""SC-Controller - Scripts. Contains code for most of what can be done using 'scc' script. Created so scc-* stuff doesn't polute /usr/bin. @@ -242,7 +240,7 @@ def cmd_lock_inputs(argv0, argv, lock="Lock: "): try: print(" ".join(data[2:]), file=sys.stdout) sys.stdout.flush() - except IOError: + except OSError: # Output closed, bail out return 0 finally: