init commit

This commit is contained in:
unknown
2025-08-19 08:06:37 -04:00
commit 2957b5515a
743 changed files with 45495 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
# Gamepad
Some predefined gamepad bindings for doing tasks like clicking, scrolling and moving your cursor.
### Usage
To enable the gamepad bindings activate tag `user.gamepad` in [gamepad_settings.talon](./gamepad_settings.talon)
## Demo - Using gamepad
[YouTube - Gamepad demo](https://youtu.be/zNeiZ9nnK_A)
## Gamepad tester
![Gamepad tester](./gamepad_tester.png)
### Usage
1. Say `"gamepad tester"` to open gamepad tester UI.
1. Press buttons on actual gamepad and see interaction in UI.
1. Close gamepad tester by saying `"gamepad tester"` again.
### Demo - Gamepad tester
[YouTube - Gamepad tester demo](https://youtu.be/FzfIlaHm8_w)
### Conflict with existing gamepad implementations
The gamepad tester doesn't disable your existing gamepad implementations. If you don't want your existing gamepad implementations to trigger during the testing phase you can add `not tag: user.gamepad_tester` at the top of your gamepad Talon files.
+304
View File
@@ -0,0 +1,304 @@
from talon import Module, actions, ctrl, ui
from talon.screen import Screen
screen: Screen = ui.main_screen()
slow_scroll = False
slow_mouse_move = False
mod = Module()
mod.tag("gamepad", desc="Activate tag to enable gamepad bindings")
@mod.action_class
class Actions:
# DPAD buttons
def gamepad_press_dpad_left():
"""Gamepad press button dpad left"""
gamepad_mouse_jump("left")
def gamepad_release_dpad_left():
"""Gamepad release button dpad left"""
actions.skip()
def gamepad_press_dpad_up():
"""Gamepad press button dpad up"""
gamepad_mouse_jump("up")
def gamepad_release_dpad_up():
"""Gamepad release button dpad up"""
actions.skip()
def gamepad_press_dpad_right():
"""Gamepad press button dpad right"""
gamepad_mouse_jump("right")
def gamepad_release_dpad_right():
"""Gamepad release button dpad right"""
actions.skip()
def gamepad_press_dpad_down():
"""Gamepad press button dpad down"""
gamepad_mouse_jump("down")
def gamepad_release_dpad_down():
"""Gamepad release button dpad down"""
actions.skip()
# Compass / ABXY buttons
def gamepad_press_west():
"""Gamepad press button west"""
actions.mouse_drag(0)
def gamepad_release_west():
"""Gamepad release button west"""
actions.mouse_release(0)
def gamepad_press_north():
"""Gamepad press button north"""
actions.mouse_drag(1)
def gamepad_release_north():
"""Gamepad release button north"""
actions.mouse_release(1)
def gamepad_press_east():
"""Gamepad press button east"""
actions.key("ctrl:down")
actions.mouse_click()
actions.key("ctrl:up")
def gamepad_release_east():
"""Gamepad release button east"""
actions.skip()
def gamepad_press_south():
"""Gamepad press button south"""
actions.mouse_drag(2)
def gamepad_release_south():
"""Gamepad release button south"""
actions.mouse_release(2)
# Select / Start buttons
def gamepad_press_select():
"""Gamepad press button select"""
actions.skip()
def gamepad_release_select():
"""Gamepad release button select"""
actions.skip()
def gamepad_press_start():
"""Gamepad press button start"""
actions.speech.toggle()
def gamepad_release_start():
"""Gamepad release button start"""
actions.skip()
# Shoulder buttons
def gamepad_press_left_shoulder():
"""Gamepad press button left shoulder"""
actions.user.go_back()
def gamepad_release_left_shoulder():
"""Gamepad release button left shoulder"""
actions.skip()
def gamepad_press_right_shoulder():
"""Gamepad press button right shoulder"""
actions.user.go_forward()
def gamepad_release_right_shoulder():
"""Gamepad release button right shoulder"""
actions.skip()
# Stick buttons
def gamepad_press_left_stick():
"""Gamepad press button left thumb stick"""
gamepad_scroll_slow_toggle()
def gamepad_release_left_stick():
"""Gamepad release button left thumb stick"""
actions.skip()
def gamepad_press_right_stick():
"""Gamepad press button right thumb stick"""
gamepad_mouse_move_slow_toggle()
def gamepad_release_right_stick():
"""Gamepad release button right thumb stick"""
actions.skip()
# Analog triggers
def gamepad_trigger_left(value: float):
"""Gamepad trigger left movement"""
gamepad_scroll(0, value * -1)
def gamepad_trigger_right(value: float):
"""Gamepad trigger right movement"""
gamepad_scroll(0, value)
# Analog thumb sticks
def gamepad_stick_left(x: float, y: float):
"""Gamepad left stick movement"""
gamepad_scroll(x, y)
def gamepad_stick_right(x: float, y: float):
"""Gamepad right stick movement"""
gamepad_mouse_move(x, y)
# Scaffolding actions used by the Talon file
def gamepad_button_down(button: str):
"""Gamepad press button <button>"""
match button:
# DPAD buttons
case "dpad_left":
actions.user.gamepad_press_dpad_left()
case "dpad_up":
actions.user.gamepad_press_dpad_up()
case "dpad_right":
actions.user.gamepad_press_dpad_right()
case "dpad_down":
actions.user.gamepad_press_dpad_down()
# Compass / ABXY buttons
case "west":
actions.user.gamepad_press_west()
case "north":
actions.user.gamepad_press_north()
case "east":
actions.user.gamepad_press_east()
case "south":
actions.user.gamepad_press_south()
# Select / Start buttons
case "select":
actions.user.gamepad_press_select()
case "start":
actions.user.gamepad_press_start()
# Shoulder buttons
case "left_shoulder":
actions.user.gamepad_press_left_shoulder()
case "right_shoulder":
actions.user.gamepad_press_right_shoulder()
# Stick buttons
case "left_stick":
actions.user.gamepad_press_left_stick()
case "right_stick":
actions.user.gamepad_press_right_stick()
case _:
raise ValueError(f"Unknown button: {button}")
def gamepad_button_up(button: str):
"""Gamepad release button <button>"""
match button:
# DPAD buttons
case "dpad_left":
actions.user.gamepad_release_dpad_left()
case "dpad_up":
actions.user.gamepad_release_dpad_up()
case "dpad_right":
actions.user.gamepad_release_dpad_right()
case "dpad_down":
actions.user.gamepad_release_dpad_down()
# Compass / ABXY buttons
case "west":
actions.user.gamepad_release_west()
case "north":
actions.user.gamepad_release_north()
case "east":
actions.user.gamepad_release_east()
case "south":
actions.user.gamepad_release_south()
# Select / Start buttons
case "select":
actions.user.gamepad_release_select()
case "start":
actions.user.gamepad_release_start()
# Shoulder buttons
case "left_shoulder":
actions.user.gamepad_release_left_shoulder()
case "right_shoulder":
actions.user.gamepad_release_right_shoulder()
# Stick buttons
case "left_stick":
actions.user.gamepad_release_left_stick()
case "right_stick":
actions.user.gamepad_release_right_stick()
case _:
raise ValueError(f"Unknown button: {button}")
def gamepad_scroll(x: float, y: float):
"""Perform gamepad scrolling"""
multiplier = 1.5 if slow_scroll else 3
x = x**3 * multiplier
y = y**3 * multiplier
if x != 0 or y != 0:
actions.mouse_scroll(x=x, y=y, by_lines=True)
def gamepad_mouse_move(dx: float, dy: float):
"""Perform gamepad mouse cursor movement"""
multiplier = 0.1 if slow_mouse_move else 0.2
x, y = ctrl.mouse_pos()
screen = get_screen(x, y)
dx = dx**3 * screen.dpi * multiplier
dy = dy**3 * screen.dpi * multiplier
actions.mouse_move(x + dx, y + dy)
def gamepad_scroll_slow_toggle():
"""Toggle gamepad slow scroll mode"""
global slow_scroll
slow_scroll = not slow_scroll
def gamepad_mouse_move_slow_toggle():
"""Toggle gamepad slow mouse move mode"""
global slow_mouse_move
slow_mouse_move = not slow_mouse_move
def gamepad_mouse_jump(direction: str):
"""Move the mouse cursor to the specified quadrant of the active screen"""
x, y = ctrl.mouse_pos()
rect = ui.screen_containing(x, y).rect
# Half distance between cursor and screen edge
match direction:
case "up":
y = rect.top + (y - rect.top) / 2
case "down":
y = rect.bot - (rect.bot - y) / 2
case "left":
x = rect.left + (x - rect.left) / 2
case "right":
x = rect.right - (rect.right - x) / 2
actions.mouse_move(x, y)
def get_screen(x: float, y: float) -> Screen:
global screen
if not screen.contains(x, y):
screen = ui.screen_containing(x, y)
return screen
+49
View File
@@ -0,0 +1,49 @@
tag: user.gamepad
and not tag: user.gamepad_tester
-
# DPAD buttons
gamepad(dpad_left:down): user.gamepad_button_down("dpad_left")
gamepad(dpad_left:up): user.gamepad_button_up("dpad_left")
gamepad(dpad_up:down): user.gamepad_button_down("dpad_up")
gamepad(dpad_up:up): user.gamepad_button_up("dpad_up")
gamepad(dpad_right:down): user.gamepad_button_down("dpad_right")
gamepad(dpad_right:up): user.gamepad_button_up("dpad_right")
gamepad(dpad_down:down): user.gamepad_button_down("dpad_down")
gamepad(dpad_down:up): user.gamepad_button_up("dpad_down")
# Compass / ABXY buttons
gamepad(west:down): user.gamepad_button_down("west")
gamepad(west:up): user.gamepad_button_up("west")
gamepad(north:down): user.gamepad_button_down("north")
gamepad(north:up): user.gamepad_button_up("north")
gamepad(east:down): user.gamepad_button_down("east")
gamepad(east:up): user.gamepad_button_up("east")
gamepad(south:down): user.gamepad_button_down("south")
gamepad(south:up): user.gamepad_button_up("south")
# Select / Start buttons
gamepad(select:down): user.gamepad_button_down("select")
gamepad(select:up): user.gamepad_button_up("select")
gamepad(start:down): user.gamepad_button_down("start")
gamepad(start:up): user.gamepad_button_up("start")
# Shoulder buttons
gamepad(l1:down): user.gamepad_button_down("left_shoulder")
gamepad(l1:up): user.gamepad_button_up("left_shoulder")
gamepad(r1:down): user.gamepad_button_down("right_shoulder")
gamepad(r1:up): user.gamepad_button_up("right_shoulder")
# Stick buttons
gamepad(l3:down): user.gamepad_button_down("left_stick")
gamepad(l3:up): user.gamepad_button_up("left_stick")
gamepad(r3:down): user.gamepad_button_down("right_stick")
gamepad(r3:up): user.gamepad_button_up("right_stick")
# Analog triggers
gamepad(l2:repeat): user.gamepad_trigger_left(value)
gamepad(r2:repeat): user.gamepad_trigger_right(value)
# Analog thumb sticks
gamepad(left_xy:repeat): user.gamepad_stick_left(x, y*-1)
gamepad(right_xy:repeat): user.gamepad_stick_right(x, y*-1)
@@ -0,0 +1,2 @@
# Uncomment to enable gamepad support
# tag(): user.gamepad
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

+290
View File
@@ -0,0 +1,290 @@
from talon import Context, Module, ui
from talon.canvas import Canvas, MouseEvent
from talon.screen import Screen
from talon.skia.canvas import Canvas as SkiaCanvas
from talon.types import Point2d, Rect
mod = Module()
ctx = Context()
canvas: Canvas | None = None
last_mouse_pos: Point2d | None = None
mod.tag("gamepad_tester", "Gamepad tester gui is showing")
buttons = {
"dpad_up": False,
"dpad_right": False,
"dpad_down": False,
"dpad_left": False,
"north": False,
"east": False,
"south": False,
"west": False,
"select": False,
"start": False,
"l1": False,
"r1": False,
"l3": False,
"r3": False,
}
triggers = {
"l2": 0.0,
"r2": 0.0,
}
sticks = {
"left": (0.0, 0.0),
"right": (0.0, 0.0),
}
BACKGROUND_COLOR = "fffafa" # Snow
BORDER_COLOR = "000000" # Black
FONT_SIZE = 16
WIDTH = 900
HEIGHT = 800
CIRCLE_RADIUS = 100
BUTTON_OFFSET = CIRCLE_RADIUS / 2
BUTTON_RADIUS = BUTTON_OFFSET / 2
ROW_OFFSET = CIRCLE_RADIUS * 1.25
BUTTON_FLAT_WIDTH = BUTTON_RADIUS * 3
BUTTON_FLAT_HEIGHT = BUTTON_RADIUS
TRIGGER_HEIGHT = CIRCLE_RADIUS
BUTTON_OFFSETS = [(0, -1), (1, 0), (0, 1), (-1, 0)]
def render_round_button(c: SkiaCanvas, x: float, y: float, is_pressed: bool):
c.paint.style = c.paint.Style.FILL if is_pressed else c.paint.Style.STROKE
c.draw_circle(x, y, BUTTON_RADIUS)
def render_square_button(c: SkiaCanvas, x: float, y: float, is_pressed: bool):
c.paint.style = c.paint.Style.FILL if is_pressed else c.paint.Style.STROKE
c.draw_rect(
Rect(
x - BUTTON_RADIUS,
y - BUTTON_RADIUS,
BUTTON_RADIUS * 2,
BUTTON_RADIUS * 2,
)
)
def render_flat_button(c: SkiaCanvas, x: float, y: float, is_pressed: bool):
c.paint.style = c.paint.Style.FILL if is_pressed else c.paint.Style.STROKE
c.draw_rect(
Rect(
x - BUTTON_FLAT_WIDTH / 2,
y - BUTTON_FLAT_HEIGHT / 2,
BUTTON_FLAT_WIDTH,
BUTTON_FLAT_HEIGHT,
)
)
def render_buttons(
c: SkiaCanvas, x: float, y: float, useCircle: bool, buttons_ids: list[str]
):
c.paint.style = c.paint.Style.STROKE
c.draw_circle(x, y, CIRCLE_RADIUS)
for i, button_id in enumerate(buttons_ids):
(offset_x, offset_y) = BUTTON_OFFSETS[i]
button_x = x + offset_x * BUTTON_OFFSET
button_y = y + offset_y * BUTTON_OFFSET
is_pressed = buttons[button_id]
if useCircle:
render_round_button(c, button_x, button_y, is_pressed)
else:
render_square_button(c, button_x, button_y, is_pressed)
def render_trigger(c: SkiaCanvas, x: float, y: float, value: float):
# Render button outline
c.paint.style = c.paint.Style.STROKE
c.draw_rect(
Rect(
x - BUTTON_FLAT_WIDTH / 2,
y - TRIGGER_HEIGHT,
BUTTON_FLAT_WIDTH,
TRIGGER_HEIGHT,
)
)
# Render button value
c.paint.style = c.paint.Style.FILL
height = value * TRIGGER_HEIGHT
c.draw_rect(
Rect(
x - BUTTON_FLAT_WIDTH / 2,
y - height,
BUTTON_FLAT_WIDTH,
height,
)
)
# Render value text
text = str(round(value * 100))
text_rect = c.paint.measure_text(text)[1]
c.draw_text(
text,
x - text_rect.x - text_rect.width / 2,
y - TRIGGER_HEIGHT - text_rect.height,
)
def render_stick(
c: SkiaCanvas, x: float, y: float, is_pressed: bool, value_x: float, value_y: float
):
# Stick click
if is_pressed:
c.paint.style = c.paint.Style.FILL
c.draw_circle(x, y, CIRCLE_RADIUS)
return
c.paint.style = c.paint.Style.STROKE
# Draw outer circle
c.draw_circle(x, y, CIRCLE_RADIUS)
# Draw cross
c.draw_line(x - CIRCLE_RADIUS, y, x + CIRCLE_RADIUS, y)
c.draw_line(x, y - CIRCLE_RADIUS, x, y + CIRCLE_RADIUS)
dot_x = x + value_x * CIRCLE_RADIUS
dot_y = y + value_y * CIRCLE_RADIUS
# Draw line to dot
c.draw_line(x, y, dot_x, dot_y)
# Draw center dot
c.paint.style = c.paint.Style.FILL
c.draw_circle(dot_x, dot_y, 5)
# Render value texts
text = f"{round(value_x * 100)}, {round(value_y * 100)}"
text_rect = c.paint.measure_text(text)[1]
c.draw_text(
text,
x - text_rect.x - text_rect.width / 2,
y - CIRCLE_RADIUS - text_rect.height,
)
def render_close_text(c: SkiaCanvas, x: float, y: float):
text = 'Say "gamepad tester" to close'
text_rect = c.paint.measure_text(text)[1]
c.draw_text(
text,
x - text_rect.x - text_rect.width / 2,
y - 2 * text_rect.height,
)
def on_draw(c: SkiaCanvas):
c.paint.textsize = FONT_SIZE
# Render background
c.paint.style = c.paint.Style.FILL
c.paint.color = BACKGROUND_COLOR
c.draw_rect(c.rect)
c.paint.color = BORDER_COLOR
y_center = c.rect.center.y + ROW_OFFSET * 0.75
offset = CIRCLE_RADIUS * 2.5
# Draw trigger buttons
y = y_center - ROW_OFFSET * 2.5
render_trigger(c, c.rect.center.x - offset, y, triggers["l2"])
render_trigger(c, c.rect.center.x + offset, y, triggers["r2"])
# Draw bumper buttons
y = y_center - ROW_OFFSET * 2.15
render_flat_button(c, c.rect.center.x - offset, y, buttons["l1"])
render_flat_button(c, c.rect.center.x + offset, y, buttons["r1"])
y = y_center - ROW_OFFSET
# Draw D-pad buttons
render_buttons(
c,
c.rect.center.x - offset,
y,
False,
["dpad_up", "dpad_right", "dpad_down", "dpad_left"],
)
# Draw compass buttons
render_buttons(
c,
c.rect.center.x + offset,
y,
True,
["north", "east", "south", "west"],
)
# Draw select/start buttons
offset = CIRCLE_RADIUS * 0.75
y = y_center - ROW_OFFSET / 3
render_round_button(c, c.rect.center.x - offset, y, buttons["select"])
render_round_button(c, c.rect.center.x + offset, y, buttons["start"])
# Draw sticks
offset = CIRCLE_RADIUS * 1.5
y = y_center + ROW_OFFSET
render_stick(c, c.rect.center.x - offset, y, buttons["l3"], *sticks["left"])
render_stick(c, c.rect.center.x + offset, y, buttons["r3"], *sticks["right"])
# Draw close text
render_close_text(c, c.rect.center.x, c.rect.bot)
def on_mouse(e: MouseEvent):
global last_mouse_pos
if e.event == "mousedown" and e.button == 0:
last_mouse_pos = e.gpos
elif e.event == "mousemove" and last_mouse_pos:
dx = e.gpos.x - last_mouse_pos.x
dy = e.gpos.y - last_mouse_pos.y
last_mouse_pos = e.gpos
if canvas is not None:
canvas.move(canvas.rect.x + dx, canvas.rect.y + dy)
elif e.event == "mouseup" and e.button == 0:
last_mouse_pos = None
def show():
global canvas
screen: Screen = ui.main_screen()
x = screen.rect.center.x
y = screen.rect.center.y
canvas = Canvas.from_rect(Rect(x - WIDTH / 2, y - HEIGHT / 2, WIDTH, HEIGHT))
canvas.draggable = True
canvas.blocks_mouse = True
canvas.register("draw", on_draw)
canvas.register("mouse", on_mouse)
ctx.tags = ["user.gamepad_tester"]
def hide():
global canvas
if canvas is not None:
canvas.unregister("draw", on_draw)
canvas.unregister("mouse", on_mouse)
canvas.close()
canvas = None
ctx.tags = []
@mod.action_class
class Actions:
def gamepad_tester_toggle():
"""Toggle visibility of gamepad tester gui"""
if canvas is None:
show()
else:
hide()
def gamepad_tester_button(id: str, is_pressed: bool):
"""Indicates that a gamepad button has changed state"""
buttons[id] = is_pressed
def gamepad_tester_trigger(id: str, value: float):
"""Indicates that a gamepad trigger has changed state"""
triggers[id] = value
def gamepad_tester_stick(id: str, x: float, y: float):
"""Indicates that a gamepad stick has changed state"""
sticks[id] = (x, y)
@@ -0,0 +1 @@
gamepad tester: user.gamepad_tester_toggle()
@@ -0,0 +1,48 @@
tag: user.gamepad_tester
-
# D-pad buttons
gamepad(dpad_up:up): user.gamepad_tester_button("dpad_up", false)
gamepad(dpad_up:down): user.gamepad_tester_button("dpad_up", true)
gamepad(dpad_right:up): user.gamepad_tester_button("dpad_right", false)
gamepad(dpad_right:down): user.gamepad_tester_button("dpad_right", true)
gamepad(dpad_down:up): user.gamepad_tester_button("dpad_down", false)
gamepad(dpad_down:down): user.gamepad_tester_button("dpad_down", true)
gamepad(dpad_left:up): user.gamepad_tester_button("dpad_left", false)
gamepad(dpad_left:down): user.gamepad_tester_button("dpad_left", true)
# Compass buttons
gamepad(north:up): user.gamepad_tester_button("north", false)
gamepad(north:down): user.gamepad_tester_button("north", true)
gamepad(east:up): user.gamepad_tester_button("east", false)
gamepad(east:down): user.gamepad_tester_button("east", true)
gamepad(south:up): user.gamepad_tester_button("south", false)
gamepad(south:down): user.gamepad_tester_button("south", true)
gamepad(west:up): user.gamepad_tester_button("west", false)
gamepad(west:down): user.gamepad_tester_button("west", true)
# Select/start buttons
gamepad(select:up): user.gamepad_tester_button("select", false)
gamepad(select:down): user.gamepad_tester_button("select", true)
gamepad(start:up): user.gamepad_tester_button("start", false)
gamepad(start:down): user.gamepad_tester_button("start", true)
# Bumper buttons
gamepad(l1:up): user.gamepad_tester_button("l1", false)
gamepad(l1:down): user.gamepad_tester_button("l1", true)
gamepad(r1:up): user.gamepad_tester_button("r1", false)
gamepad(r1:down): user.gamepad_tester_button("r1", true)
# Stick click buttons
gamepad(l3:up): user.gamepad_tester_button("l3", false)
gamepad(l3:down): user.gamepad_tester_button("l3", true)
gamepad(r3:up): user.gamepad_tester_button("r3", false)
gamepad(r3:down): user.gamepad_tester_button("r3", true)
# Trigger buttons
gamepad(l2:change): user.gamepad_tester_trigger("l2", value)
gamepad(r2:change): user.gamepad_tester_trigger("r2", value)
# Sticks axis
gamepad(left_xy): user.gamepad_tester_stick("left", x, y*-1)
gamepad(right_xy): user.gamepad_tester_stick("right", x, y*-1)