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
+22
View File
@@ -0,0 +1,22 @@
# Laying Out Windows
The experimental laying out windows command requires that you first enable a tag. You can find an example in the provided settings.talon file, or you can set it yourself like this:
```
tag(): user.experimental_window_layout
```
The `layout` command allows you to lay out multiple windows around the screen in prearranged configurations. With a single command you can arrange multiple windows and if you repeat the same command it will rotate them. Here are some example arrangements:
Half: Split the screen into two halves. The first window goes to the left half and the second goes to the right.
Thirds: Split the screen into thirds, arranging from left to right.
Clock: Arrange one window on the left half, and split the right from top to bottom.
When arranging windows if you specify nothing it will arrange in order of windows from top to bottom-in other words, the most recent three windows that you have interacted with will be snapped into the arrangement. If you want more control you can specify windows by saying an application name or using an ordinal such as 'second' to refer to the second window from the top of the window manager (the second most recently used window). If you want to skip a particular position when arranging windows, you can use the word 'gap' to skip a position. You can also use the word 'all' to refer to the rest of the windows available filling up all available slots. Here are some examples:
1. `layout clock`: Arrange the most recent three windows in a clockwise layout
2. `layout halves chrome slack`: Arrange chrome and slack in a split screen.
3. `layout halves gap slack`: Arrange slack on the right (skipping the first placement, which would have been on the left)
4. `layout clock second all`: Move these second from the top window to the first position, rearranging all other windows accordingly.
If you repeat any of these commands without interacting with the window using the mouse, it will rotate the arrangement.
+21
View File
@@ -0,0 +1,21 @@
from talon import Module, actions, app
mod = Module()
@mod.action_class
class TabActions:
def tab_jump(number: int):
"""Jumps to the specified tab"""
def tab_final():
"""Jumps to the final tab"""
def tab_close_wrapper():
"""Closes the current tab.
Exists so that apps can implement their own delay before running tab_close() to handle repetitions better.
"""
actions.app.tab_close()
def tab_duplicate():
"""Duplicates the current tab"""
@@ -0,0 +1,10 @@
tag: user.tabs
-
tab (open | new): app.tab_open()
tab (last | previous): app.tab_previous()
tab next: app.tab_next()
tab close: user.tab_close_wrapper()
tab (reopen | restore): app.tab_reopen()
go tab <number>: user.tab_jump(number)
go tab final: user.tab_final()
tab (duplicate | clone): user.tab_duplicate()
@@ -0,0 +1,291 @@
import copy
import time
from dataclasses import dataclass
from typing import List, Optional, Union
from talon import Context, Module, actions, settings, ui
from talon.ui import UIErr, Window
from .windows_and_tabs import is_window_valid
"""Tools for laying out windows in an arrangement """
SPLIT_POSITIONS = {
# Explicit layout names with only one configuration can be easier to force
# the desired result:
"HALF": ["LEFT", "RIGHT"],
"THIRDS": ["LEFT_THIRD", "CENTER_THIRD", "RIGHT_THIRD"],
"CLOCK": [
"LEFT",
"TOP_RIGHT",
"BOTTOM_RIGHT",
],
"COUNTERCLOCK": [
"RIGHT",
"TOP_LEFT",
"BOTTOM_LEFT",
],
"GRID": [
"TOP_LEFT",
"TOP_RIGHT",
"BOTTOM_LEFT",
"BOTTOM_RIGHT",
],
"BIG_GRID": [
"TOP_LEFT_THIRD",
"TOP_CENTER_THIRD",
"TOP_RIGHT_THIRD",
"BOTTOM_LEFT_THIRD",
"BOTTOM_CENTER_THIRD",
"BOTTOM_RIGHT_THIRD",
],
}
# Keys in `windows_snap.py` `_snap_positions`, ie "TopLeft", "BottomCenterThird", etc.
SnapPosition = str
@dataclass
class WindowLayout:
"""Represents a layout of windows on a screen"""
name: str
split_positions: list[SnapPosition]
windows: list[Window]
can_rotate: bool
rotation_count: int
finish_time: float
class Gap:
"""Users can leave gaps or holes (as in a code snippet) when dictating a layout;
this represents such a gap."""
pass
# Create a union type for Talon windows and Gaps:
Window = Union[Window, Gap]
# The current layout being arranged and the last one arranged, if any.
layout_in_progress: Optional[WindowLayout] = None
last_layout: Optional[WindowLayout] = None
def snap_next(windows: list[Window], target_layout: SnapPosition) -> Optional[Window]:
"""This function snaps a window and returns the window if successful"""
while windows:
window = windows.pop(0)
if isinstance(window, Gap):
return window
try:
actions.user.snap_window_to_position(
target_layout,
window,
)
return window
except (UIErr, AttributeError) as e:
print(
f'Failed to snap {window.app.name}\'s "{window.title}" window ({type(e).__name__} {e}); this is normal; continuing to the next'
)
return Gap()
def snap_layout(layout: WindowLayout):
"""Split the screen between multiple windows."""
try:
global layout_in_progress, last_layout
layout_in_progress = layout
# If called multiple times (and the user hasn't focused a window manually since
# last time), rotate the offset of the existing windows in the arrangement,
# allowing the user to use a repeater to cycle through the windows to get the
# desired result.
if (
layout.can_rotate
and last_layout is not None
and last_layout.name == layout.name
and layout.windows == last_layout.windows
):
layout.rotation_count = last_layout.rotation_count + 1
# Copy these data structures so we can mutate them:
remaining_windows = [w for w in layout.windows]
split_positions = layout.split_positions.copy()
snapped_windows = []
for _ in range(layout.rotation_count):
split_positions.append(split_positions.pop(0))
while len(split_positions) > 0:
snapped_window: Window = snap_next(
remaining_windows, split_positions.pop(0)
)
snapped_windows.insert(0, snapped_window)
if len(snapped_windows) > 0:
for _ in range(layout.rotation_count):
snapped_windows.append(snapped_windows.pop(0))
for window in snapped_windows:
if isinstance(window, Gap):
continue
actions.user.switcher_focus_window(window)
layout_in_progress.finish_time = time.perf_counter()
last_layout = layout_in_progress
finally:
layout_in_progress = None
def filter_nonviable_windows(windows: List[Window]) -> list[Window]:
active_window = ui.active_window()
# Many invisible non-resizable windows are identifiable because they exist above the current window
# in the z-index
all_windows = ui.windows()
active_window_idx = all_windows.index(active_window) # type: ignore
return list(
filter(
lambda w: (isinstance(w, Gap) or is_window_valid(w)),
windows,
)
)
mod = Module()
mod.list(
"window_split_positions",
"Predefined window positions when splitting the screen between multiple windows.",
)
mod.tag(
"experimental_window_layout",
desc="Tag to enable experimental window layout commands",
)
ctx = Context()
@mod.capture(rule="all")
def all_candidate_windows(m) -> list[Window]:
return filter_nonviable_windows(ui.windows())
@mod.capture(rule="gap")
def skip_window(m) -> list[Window]:
return [Gap()]
@mod.capture(rule="<user.running_applications>")
def application_windows(m) -> list[Window]:
return filter_nonviable_windows(
[
window
for app in m.running_applications_list
for window in actions.self.get_running_app(app).windows()
]
)
@mod.capture(
rule="<user.application_windows>|<user.numbered_windows>|<user.skip_window>"
)
def layout_item(m) -> list[Optional[Window]]:
attributes = [
"application_windows",
"numbered_windows",
"skip_window",
]
num_passed = len(list(filter(lambda attrs: hasattr(m, attrs), attributes)))
if num_passed > 1:
raise ValueError(
"Multiple attributes found on 'm'. Only one of 'application_windows', 'numbered_windows', or 'skip_window' should be present."
)
# Return the appropriate list based on which attribute is available
if hasattr(m, "application_windows"):
return m.application_windows
elif hasattr(m, "numbered_windows"):
return m.numbered_windows
elif hasattr(m, "skip_window"):
return m.skip_window
else:
return []
@mod.capture(rule="<user.ordinals_small>+")
def numbered_windows(m) -> list[Window]:
all_windows = filter_nonviable_windows(ui.windows())
selected_windows = [
all_windows[i - 1] for i in m.ordinals_small_list if i - 1 < len(all_windows)
]
return selected_windows
@mod.capture(rule="<user.layout_item>+ [<user.all_candidate_windows>]")
def target_windows(m) -> list[Window]:
windows = []
if hasattr(m, "layout_item_list"):
windows += [window for sublist in m.layout_item_list for window in sublist]
if hasattr(m, "all_candidate_windows"):
windows += [w for w in m.all_candidate_windows if w not in windows]
return windows
def pick_split_arrangement(
target_windows: Optional[list[Window]],
layout_name: str,
) -> list[SnapPosition]:
return SPLIT_POSITIONS[layout_name]
@mod.capture(rule="{user.window_split_positions} [<user.target_windows>]")
def window_layout(m) -> WindowLayout:
global last_layout
layout_name = m.window_split_positions
window_was_specified = hasattr(m, "target_windows")
target_windows = (
m.target_windows
if window_was_specified
else filter_nonviable_windows(ui.windows())
)
layout = pick_split_arrangement(target_windows, layout_name)
return WindowLayout(
name=layout_name,
split_positions=layout,
windows=target_windows,
can_rotate=True,
rotation_count=0,
finish_time=0,
)
@mod.action_class
class Actions:
def snap_layout(layout: WindowLayout):
"""Split the screen between multiple applications."""
snap_layout(layout)
def focus_callback(_):
global layout_in_progress
global last_layout
# Running a layout will generate focus events, which we don't consider to be manual
# / user initiated, so skip in that case.
if last_layout is None or layout_in_progress is not None:
return
# Track if the user has manually focused since layout and clear the state if so;
# this way we won't rotate if that same layout request is made again.
delta = time.perf_counter() - last_layout.finish_time
if delta >= 1:
last_layout = None
ui.register("app_activate", focus_callback)
ui.register("win_focus", focus_callback)
@@ -0,0 +1,3 @@
tag: user.experimental_window_layout
-
layout <user.window_layout>: user.snap_layout(window_layout)
@@ -0,0 +1,23 @@
window (new | open): app.window_open()
window next: app.window_next()
window last: app.window_previous()
window close: app.window_close()
window hide: app.window_hide()
app (preferences | prefs | settings): app.preferences()
focus <user.running_applications>: user.switcher_focus(running_applications)
# following only works on windows. Can't figure out how to make it work for mac. No idea what the equivalent for linux would be.
focus$: user.switcher_menu()
focus last: user.switcher_focus_last()
running list: user.switcher_toggle_running()
running close: user.switcher_hide_running()
launch <user.launch_applications>: user.switcher_launch(launch_applications)
snap <user.window_snap_position>: user.snap_window(window_snap_position)
snap next [screen]: user.move_window_next_screen()
snap last [screen]: user.move_window_previous_screen()
snap screen <number>: user.move_window_to_screen(number)
snap <user.running_applications> <user.window_snap_position>:
user.snap_app(running_applications, window_snap_position)
snap <user.running_applications> [screen] <number>:
user.move_app_to_screen(running_applications, number)
@@ -0,0 +1,354 @@
"""Tools for voice-driven window management.
Originally from dweil/talon_community - modified for newapi by jcaw.
"""
# TODO: Map keyboard shortcuts to this manager once Talon has key hooks on all
# platforms
import logging
from typing import Dict, Optional
from talon import Context, Module, actions, app, registry, settings, ui
from talon.ui import Window
mod = Module()
mod.list(
"window_snap_positions",
"Predefined window positions for the current window. See `RelativeScreenPos`.",
)
mod.list(
"window_split_positions",
"Predefined window positions when splitting the screen between three applications.",
)
mod.setting(
"window_snap_screen",
type=str,
default="proportional",
desc="""How to position and size windows when snapping across different physical screens. Options:
"proportional" (default): Preserve the window's relative position and size proportional to the screen.
"size aware": Preserve position relative to the screen, but keep absolute size the same, except if window is full-height or -width, keep it so.
""",
)
def _set_window_pos(window, x, y, width, height):
"""Helper to set the window position."""
window.rect = ui.Rect(round(x), round(y), round(width), round(height))
# on occassion, for whatever reason, it fails to
# position correctly on windows the first time
if app.platform == "windows" and "user.experimental_window_layout" in registry.tags:
actions.sleep("100ms")
window.rect = ui.Rect(round(x), round(y), round(width), round(height))
def _bring_forward(window):
current_window = ui.active_window()
try:
window.focus()
current_window.focus()
except Exception as e:
# We don't want to block if this fails.
print(f"Couldn't bring window to front: {e}")
def _get_app_window(app_name: str) -> ui.Window:
return actions.self.get_running_app(app_name).active_window
def interpolate_interval(w0, w1, s0, s1, d0, d1):
"""
Interpolates an interval (w0, w1) which is within (s0, s1) so that it lies
within (d0, d1). Returns (r0, r1). Tries to preserve absolute interval size,
w1 - w0, while maintaining its relative 'position' within (s0, s1). For
instance, if w0 == s0 then r0 == d0.
Use-case: fix a window w, a source screen s, and a destination screen d.
Let w0 = w.left, w1 = window.right, s0 = s.left, s1 = s.right, d0 = d.left, d1 = d.right.
"""
wsize, ssize, dsize = w1 - w0, s1 - s0, d1 - d0
assert wsize > 0 and ssize > 0 and dsize > 0
before = max(0, (w0 - s0) / ssize)
after = max(0, (s1 - w1) / ssize)
# If we're within 5% of maximized, preserve this.
if before + after <= 0.05:
return (d0, d1)
# If before is 0 (eg. window is left-aligned), we want to preserve before.
# If after is 0 (eg. window is right-aligned), we want to preserve after.
# In between, we linearly interpolate.
beforeness = before / (before + after)
afterness = after / (before + after)
a0, b1 = d0 + before * dsize, d1 - after * dsize
a1, b0 = a0 + wsize, b1 - wsize
r0 = a0 * afterness + b0 * beforeness
r1 = a1 * afterness + b1 * beforeness
return (max(d0, r0), min(d1, r1)) # clamp to destination
def _move_to_screen(
window: ui.Window, offset: Optional[int] = None, screen_number: Optional[int] = None
):
"""Move a window to a different screen.
Provide one of `offset` or `screen_number` to specify a target screen.
Provide `window` to move a specific window, otherwise the current window is
moved.
"""
assert (
screen_number or offset and not (screen_number and offset)
), "Provide exactly one of `screen_number` or `offset`."
src_screen = window.screen
if offset:
if offset < 0:
dest_screen = actions.user.screens_get_previous(src_screen)
else:
dest_screen = actions.user.screens_get_next(src_screen)
else:
dest_screen = actions.user.screens_get_by_number(screen_number)
if src_screen == dest_screen:
return
dest = dest_screen.visible_rect
src = src_screen.visible_rect
maximized = window.maximized
how = settings.get("user.window_snap_screen")
if how == "size aware":
r = window.rect
left, right = interpolate_interval(
r.left, r.right, src.left, src.right, dest.left, dest.right
)
top, bot = interpolate_interval(
r.top, r.bot, src.top, src.bot, dest.top, dest.bot
)
r.x, r.y = left, top
r.width = right - left
r.height = bot - top
window.rect = r
if maximized:
window.maximized = True
return
# TODO: Test vertical screen with different aspect ratios
# Does the orientation between the screens change? (vertical/horizontal)
if how != "proportional":
logging.warning(
f"Unrecognized 'window_snap_screen' setting: {how!r}. Using default 'proportional'."
)
if (src.width / src.height > 1) != (dest.width / dest.height > 1):
# Horizontal -> vertical or vertical -> horizontal
# Retain proportional window size, but flip x/y of the vertical monitor to account for the monitors rotation.
if src.width / src.height > 1:
# horizontal -> vertical
width = window.rect.width * dest.height / src.width
height = window.rect.height * dest.width / src.height
else:
# vertical -> horizontal
width = window.rect.width * dest.width / src.height
height = window.rect.height * dest.height / src.width
# Deform window if width or height is bigger than the target monitors while keeping the window area the same.
if width > dest.width:
over = (width - dest.width) * height
width = dest.width
height += over / width
if height > dest.height:
over = (height - dest.height) * width
height = dest.height
width += over / height
# Proportional position:
# Since the window size in respect to the monitor size is not proportional (x/y was flipped),
# the positioning is more complicated than proportionally scaling the x/y coordinates.
# It is computed by keeping the free space to the left of the window proportional to the right
# and respectively for the top/bottom free space.
# The if conditions account for division by 0. TODO: Refactor positioning without division by 0
if src.height == window.rect.height:
x = dest.left + (dest.width - width) / 2
else:
x = dest.left + (window.rect.top - src.top) * (dest.width - width) / (
src.height - window.rect.height
)
if src.width == window.rect.width:
y = dest.top + (dest.height - height) / 2
else:
y = dest.top + (window.rect.left - src.left) * (dest.height - height) / (
src.width - window.rect.width
)
else:
# Horizontal -> horizontal or vertical -> vertical
# Retain proportional size and position
proportional_width = dest.width / src.width
proportional_height = dest.height / src.height
x = dest.left + (window.rect.left - src.left) * proportional_width
y = dest.top + (window.rect.top - src.top) * proportional_height
width = window.rect.width * proportional_width
height = window.rect.height * proportional_height
_set_window_pos(window, x=x, y=y, width=width, height=height)
if maximized:
window.maximized = True
def _snap_window_helper(window, pos):
screen = window.screen.visible_rect
_set_window_pos(
window,
x=screen.x + (screen.width * pos.left),
y=screen.y + (screen.height * pos.top),
width=screen.width * (pos.right - pos.left),
height=screen.height * (pos.bottom - pos.top),
)
class RelativeScreenPos:
"""Represents a window position as a fraction of the screen."""
def __init__(self, left, top, right, bottom):
self.left = left
self.top = top
self.bottom = bottom
self.right = right
def __str__(self):
return f"RelativeScreenPos(left={self.left}, top={self.top}, right={self.right}, bottom={self.bottom})"
_snap_positions = {
# Halves
# .---.---. .-------.
# | | | & |-------|
# '---'---' '-------'
"LEFT": RelativeScreenPos(0, 0, 0.5, 1),
"RIGHT": RelativeScreenPos(0.5, 0, 1, 1),
"TOP": RelativeScreenPos(0, 0, 1, 0.5),
"BOTTOM": RelativeScreenPos(0, 0.5, 1, 1),
# Thirds
# .--.--.--.
# | | | |
# '--'--'--'
"CENTER_THIRD": RelativeScreenPos(1 / 3, 0, 2 / 3, 1),
"LEFT_THIRD": RelativeScreenPos(0, 0, 1 / 3, 1),
"RIGHT_THIRD": RelativeScreenPos(2 / 3, 0, 1, 1),
"LEFT_TWO_THIRDS": RelativeScreenPos(0, 0, 2 / 3, 1),
"RIGHT_TWO_THIRDS": RelativeScreenPos(1 / 3, 0, 1, 1),
# Alternate (simpler) spoken forms for thirds
"CENTER_SMALL": RelativeScreenPos(1 / 3, 0, 2 / 3, 1),
"LEFT_SMALL": RelativeScreenPos(0, 0, 1 / 3, 1),
"RIGHT_SMALL": RelativeScreenPos(2 / 3, 0, 1, 1),
"LEFT_LARGE": RelativeScreenPos(0, 0, 2 / 3, 1),
"RIGHT_LARGE": RelativeScreenPos(1 / 3, 0, 1, 1),
# Quarters
# .---.---.
# |---|---|
# '---'---'
"TOP_LEFT": RelativeScreenPos(0, 0, 0.5, 0.5),
"TOP_RIGHT": RelativeScreenPos(0.5, 0, 1, 0.5),
"BOTTOM_LEFT": RelativeScreenPos(0, 0.5, 0.5, 1),
"BOTTOM_RIGHT": RelativeScreenPos(0.5, 0.5, 1, 1),
# Sixths
# .--.--.--.
# |--|--|--|
# '--'--'--'
"TOP_LEFT_THIRD": RelativeScreenPos(0, 0, 1 / 3, 0.5),
"TOP_RIGHT_THIRD": RelativeScreenPos(2 / 3, 0, 1, 0.5),
"TOP_LEFT_TWO_THIRDS": RelativeScreenPos(0, 0, 2 / 3, 0.5),
"TOP_RIGHT_TWO_THIRDS": RelativeScreenPos(1 / 3, 0, 1, 0.5),
"TOP_CENTER_THIRD": RelativeScreenPos(1 / 3, 0, 2 / 3, 0.5),
"BOTTOM_LEFT_THIRD": RelativeScreenPos(0, 0.5, 1 / 3, 1),
"BOTTOM_RIGHT_THIRD": RelativeScreenPos(2 / 3, 0.5, 1, 1),
"BOTTOM_LEFT_TWO_THIRDS": RelativeScreenPos(0, 0.5, 2 / 3, 1),
"BOTTOM_RIGHT_TWO_THIRDS": RelativeScreenPos(1 / 3, 0.5, 1, 1),
"BOTTOM_CENTER_THIRD": RelativeScreenPos(1 / 3, 0.5, 2 / 3, 1),
# Alternate (simpler) spoken forms for sixths
"TOP_LEFT_SMALL": RelativeScreenPos(0, 0, 1 / 3, 0.5),
"TOP_RIGHT_SMALL": RelativeScreenPos(2 / 3, 0, 1, 0.5),
"TOP_LEFT_LARGE": RelativeScreenPos(0, 0, 2 / 3, 0.5),
"TOP_RIGHT_LARGE": RelativeScreenPos(1 / 3, 0, 1, 0.5),
"TOP_CENTER_SMALL": RelativeScreenPos(1 / 3, 0, 2 / 3, 0.5),
"BOTTOM_LEFT_SMALL": RelativeScreenPos(0, 0.5, 1 / 3, 1),
"BOTTOM_RIGHT_SMALL": RelativeScreenPos(2 / 3, 0.5, 1, 1),
"BOTTOM_LEFT_LARGE": RelativeScreenPos(0, 0.5, 2 / 3, 1),
"BOTTOM_RIGHT_LARGE": RelativeScreenPos(1 / 3, 0.5, 1, 1),
"BOTTOM_CENTER_SMALL": RelativeScreenPos(1 / 3, 0.5, 2 / 3, 1),
# Special
"CENTER": RelativeScreenPos(1 / 8, 1 / 6, 7 / 8, 5 / 6),
"FULL": RelativeScreenPos(0, 0, 1, 1),
"FULLSCREEN": RelativeScreenPos(0, 0, 1, 1),
}
@mod.capture(rule="{user.window_snap_positions}")
def window_snap_position(m) -> RelativeScreenPos:
return _snap_positions[m.window_snap_positions]
ctx = Context()
ctx.lists["user.window_snap_positions"] = _snap_positions.keys()
@mod.action_class
class Actions:
def snap_window(
position: RelativeScreenPos, window: Optional[Window] = None
) -> None:
"""Move a window (defaults to the active window) to a specific position on its current screen, given a `RelativeScreenPos` object."""
if window is None:
window = ui.active_window()
_snap_window_helper(window, position)
def snap_window_to_position(
position_name: str, window: Optional[Window] = None
) -> None:
"""Move a window (defaults to the active window) to a specifically named position on its current screen, using a key from `_snap_positions`."""
position: Optional[RelativeScreenPos] = None
if position_name in _snap_positions:
position = _snap_positions[position_name]
actions.user.snap_window(position, window)
else:
# Previously this function took a spoken form, but we now have constant identifiers in `_snap_positions`.
# If the user passed a previous spoken form instead, see if we can convert it to the new identifier.
new_key = actions.user.formatted_text(position_name, "ALL_CAPS,SNAKE_CASE")
if new_key in _snap_positions:
actions.user.deprecate_action(
"2024-12-02",
f"snap_window_to_position('{position_name}')",
f"snap_window_to_position('{new_key}')",
)
position = _snap_positions[new_key]
actions.user.snap_window(position, window)
else:
raise KeyError(position_name)
def move_window_next_screen() -> None:
"""Move the active window to a specific screen."""
_move_to_screen(ui.active_window(), offset=1)
def move_window_previous_screen() -> None:
"""Move the active window to the previous screen."""
_move_to_screen(ui.active_window(), offset=-1)
def move_window_to_screen(screen_number: int) -> None:
"""Move the active window leftward by one."""
_move_to_screen(ui.active_window(), screen_number=screen_number)
def snap_app(app_name: str, position: RelativeScreenPos):
"""Snap a specific application to another screen."""
window = _get_app_window(app_name)
_bring_forward(window)
_snap_window_helper(window, position)
def move_app_to_screen(app_name: str, screen_number: int):
"""Move a specific application to another screen."""
window = _get_app_window(app_name)
_bring_forward(window)
_move_to_screen(
window,
screen_number=screen_number,
)
@@ -0,0 +1,44 @@
list: user.window_snap_positions
-
left: LEFT
right: RIGHT
top: TOP
bottom: BOTTOM
center third: CENTER_THIRD
left third: LEFT_THIRD
right third: RIGHT_THIRD
left two thirds: LEFT_TWO_THIRDS
right two thirds: RIGHT_TWO_THIRDS
center small: CENTER_SMALL
left small: LEFT_SMALL
right small: RIGHT_SMALL
left large: LEFT_LARGE
right large: RIGHT_LARGE
top left: TOP_LEFT
top right: TOP_RIGHT
bottom left: BOTTOM_LEFT
bottom right: BOTTOM_RIGHT
top left third: TOP_LEFT_THIRD
top right third: TOP_RIGHT_THIRD
top left two thirds: TOP_LEFT_TWO_THIRDS
top right two thirds: TOP_RIGHT_TWO_THIRDS
top center third: TOP_CENTER_THIRD
bottom left third: BOTTOM_LEFT_THIRD
bottom right third: BOTTOM_RIGHT_THIRD
bottom left two thirds: BOTTOM_LEFT_TWO_THIRDS
bottom right two thirds: BOTTOM_RIGHT_TWO_THIRDS
bottom center third: BOTTOM_CENTER_THIRD
top left small: TOP_LEFT_SMALL
top right small: TOP_RIGHT_SMALL
top left large: TOP_LEFT_LARGE
top right large: TOP_RIGHT_LARGE
top center small: TOP_CENTER_SMALL
bottom left small: BOTTOM_LEFT_SMALL
bottom right small: BOTTOM_RIGHT_SMALL
bottom left large: BOTTOM_LEFT_LARGE
bottom right large: BOTTOM_RIGHT_LARGE
bottom center small: BOTTOM_CENTER_SMALL
center: CENTER
full: FULL
fullscreen: FULLSCREEN
@@ -0,0 +1,9 @@
list: user.window_split_positions
-
half: HALF
thirds: THIRDS
clock: CLOCK
counterclock: COUNTERCLOCK
grid: GRID
big grid: BIG_GRID
@@ -0,0 +1,45 @@
from talon import Context, actions, ui
ctx = Context()
@ctx.action_class("app")
class AppActions:
def window_previous():
cycle_windows(ui.active_app(), -1)
def window_next():
cycle_windows(ui.active_app(), 1)
def cycle_windows(app: ui.App, diff: int):
"""Cycle windows backwards or forwards for the given application"""
active = app.active_window
windows = [w for w in app.windows() if w == active or is_window_valid(w)]
windows.sort(key=lambda w: w.id)
current = windows.index(active)
i = (current + diff) % len(windows)
while i != current:
try:
actions.user.switcher_focus_window(windows[i])
break
except Exception:
i = (i + diff) % len(windows)
def is_window_valid(window: ui.Window) -> bool:
"""Returns true if this window is valid for focusing"""
try:
return (
not window.hidden
# On Windows, there are many fake windows with empty titles -- this excludes them.
and len(window.title) > 0
and (window.title not in ("Chrome Legacy Window"))
# This excludes many tiny windows that are not actual windows, and is a rough heuristic.
and window.rect.width > window.screen.dpi
and window.rect.height > window.screen.dpi
)
except AttributeError:
# Handle case where window.rect might not be accessible
return False
@@ -0,0 +1,44 @@
# defines the default app actions for linux
from talon import Context, actions
ctx = Context()
ctx.matches = r"""
os: linux
"""
@ctx.action_class("app")
class AppActions:
def tab_close():
actions.key("ctrl-w")
def tab_next():
actions.key("ctrl-tab")
def tab_open():
actions.key("ctrl-t")
def tab_previous():
actions.key("ctrl-shift-tab")
def tab_reopen():
actions.key("ctrl-shift-t")
def window_close():
actions.key("alt-f4")
def window_hide():
actions.key("alt-space n")
def window_hide_others():
actions.key("win-d alt-tab")
def window_open():
actions.key("ctrl-n")
@ctx.action_class("user")
class UserActions:
def switcher_focus_last():
actions.key("alt-tab")
@@ -0,0 +1,51 @@
from talon import Context, actions
ctx = Context()
ctx.matches = r"""
os: mac
"""
@ctx.action_class("app")
class AppActions:
def preferences():
actions.key("cmd-,")
def tab_close():
actions.key("cmd-w")
def tab_next():
actions.key("ctrl-tab")
def tab_open():
actions.key("cmd-t")
def tab_previous():
actions.key("ctrl-shift-tab")
def tab_reopen():
actions.key("cmd-shift-t")
def window_close():
actions.key("cmd-w")
def window_hide():
actions.key("cmd-m")
def window_hide_others():
actions.key("cmd-alt-h")
def window_open():
actions.key("cmd-n")
def window_previous():
actions.key("cmd-shift-`")
def window_next():
actions.key("cmd-`")
@ctx.action_class("user")
class UserActions:
def switcher_focus_last():
actions.key("cmd-tab")
@@ -0,0 +1,44 @@
# defines the default app actions for windows
from talon import Context, actions
ctx = Context()
ctx.matches = r"""
os: windows
"""
@ctx.action_class("app")
class AppActions:
def tab_close():
actions.key("ctrl-w")
def tab_next():
actions.key("ctrl-tab")
def tab_open():
actions.key("ctrl-t")
def tab_previous():
actions.key("ctrl-shift-tab")
def tab_reopen():
actions.key("ctrl-shift-t")
def window_close():
actions.key("alt-f4")
def window_hide():
actions.key("alt-space n")
def window_hide_others():
actions.key("win-d alt-tab")
def window_open():
actions.key("ctrl-n")
@ctx.action_class("user")
class UserActions:
def switcher_focus_last():
actions.key("alt-tab")