-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()
+++ /dev/null
-#!/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()
--- /dev/null
+#!/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()
#!/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
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(
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)
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"])
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
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"]:
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])
#!/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 = (
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
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:
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
"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():