Last active
January 4, 2023 08:53
-
-
Save m-bartlett/a6fd9756982527044353ac9a322e1b9d to your computer and use it in GitHub Desktop.
i3wm native split container guake-like dropdown terminal. The container drops down in animated steps using i3 move commands. In theory this could be extended to make any application dropdown summonable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import i3ipc | |
from time import sleep | |
DROPDOWN_IDENTIFIER = "dropdown" | |
DROPDOWN_CHILD_IDENTIFIER = "dropdownchild" | |
INITIAL_TERMINAL_QUANTITY = 3 | |
TERMINAL_COMMAND = f"terminal -n {DROPDOWN_CHILD_IDENTIFIER}" | |
HEIGHT_PERCENTAGE = 39 | |
FRAME_STEP = 20 | |
FRAME_DELAY = 0.005 | |
def is_ancestor(ancestor, descendent): | |
ancestor_id = ancestor.id | |
parent = descendent.parent | |
while parent.type == 'con': | |
if parent.id == ancestor_id: return True | |
parent = parent.parent | |
return False | |
def summon(dropdown, focused): | |
workspace_rect = focused.workspace().rect | |
dropdown_height = workspace_rect.height*HEIGHT_PERCENTAGE//100 | |
dropdown_width = workspace_rect.width | |
final_y = dropdown_height + workspace_rect.y | |
move_cmd = f"move down {FRAME_STEP}px" | |
dropdown.command(f"""scratchpad show, | |
resize set {dropdown_width} px {dropdown_height} px, | |
move absolute position {workspace_rect.x}px {-(dropdown_height-1)}px""") | |
for i in range(1, final_y, FRAME_STEP): | |
dropdown.command(move_cmd) | |
sleep(FRAME_DELAY) | |
dropdown.command(f"move absolute position {workspace_rect.x}px {workspace_rect.y}px, focus child") | |
def vanish(dropdown): | |
dropdown_rect = dropdown.rect | |
final_y = dropdown_rect.y + dropdown_rect.height | |
move_cmd = f"move up {FRAME_STEP}px" | |
for i in range(0, final_y, FRAME_STEP): | |
dropdown.command(move_cmd) | |
sleep(FRAME_DELAY) | |
dropdown.command(f"move absolute position {dropdown_rect.x}px {-dropdown_rect.height}px") | |
sleep(0.15) | |
dropdown.command("move scratchpad") | |
i3 = i3ipc.Connection() | |
tree = i3.get_tree() | |
focused = tree.find_focused() | |
try: | |
dropdown = tree.find_marked(DROPDOWN_IDENTIFIER)[0] | |
if dropdown.workspace().id == focused.workspace().id: | |
if is_ancestor(dropdown, focused): | |
vanish(dropdown) | |
else: | |
dropdown.command('focus, focus child') | |
else: | |
summon(dropdown, focused) | |
except IndexError: | |
# If we make it here, no container had the special dropdown mark. | |
# i3 resets marks if it gets reloaded, so search for container whose instance name is identifier | |
try: | |
dropdown = tree.find_instanced(DROPDOWN_IDENTIFIER)[0].parent | |
dropdown.command(f'mark --add {DROPDOWN_IDENTIFIER}') | |
summon(dropdown, focused) | |
except IndexError: | |
# Last resort, spawn a new dropdown container with our terminals | |
import subprocess | |
import shlex | |
import json | |
import tempfile | |
def terminal(): # Launch a background terminal process | |
subprocess.Popen( shlex.split(TERMINAL_COMMAND), | |
shell=False, | |
stdin=None, | |
stdout=None, | |
stderr=None, | |
close_fds=True ) | |
terminal_placeholder = { | |
"border": "pixel", | |
"layout": "splith", | |
"current_border_width": 1, | |
"floating": "auto_off", | |
"percent": 0.30, | |
"swallows": [{ "instance": DROPDOWN_CHILD_IDENTIFIER }] | |
} | |
swallowable_layout = { | |
"border": "none", | |
"floating": "auto_off", | |
"layout": "splith", | |
"type": "con", | |
"name": DROPDOWN_IDENTIFIER, | |
"instance": DROPDOWN_IDENTIFIER, | |
"marks": [DROPDOWN_IDENTIFIER], | |
"nodes": [terminal_placeholder] * INITIAL_TERMINAL_QUANTITY | |
} | |
with tempfile.NamedTemporaryFile('w', delete=True) as f: | |
f.write(json.dumps(swallowable_layout)) | |
f.flush() | |
i3.command(f'append_layout {f.name}') | |
tree = i3.get_tree() | |
dropdown = tree.find_marked(DROPDOWN_IDENTIFIER)[0] | |
dropdown.command("move scratchpad") | |
for i in range(INITIAL_TERMINAL_QUANTITY): | |
terminal() | |
summon(dropdown, focused) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment