From 36c74263a217a699b8d344bedf6b6ae07964d3a5 Mon Sep 17 00:00:00 2001 From: mar77i Date: Tue, 17 Dec 2024 16:34:50 +0100 Subject: [PATCH] add all the functionality I wanted to add --- bluetooth.py | 53 +++++---------- ease.py | 169 --------------------------------------------- ui.py | 177 ++++++++++++++++++++++++++++++++++++++++++++++++ vectors.py | 132 +++++++++++++++++++----------------- xinput.py | 38 ++++++++++- xrandr.py | 65 ++++++++++++++---- zenbook_conf.py | 85 ++++++++++++++++++----- 7 files changed, 417 insertions(+), 302 deletions(-) delete mode 100755 ease.py create mode 100755 ui.py diff --git a/bluetooth.py b/bluetooth.py index b9b2185..214fdde 100644 --- a/bluetooth.py +++ b/bluetooth.py @@ -1,42 +1,25 @@ -from time import sleep -from traceback import print_exception - -import gi -import gi.repository.GLib -import pydbus +import os +from subprocess import check_output, run class BluetoothConf: - ADAPTER = "org.bluez.Adapter1" - RETRIES = 20 - RETRY_SLEEP = 5 + DEVICE_TYPE = "bluetooth" def __init__(self): - self.bus = pydbus.SystemBus().get("org.bluez", "/org/bluez/hci0") - self.conf = self.bus.Get(self.ADAPTER, "Powered") + self.conf = self.get_config() + + def get_config(self): + output = check_output( + ["rfkill", "-rnoSOFT,HARD", "list", self.DEVICE_TYPE], text=True + ) + state = None + for line in output.strip().split(os.linesep): + new_state = "blocked" not in line.split() + if state is not None and new_state != state: + return None + state = new_state + return state def update(self, value): - assert isinstance(value, bool) - if value == self.conf: - return - for retry in range(self.RETRIES + 1): - try: - self.bus.Set(self.ADAPTER, "Powered", pydbus.Variant("b", value)) - except gi.repository.GLib.GError as e: - if retry >= self.RETRIES: - raise - print_exception(e) - print( - f"Sleeping {self.RETRY_SLEEP} seconds to try again " - f"({retry + 1} / {self.RETRIES})..." - ) - sleep(self.RETRY_SLEEP) - else: - break - # dbus-send --system - # --dest=org.bluez --print-reply /org/bluez/hci0 - # org.freedesktop.DBus.Properties.Set - # string:org.bluez.Adapter1 - # string:Powered - # variant:boolean:false - self.conf = value + run(["rfkill", f"{'un' if value else ''}block", self.DEVICE_TYPE]) + self.conf = self.get_config() diff --git a/ease.py b/ease.py deleted file mode 100755 index a18527a..0000000 --- a/ease.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python3 - -from math import asin, atan2, ceil, cos, floor, pi, sin - -import pygame - -tau = 2 * pi -FPS = 60 - - -def make_line_shape(p1, p2, n, shape_r): - angle = atan2(p1[1] - p2[1], p1[0] - p2[0]) - if angle < 0: - angle += tau - # 0 <= angle < tau - initial_corner = ceil((angle + tau * 3 / 4) * n / tau) % n - opposing_corner = (floor((angle + tau / 4) * n / tau) - initial_corner) % n - shape = [ - (cos(a) * shape_r, sin(a) * shape_r) - for a in ((i + initial_corner) * tau / n for i in range(n)) - ] - left = (cos(angle + tau * 3 / 4) * shape_r, sin(angle + tau * 3 / 4) * shape_r) - right = (cos(angle + tau / 4) * shape_r, sin(angle + tau / 4) * shape_r) - yield (p2[0] + left[0], p2[1] + left[1]) - yield (p1[0] + left[0], p1[1] + left[1]) - p = p1 - for i in range(n): - s = shape[i] - yield (p[0] + s[0], p[1] + s[1]) - if i == opposing_corner and p == p1 and p1 != p2: - yield (p[0] + right[0], p[1] + right[1]) - p = p2 - yield (p[0] + right[0], p[1] + right[1]) - - -class EaseGraph: - CYCLE = {"length": 4, "pause": 1} - STEPS = 256 - WIDTH = 2 - LINES_COLOR = "blue" - GRAPH_COLOR = "lightblue" - - def __init__(self, rect, line, func): - self.rect = rect - self.line = line - self.current_frame = 0 - self.current_setting = 0 - self.func = func - - def draw(self, surf): - pygame.draw.rect(surf, self.LINES_COLOR, self.rect, 1) - pygame.draw.line(surf, self.LINES_COLOR, *self.line) - pos = ( - self.line[0][0], - self.line[0][1] + ( - self.line[1][1] - self.line[0][1] - ) * (1 - self.func(self.current_setting)), - ) - pygame.draw.circle(surf, self.GRAPH_COLOR, pos, 32) - prev = self.rect.bottomleft - for i in range(1, self.STEPS + 1): - i_fract = i / self.STEPS - pos = ( - self.rect.left + (i_fract * self.rect.width), - self.rect.bottom - self.func(i_fract) * self.rect.height, - ) - pygame.draw.polygon( - surf, self.GRAPH_COLOR, list(make_line_shape(prev, pos, 8, self.WIDTH)) - ) - prev = pos - - def update(self): - self.current_frame += 1 - full_cycle = sum(self.CYCLE.values()) - elapsed = self.current_frame / FPS % (full_cycle * 2) - if elapsed > full_cycle: - elapsed = 2 * full_cycle - elapsed - start = self.CYCLE["pause"] / 2 - end = start + self.CYCLE["length"] - if elapsed < start: - current_setting = 0 - elif elapsed > end: - current_setting = 1 - else: - current_setting = (elapsed - start) / self.CYCLE["length"] - if current_setting == self.current_setting: - return False - self.current_setting = current_setting - return True - - -def ease_in_out_elastic(mag): - p = 1 - mag - s = p / tau * asin(1) - def inner(x): - if x == 0: - return 0 - elif x == 1: - return 1 - elif x < 0 or x > 1: - raise ValueError(f"x must be between 0 and 1: got {x}") - st = x * 2 - st1 = st - 1 - sgn = (st >= 1) * 2 - 1 - return 2 ** (-sgn * 10 * st1 - 1) * sin((st1 - s) * tau / p) * sgn + (sgn > 0) - return inner - - -def main(): - pygame.init() - surf = pygame.display.set_mode((800, 600)) - clock = pygame.time.Clock() - running = True - dirty = False - #lerp = lambda start, end, x: start + (end - start) * x - ease_in = lambda x: x * x - flip = lambda x: 1 - x - mirror = lambda f, x: (f(2 * x) if x < 0.5 else flip(f(2 * x - 2)) + 1) / 2 - #ease_out = lambda x: flip(ease_in(flip(x))) - #ease_in_out = lambda x: lerp(ease_in(x), ease_out(x), x) - #ease_in_out = lambda x: ease_in(x) if x < .5 else ease_out(x) - ease_in_out = lambda x: mirror(ease_in, x) - #one_minus_cos = lambda x: .5 - cos(tau * x / 2) / 2 - eg = EaseGraph( - pygame.Rect((64, 64), (472, 472)), - ((600, 64), (600, 536)), - ease_in_out_elastic(5 ** .5 / 2 - 0.5) - ) - frame = 0 - while True: - for ev in pygame.event.get(): - if ev.type == pygame.QUIT: - running = False - break - elif ev.type == pygame.WINDOWEXPOSED: - dirty = True - elif ev.type == pygame.KEYDOWN: - if ev.key == pygame.K_ESCAPE: - running = False - break - if not running: - break - dirty |= eg.update() - dirty = True - if dirty: - surf.fill("black") - angle = frame / (tau * 30) - a = (300 + cos(angle) * 200, 300 + sin(angle) * 200) - b = (300 + cos(angle + pi) * 200, 300 + sin(angle + pi) * 200) - width = 100 - n = 5 - pygame.draw.polygon( - surf, "purple", list(make_line_shape(a, b, n, width / 2)), 1 - ) - shape = [ - (cos(a) * width / 2, sin(a) * width / 2) - for a in (i * tau / n for i in range(n)) - ] - pygame.draw.polygon(surf, "yellow", [(s[0] + a[0], s[1] + a[1]) for s in shape], 1) - pygame.draw.polygon(surf, "yellow", [(s[0] + b[0], s[1] + b[1]) for s in shape], 1) - frame += 1 - eg.draw(surf) - pygame.display.update() - dirty = False - clock.tick(FPS) - - -if __name__ == "__main__": - main() diff --git a/ui.py b/ui.py new file mode 100755 index 0000000..c70307c --- /dev/null +++ b/ui.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 + +from math import asin, nan, pi, sin +from time import time +from colorsys import hsv_to_rgb + +import pygame + +from bluetooth import BluetoothConf + +tau = 2 * pi + + +def ease_in_out_elastic(mag): + p = 1 - mag + s = p / tau * asin(1) + def inner(x): + if x == 0: + return 0 + elif x == 1: + return 1 + elif x < 0 or x > 1: + raise ValueError(f"x must be between 0 and 1: got {x}") + st = x * 2 + st1 = st - 1 + sgn = (st >= 1) * 2 - 1 + return 2 ** (-sgn * 10 * st1 - 1) * sin((st1 - s) * tau / p) * sgn + (sgn > 0) + return inner + + +class Switch: + MOVE_FOR_SEC = 1 + EASE = staticmethod(ease_in_out_elastic((5 ** .5 - 1) / 2)) + + def __init__(self, rect, update_callback, setting=False): + self.rect = rect + self.update_callback = update_callback + if setting is not None and not isinstance(setting, bool): + setting = bool(setting) + self.setting = setting + self.moving_since = nan + self.flip_again = False + + def handle_mousebuttondown(self, ev): + if ev.button == 1 and self.rect.collidepoint(ev.pos): + if self.moving_since is not nan: + self.flip_again = True + else: + offset = self.MOVE_FOR_SEC / 2 if self.setting is None else 0 + self.setting = bool(self.setting) ^ True + self.moving_since = time() - offset + + def update(self, new_value=None): + if new_value is not None and new_value != self.setting: + self.setting = new_value + self.moving_since = time() + return self.moving_since is not nan + + def draw(self, surf): + pygame.draw.rect(surf, "gray", self.rect, 8) + t = time() + if t > self.moving_since + self.MOVE_FOR_SEC: + self.update_callback(self.setting) + if self.flip_again: + self.setting = bool(self.setting) ^ True + self.moving_since = t + self.flip_again = False + else: + self.moving_since = nan + if self.moving_since is nan: + if self.setting is None: + current = 0.5 + else: + current = min(max(self.setting, 0), 1) + else: + current = (t - self.moving_since) / self.MOVE_FOR_SEC + if not self.setting: + current = 1 - current + eased_current = self.EASE(current) + base_radius = min(self.rect.height, self.rect.width / 4) + base_left = self.rect.left + base_radius + movement_width = self.rect.width - 2 * base_radius + normalized_current = min(max(current, 0), 1) + if current < 0.5: + args = (0, 1 - 2 * normalized_current, 1 - normalized_current) + else: + normalized_current -= 0.5 + args = (1 / 3, 2 * normalized_current, 0.5 + normalized_current * 0.5) + rgb = hsv_to_rgb(*args) + pygame.draw.circle( + surf, + pygame.Color(*(int(x * 255) for x in rgb)), + (base_left + eased_current * movement_width, self.rect.top + base_radius), + base_radius + ) + + +class Button: + def __init__(self, rect, font, label, callback): + self.rect = rect + self.font = font + self.label = label + self.pushed = False + self.callback = callback + + def draw(self, surf): + if not self.pushed: + pygame.draw.rect(surf, "gray", self.rect, 8) + fs = self.font.render(self.label, True, "gray") + else: + pygame.draw.rect(surf, "darkgray", self.rect) + pygame.draw.rect(surf, "lightgray", self.rect, 4) + fs = self.font.render(self.label, True, "black") + fs_size = fs.get_size() + center = self.rect.center + surf.blit(fs, (center[0] - fs_size[0] // 2, center[1] - fs_size[1] // 2)) + + def handle_event(self, ev): + if ev.type == pygame.MOUSEBUTTONDOWN: + if ev.button == 1 and self.rect.collidepoint(ev.pos): + self.pushed = True + return True + elif ev.type == pygame.MOUSEBUTTONUP: + if ev.button == 1 and self.pushed and self.rect.collidepoint(ev.pos): + self.pushed = False + self.callback() + return True + elif ev.type == pygame.MOUSEMOTION: + if ev.buttons[0] and self.pushed and not self.rect.collidepoint(ev.pos): + self.pushed = False + return True + return False + + +def main(): + pygame.init() + surf = pygame.display.set_mode((800, 600)) + clock = pygame.time.Clock() + font = pygame.font.Font(None, size=64) + bc = BluetoothConf() + switch = Switch(pygame.Rect((64, 64), (256, 128)), bc.update, bc.conf) + + def cb(): + switch.update(not bc.conf) + bc.update(not bc.conf) + + button = Button(pygame.Rect((64, 256), (384, 128)), font, "Apply", cb) + running = True + dirty = False + while True: + for ev in pygame.event.get(): + if ev.type == pygame.QUIT: + running = False + break + elif ev.type == pygame.WINDOWEXPOSED: + dirty = True + elif ev.type == pygame.KEYDOWN: + if ev.key == pygame.K_ESCAPE: + running = False + break + elif ev.type == pygame.MOUSEBUTTONDOWN: + switch.handle_mousebuttondown(ev) + dirty |= button.handle_event(ev) + if not running: + break + dirty |= switch.update() + if dirty: + surf.fill("black") + switch.draw(surf) + button.draw(surf) + pygame.display.update() + dirty = False + clock.tick(60) + + +if __name__ == "__main__": + main() diff --git a/vectors.py b/vectors.py index 20880fd..679a7c2 100755 --- a/vectors.py +++ b/vectors.py @@ -1,8 +1,7 @@ #!/usr/bin/env python3 -from collections.abc import Sequence from itertools import chain -from math import atan2, cos, pi, sin +from math import atan2, ceil, cos, floor, pi, sin import pygame @@ -76,94 +75,99 @@ class StrokeLine(Polygon): if angle < 0: angle += tau # 0 <= angle < tau - # corner below 270° - initial_corner = int((angle + pi * 3 / 2) * n / tau + .5) % n - # corner below 90° - opposing_corner = int((angle + pi / 2) * n / tau + .5) % n + initial_corner = ceil((angle + tau * 3 / 4) * n / tau) % n + opposing_corner = (floor((angle + tau / 4) * n / tau) - initial_corner) % n shape = [ - (cos(i * tau / n) * shape_r, sin(i * tau / n) * shape_r) - for i in range(n) + (cos(a) * shape_r, sin(a) * shape_r) + for a in ((i + initial_corner) * tau / n for i in range(n)) ] + left = (cos(angle + tau * 3 / 4) * shape_r, sin(angle + tau * 3 / 4) * shape_r) + right = (cos(angle + tau / 4) * shape_r, sin(angle + tau / 4) * shape_r) + yield (p2[0] + left[0], p2[1] + left[1]) + yield (p1[0] + left[0], p1[1] + left[1]) p = p1 - i = initial_corner - while i <= n + initial_corner: - mod_i = i % n - s = shape[mod_i] + for i in range(n): + s = shape[i] yield (p[0] + s[0], p[1] + s[1]) - if mod_i == opposing_corner and p == p1 and p1 != p2: + if i == opposing_corner and p == p1 and p1 != p2: + yield (p[0] + right[0], p[1] + right[1]) p = p2 - else: - i += 1 + yield (p[0] + right[0], p[1] + right[1]) class Shapes(Shape): def __init__(self, shapes): - self.shapes = shapes + self.shapes = list(shapes) def draw(self, surf, pos, unit, color): for shape in self.shapes: shape.draw(surf, pos, unit, color) -class StrokeCircle(Shapes): +class StrokePath(Shapes): + def __init__(self, points, closed, width): + self._points = list(points) + self.closed = bool(closed) + self.width = width + super().__init__(self.get_shapes()) + + def get_shapes(self): + if self.closed: + yield StrokeLine(self._points[-1], self._points[0], self.width) + iter_points = iter(self._points) + old = next(iter_points) + for new in iter_points: + yield StrokeLine(old, new, self.width) + old = new + + @property + def points(self): + return self._points[:] + + @points.setter + def points(self, points): + self._points.clear() + self._points.extend(points) + self._points = points + self.shapes.clear() + self.shapes.extend(self.get_shapes()) + + +class StrokeCircle(StrokePath): def __init__(self, center, radius, width): - super().__init__([ - StrokeLine( - ( - center[0] + cos(a) * radius, - center[1] + sin(a) * radius, - ), - ( - center[0] + cos(b) * radius, - center[1] + sin(b) * radius, - ), - width, - ) - for a, b in self.get_angle_segments() - ]) - - @staticmethod - def get_angle_segments(): num_vertices = StrokeLine.num_vertices - a = 0 - for i in range(1, num_vertices + 1): - b = tau * i / num_vertices - yield a, b - a = b + super().__init__( + [ + (center[0] + cos(a) * radius, center[1] + sin(a) * radius) + for a in (tau * i / num_vertices for i in range(num_vertices)) + ], + True, + width, + ) -class StrokeCircleSegment(Shapes): +class StrokeCircleSegment(StrokePath): def __init__(self, center, radius, start_angle, end_angle, width): - super().__init__([ - StrokeLine( - ( - center[0] + cos(a) * radius, - center[1] + sin(a) * radius, - ), - ( - center[0] + cos(b) * radius, - center[1] + sin(b) * radius, - ), - width, - ) - for a, b in self.get_angle_segments(start_angle, end_angle) - ]) + super().__init__( + [ + (center[0] + cos(a) * radius, center[1] + sin(a) * radius) + for a in self.get_angle_segments(start_angle, end_angle) + ], + False, + width, + ) @classmethod def get_angle_segments(cls, start_angle, end_angle): num_vertices = StrokeLine.num_vertices - start_angle, end_angle = ( - min(start_angle, end_angle), max(start_angle, end_angle) - ) + start_angle, end_angle = sorted((start_angle, end_angle)) diff_angle = end_angle - start_angle - a = 0 - for i in range(1, num_vertices + 1): - b = tau * i / num_vertices - if b >= diff_angle: - yield a + start_angle, end_angle + for i in range(num_vertices): + a = tau * i / num_vertices + if a >= diff_angle: + yield end_angle break - yield a + start_angle, b + start_angle - a = b + yield start_angle + a laptop_single = Shapes( diff --git a/xinput.py b/xinput.py index 1176f55..6a78d0d 100644 --- a/xinput.py +++ b/xinput.py @@ -36,7 +36,7 @@ class XinputConf: def __init__(self, xrandr_conf): self.xrandr_conf = xrandr_conf - self.current_conf = self.get_conf() + self.conf = self.get_conf() def get_conf(self): output = subprocess.check_output(["xinput", "list"], text=True) @@ -58,17 +58,49 @@ class XinputConf: print("line", repr(line)) return devices - def update_device(self, device, enable=True): + def update_device(self, device, enable=None): + if enable is None: + enable = device["enabled"] if enable: subprocess.run(["xinput", "enable", device["id"]]) subprocess.run(["xinput", "map-to-output", device["id"], device["output"]]) else: subprocess.run(["xinput", "disable", device["id"]]) + self.conf = self.get_conf() def update(self, mode): enable_second = mode != "single" - for device in self.current_conf: + for device in self.conf: if device["output"] == self.xrandr_conf.OUTPUTS[0]: self.update_device(device) elif device["output"] == self.xrandr_conf.OUTPUTS[1]: self.update_device(device, enable_second) + + def update_by_type(self, device_type, state): + outputs = {o["name"]: o for o in self.xrandr_conf.get_relevant_outputs()} + for device in self.conf: + if device["type"] != device_type: + continue + s = state + if not outputs[device["output"]]["active"]: + s = False + self.update_device(device, s) + return state + + def conf_by_type(self, device_type): + outputs = {o["name"]: o for o in self.xrandr_conf.get_relevant_outputs()} + state = None + for device in self.conf: + if device["type"] != device_type or not outputs[device["output"]]["active"]: + continue + new_state = device["enabled"] + if state is not None and new_state != state: + return None + state = new_state + return state + + def reapply_by_type(self, device_type): + for device in self.conf: + if device["type"] != device_type: + continue + self.update_device(device, device["enabled"]) diff --git a/xrandr.py b/xrandr.py index c203b79..9263586 100644 --- a/xrandr.py +++ b/xrandr.py @@ -139,7 +139,6 @@ class XrandrConf: if line[3] == " " or "modes" not in output: continue output["modes"].append(cls.parse_mode(*lines[i:i + 3])) - #print(json.dumps(screens, indent=4)) return screens @classmethod @@ -162,8 +161,8 @@ class XrandrConf: self.call(1, "auto", rotate="left", left_of=self.OUTPUTS[0]) self.current_conf = self.get_conf() - def get_relevant_outputs(self): - screen = next(s for s in self.current_conf if s["screen"] == 0) + def get_relevant_outputs(self, screen_id=0): + screen = next(s for s in self.current_conf if s["screen"] == screen_id) outputs = [None, None] to_finds = list(self.OUTPUTS) for output in screen["outputs"]: @@ -175,16 +174,54 @@ class XrandrConf: return outputs raise ValueError(f"Outputs not found: {', '.join(self.OUTPUTS)}") + @staticmethod + def compare_output(outputs, expecteds): + for output, expected in zip(outputs, expecteds): + for key, value in expected.items(): + if key == "pos": + for p, q in zip(output[key], value): + if p != q: + return False + elif output[key] != value: + return False + return True + def is_active(self, mode): outputs = self.get_relevant_outputs() - if mode == "vertical": - expected_direction = "left" - else: - expected_direction = "normal" - second_active = mode != "single" - return ( - outputs[0]["active"] - and outputs[0]["direction"] == expected_direction - and outputs[1]["active"] == second_active - and (not second_active or outputs[1]["direction"] == expected_direction) - ) + expected = { + "single": [ + { + "active": True, + "pos": [0, 0], + "direction": "normal", + }, + { + "active": False, + }, + ], + "double": [ + { + "active": True, + "pos": [0, 0], + "direction": "normal", + }, + { + "active": True, + "pos": [0, outputs[0].get("size", [None])[-1]], + "direction": "normal", + }, + ], + "vertical": [ + { + "active": True, + "pos": [outputs[1].get("size", [None])[0], 0], + "direction": "left", + }, + { + "active": True, + "pos": [0, 0], + "direction": "left", + }, + ], + } + return self.compare_output(outputs, expected[mode]) diff --git a/zenbook_conf.py b/zenbook_conf.py index f270af9..86b262d 100755 --- a/zenbook_conf.py +++ b/zenbook_conf.py @@ -1,25 +1,15 @@ #!/usr/bin/env python +from functools import partial + import pygame from bluetooth import BluetoothConf -from vectors import laptop_double, laptop_single, laptop_vertical +from ui import Button, Switch +from vectors import laptop_double, laptop_single, laptop_vertical, touchscreen, stylus from xinput import XinputConf from xrandr import XrandrConf -# - slider button with "undecided" state -# - slider button for bluetooth -# - that springs on for the 2-screen modes, because the keyboard needs it -# - slider button for touchscreen (undecided if disabled on second screen) -# - extra button to calibrate -# - slider button for stylus (undecided if disabled on second screen) -# - extra button to calibrate -# - recognize current setting for touchscreen and stylus -# - ability to display messages -# - below the three screen buttons -# - next to the slider button for touchscreen -# - next to the slider button for stylus - class ZenbookConf: SHAPES = ( @@ -42,8 +32,8 @@ class ZenbookConf: def __init__(self): pygame.init() - self.surf = pygame.display.set_mode((1536, 768)) - self.font = pygame.font.Font(None, size=64) + self.surf = pygame.display.set_mode((1536, 1200)) + self.font = pygame.font.Font(None, size=96) self.clock = pygame.time.Clock() self.running = True self.dirty = False @@ -51,6 +41,33 @@ class ZenbookConf: self.xinput_conf = XinputConf(self.xrandr_conf) self.bluetooth_conf = BluetoothConf() self.current_fps = -1 + self.bt_switch = Switch( + pygame.Rect((128, 552), (256, 128)), + self.bluetooth_conf.update, + self.bluetooth_conf.conf + ) + self.touch_switch = Switch( + pygame.Rect((128, 748), (256, 128)), + partial(self.xinput_conf.update_by_type, "touchpad"), + self.xinput_conf.conf_by_type("touchpad"), + ) + self.touch_button = Button( + pygame.Rect((784, 748), (384, 128)), + self.font, + "Re-apply", + partial(self.xinput_conf.reapply_by_type, "touchpad") + ) + self.stylus_switch = Switch( + pygame.Rect((128, 944), (256, 128)), + partial(self.xinput_conf.update_by_type, "stylus"), + self.xinput_conf.conf_by_type("stylus"), + ) + self.stylus_button = Button( + pygame.Rect((784, 944), (384, 128)), + self.font, + "Re-apply", + partial(self.xinput_conf.reapply_by_type, "stylus") + ) def handle_event(self, ev): if ev.type == pygame.QUIT: @@ -66,13 +83,24 @@ class ZenbookConf: for item in self.SHAPES: if item["rect"].collidepoint(ev.pos): self.xrandr_conf.update(item["name"]) - self.xinput_conf.update(item["name"]) if item["name"] != "single": self.bluetooth_conf.update(True) + self.bt_switch.update(True) + self.xinput_conf.update_touch(self.touch_switch.setting) + self.xinput_conf.update_stylus(self.stylus_switch.setting) self.dirty = True + else: + self.bt_switch.handle_mousebuttondown(ev) + self.touch_switch.handle_mousebuttondown(ev) + self.stylus_switch.handle_mousebuttondown(ev) + self.dirty |= self.touch_button.handle_event(ev) + self.dirty |= self.stylus_button.handle_event(ev) return True def update(self): + self.dirty |= self.bt_switch.update() + self.dirty |= self.touch_switch.update() + self.dirty |= self.stylus_switch.update() if self.clock.get_fps() != self.current_fps: self.current_fps = int(self.clock.get_fps()) self.dirty = True @@ -91,6 +119,29 @@ class ZenbookConf: "white", ) + self.bt_switch.draw(self.surf) + fs = self.font.render("Bluetooth", True, "gray") + r = self.bt_switch.rect + self.surf.blit(fs, (r.right + 68, r.centery - fs.get_height() // 2)) + + self.touch_switch.draw(self.surf) + touchscreen.draw( + self.surf, + (self.touch_switch.rect.right + 68, self.touch_switch.rect.centery - 132), + (12, 12), + "gray", + ) + self.touch_button.draw(self.surf) + + self.stylus_switch.draw(self.surf) + stylus.draw( + self.surf, + (self.stylus_switch.rect.right + 68, self.stylus_switch.rect.centery - 132), + (12, 12), + "gray", + ) + self.stylus_button.draw(self.surf) + def run(self): while True: for ev in pygame.event.get(): -- 2.47.1