Skip to content

Instantly share code, notes, and snippets.

@m-bartlett
Last active January 4, 2023 08:53
Show Gist options
  • Save m-bartlett/a6fd9756982527044353ac9a322e1b9d to your computer and use it in GitHub Desktop.
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.
#!/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