From d70b4870abb4fbfa797c484fa6c8b54f19daf912 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 20 Jul 2024 20:25:38 +0700 Subject: [PATCH 1/3] avoid using "sticky enable" i3 window command if the window is already marked as sticky turns out, a new _NET_WM_STATE_STICKY was added every time the command was fired it's not like it was breaking anything, but still --- quake-terminal.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quake-terminal.py b/quake-terminal.py index 876a1d7..49c20fa 100755 --- a/quake-terminal.py +++ b/quake-terminal.py @@ -188,7 +188,8 @@ def show_internal(window: i3ipc.Con, position: tuple[int, int], size: tuple[int, """Actually moves and resizes the window to dimensions specified""" x, y = position w, h = size - window.command('scratchpad show, sticky enable,' + sticky_command = '' if window.sticky else 'sticky enable,' + window.command(f'scratchpad show, {sticky_command}' + f' resize set {w}px {h}px, move position {x}px {y}px') def focus(window: i3ipc.Con): From 1cb3804e498fa0074d499b680404798d09eb352c Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 20 Jul 2024 21:09:12 +0700 Subject: [PATCH 2/3] comments/documentation improvements, other formatting changes, license year updated --- LICENSE | 2 +- README.md | 10 ++++++---- quake-terminal.py | 38 +++++++++++++++++++++++++------------- 3 files changed, 32 insertions(+), 18 deletions(-) diff --git a/LICENSE b/LICENSE index 1f3a13c..a961fc4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022-2023 bnfour +Copyright (c) 2022-2024 bnfour Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index c1b22ab..bd4f6d2 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A script for i3 window manager to have one global drop-down terminal window togg Just drop `quake-terminal.py` somewhere and create some keybinds to launch it in your i3 config. ## Requirements -Requires `i3ipc` package ([PyPI](https://pypi.org/project/i3ipc/), [GitHub](https://github.com/altdesktop/i3ipc-python)). +Requires `i3ipc` package ([PyPI](https://pypi.org/project/i3ipc/), [GitHub](https://github.com/altdesktop/i3ipc-python)). Fellow Fedora enjoyers: `sudo dnf install python3-i3ipc` @@ -14,7 +14,8 @@ For other distros, consult your package manager repos, or install via `pip`: `python -m pip install i3ipc` # Usage -The script will create a single sticky terminal window on specified monitor, toggleable via hotkey. The first call will launch show the window, the second call will hide it. If the window is closed, subsequent call will create it again. +The script creates a single sticky terminal window on specified output, toggleable via calling the script again. The first call will show the window, the second call will hide it, and so on. If the window is closed, the first subsequent call will create and show a new one. + Available settings: ``` $ quake-terminal.py -? @@ -61,12 +62,13 @@ This script only really supports `urxvt` (as it's _the_ terminal emulator I use) Otherwise, please extend the script to work with another terminal emulator. See `terminals` dict in the configuration section. ## Flickering -As stated in help message, to prevent terminal window first appearing in default position and visibly teleporting, add a rule to i3 config to move it to the scratchpad by default. +As stated in the help message, to prevent terminal window first appearing in default position and visibly teleporting to proper position, add a [`for_window` rule](https://i3wm.org/docs/userguide.html#for_window) to your i3 config to move it to the scratchpad by default. + An example for default settings, an `urxvt` window called "The terminal": ``` for_window [class="URxvt" title="The terminal"] move scratchpad ``` -Adjust class and title as needed. +Adjust class and title as needed. Class name for your terminal emulator can be found using `xprop`. # Credits This script is inspired by https://github.com/NearHuscarl/i3-quake. If this script is not exactly what you're looking for, check it out! diff --git a/quake-terminal.py b/quake-terminal.py index 49c20fa..1733ffe 100755 --- a/quake-terminal.py +++ b/quake-terminal.py @@ -11,6 +11,7 @@ import os import sys import time +from typing import NamedTuple, Final try: import i3ipc @@ -18,13 +19,11 @@ print('i3ipc module not found. Exiting.') sys.exit(1) -from typing import NamedTuple, Final - #endregion #region configuration -version: Final = "2.0" +version: Final = '2.0' class Defaults(object): """ @@ -61,7 +60,10 @@ class Terminal(NamedTuple): } def get_args() -> tuple[argparse.Namespace, list[str]]: - + """ + Returns parsed arguments for the script itself, + and a list of unrecognized arguments to be passed to the terminal emulator as is. + """ parser = argparse.ArgumentParser(add_help=False, description='A script to have one global terminal window toggleable by a hotkey.', epilog='Any unrecognized arguments are passed as is to the terminal emulator. To prevent flickering, please add an i3 rule to move created terminal windows to the scratchpad, for example: for_window [class="URxvt" title="The terminal"] move scratchpad', @@ -109,7 +111,11 @@ def get_args() -> tuple[argparse.Namespace, list[str]]: #endregion def main(config: argparse.Namespace, arguments_to_pass: list[str]): - + """ + Main entry point of the script. + Toggles the visibility of the terminal emulator window if it's present; + otherwise, creates a new one and shows it. + """ i3 = i3ipc.Connection() window_tag = generate_window_tag(config.name) @@ -135,7 +141,7 @@ def main(config: argparse.Namespace, arguments_to_pass: list[str]): else: term_by_name = None # wait for the terminal to appear for a second - for i in range(10): + for _ in range(10): time.sleep(0.1) term_by_name = i3.get_tree().find_titled(config.name) if term_by_name: @@ -151,16 +157,18 @@ def main(config: argparse.Namespace, arguments_to_pass: list[str]): term_by_name[0].command(f'mark {window_tag}') show(term_by_name[0], i3, config) +#region window manipulation code + def toggle(window: i3ipc.Con, i3: i3ipc.Connection, config: argparse.Namespace): """ - Toggles the terminal state, shows or hides it. - Can be configured to focus the terminal window first before closing on - a subsequent call. + Toggles the terminal visibility state. + Can be configured to focus the visible terminal window first + before closing on a subsequent call when it's focused. """ if in_scratchpad(window): show(window, i3, config) else: - if not window.focused and config.focus_first: + if config.focus_first and not window.focused: focus(window) else: hide(window) @@ -168,7 +176,7 @@ def toggle(window: i3ipc.Con, i3: i3ipc.Connection, config: argparse.Namespace): def show(window: i3ipc.Con, i3: i3ipc.Connection, config: argparse.Namespace): """ Calls internal methods required to calculate the terminal window position - and size and show it there + and size and show it there. """ output_position, output_size = get_output_properties(config.output, i3) output_width, output_height = output_size @@ -185,7 +193,10 @@ def show(window: i3ipc.Con, i3: i3ipc.Connection, config: argparse.Namespace): show_internal(window, window_position, window_size) def show_internal(window: i3ipc.Con, position: tuple[int, int], size: tuple[int, int]): - """Actually moves and resizes the window to dimensions specified""" + """ + Actually moves and resizes the window to dimensions specified. + Sticky mode is applied only if not already set. + """ x, y = position w, h = size sticky_command = '' if window.sticky else 'sticky enable,' @@ -203,7 +214,7 @@ def hide(window: i3ipc.Con): def get_output_properties(name: str, i3: i3ipc.Connection) -> tuple[tuple[int, int], tuple[int, int]]: """ Gets dimensions of a physical output by given name, or special 'main' value - that specifies the primary output, whatever its name is. + that specifies the primary output, whatever its actual name is. Returns output's dimesions as ((x position, y position), (width, height)) """ outputs = i3.get_outputs() @@ -256,6 +267,7 @@ def generate_window_tag(name: str) -> str: """ return '_bnqi3_' + name.lower().replace(' ', '_') +#endregion if __name__ == '__main__': main(*get_args()) From 3bac1719d5399cec53f58a7f30135ac0a116a9d3 Mon Sep 17 00:00:00 2001 From: Bn4 Date: Sat, 20 Jul 2024 22:11:02 +0700 Subject: [PATCH 3/3] version updated following the multiple sticky attributes fix i've also spent about an hour trying to fit typed enums instead of strings for position anchors; it worked, but was unnecessary complicated -- python isn't the language to use strict typing, no matter how much i would like to --- quake-terminal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quake-terminal.py b/quake-terminal.py index 1733ffe..041677c 100755 --- a/quake-terminal.py +++ b/quake-terminal.py @@ -23,7 +23,7 @@ #region configuration -version: Final = '2.0' +version: Final = '2.1' class Defaults(object): """