Skip to content

Commit

Permalink
Make it so root workspace layout mode is restored (#42)
Browse files Browse the repository at this point in the history
Make it so root workspace layout mode is restored

Previously the layout mode/direction of the root workspace
node was not saved/restored. Now this is done by saving the
whole workspace tree (instead of just the children of the
workspace node) and when the layout is restored the root
workspace node is set correctly and the child nodes are
extracted into a temporary file so that they can be passed
to append_layout on their own.

Closes #33
  • Loading branch information
JonnyHaystack committed Nov 2, 2019
1 parent 314959e commit af0ded2
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 120 deletions.
7 changes: 6 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,18 @@ dist: disco
services:
- xvfb
before_install:
- sudo apt-get install -y i3 i3status
- sudo /usr/lib/apt/apt-helper download-file https://debian.sur5r.net/i3/pool/main/s/sur5r-keyring/sur5r-keyring_2019.02.01_all.deb keyring.deb SHA256:176af52de1a976f103f9809920d80d02411ac5e763f695327de9fa6aff23f416
- sudo dpkg -i ./keyring.deb
- echo "deb https://debian.sur5r.net/i3/ $(grep '^DISTRIB_CODENAME=' /etc/lsb-release | cut -f2 -d=) universe" | sudo tee -a /etc/apt/sources.list.d/sur5r-i3.list
- sudo apt update
- sudo apt install -y i3 i3status wmctrl xdotool
install:
- pip install -r requirements.txt
before_script:
- i3 &
- sleep 3
script:
- i3 -v
- pytest -vv --cov=i3_resurrect tests/
- tox
after_success:
Expand Down
49 changes: 39 additions & 10 deletions i3_resurrect/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,26 +207,55 @@ def restore_layout(workspace, directory):
placeholder_window_ids = []
for (_, window) in util.windows_in_workspace(workspace):
pid = window.pid

# If window has no process, add it to list of placeholder windows.
if pid == 0:
# If window has no process, add it to list of placeholder windows.
placeholder_window_ids.append(int(window.id, 16))
continue

# Otherwise, add it to the list of regular windows.
window_ids.append(int(window.id, 16))
else:
# Otherwise, add it to the list of regular windows.
window_ids.append(int(window.id, 16))

# Unmap all windows in workspace.
# Unmap all non-placeholder windows in workspace.
for window_id in window_ids:
util.xdo_unmap_window(window_id)

# Remove any remaining placeholder windows in workspace.
# Remove any remaining placeholder windows in workspace so that we don't
# have duplicates.
for window_id in placeholder_window_ids:
util.xdo_kill_window(window_id)

# Read saved layout file.
layout_file = Path(directory) / f'workspace_{workspace}_layout.json'
with layout_file.open('r') as f:
layout = json.load(f)

# append_layout can only insert nodes so we must separately change the
# layout mode of the workspace node.
ws_layout_mode = layout.get('layout', 'default')
tree = i3.get_tree()
focused = tree.find_focused()
workspace_node = focused.workspace()
workspace_node.command(f'layout {ws_layout_mode}')

# We don't want to pass the whole layout file because we don't want to
# append a new workspace, but append_layout requires a file path so we must
# extract the part of the json that we want and store it in a temporary
# file.
restorable_layout = (
layout.get('nodes', []) + layout.get('floating_nodes', [])
)
restorable_layout_file = Path(
f'/tmp/i3-resurrect/workspace_{workspace}_layout.json'
)
# Create tempfile directory if non-existent.
restorable_layout_file.parent.mkdir(parents=True, exist_ok=True)
with restorable_layout_file.open('w') as f:
f.write(json.dumps(restorable_layout))

# Create fresh placeholder windows by appending layout to workspace.
layout_file = str(Path(directory) / f'workspace_{workspace}_layout.json')
i3.command(f'append_layout {layout_file}')
i3.command(f'append_layout {str(restorable_layout_file)}')

# Delete tempfile.
restorable_layout_file.unlink()

# Map all unmapped windows.
for window_id in window_ids:
Expand Down
2 changes: 1 addition & 1 deletion i3_resurrect/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def build_layout(tree, swallow):
JSON serialisable.
"""
processed = process_node(tree, swallow)
return processed.get('nodes', []) + processed.get('floating_nodes', [])
return processed


def process_node(original, swallow):
Expand Down
220 changes: 120 additions & 100 deletions tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,110 +288,130 @@ def test_build_layout(monkeypatch):
"floating": "auto_off",
"swallows": []
}
expected_tree = [
{
"border": "pixel",
"current_border_width": 2,
"floating": "auto_off",
"fullscreen_mode": 0,
"geometry": {
"x": 2049,
"y": 486,
"width": 553,
"height": 107
},
"layout": "splith",
"name": "Ario",
"orientation": "none",
"percent": 0.5,
"scratchpad_state": "none",
"type": "con",
"workspace_layout": "default",
"swallows": [
{
"class": "^Ario$",
"instance": "^ario$"
}
],
"sticky": False
expected_tree = {
"type": "workspace",
"orientation": "horizontal",
"scratchpad_state": "none",
"percent": None,
"layout": "splith",
"workspace_layout": "default",
"sticky": False,
"border": "normal",
"current_border_width": -1,
"floating": "auto_off",
"fullscreen_mode": 0,
"geometry": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
{
"border": "normal",
"current_border_width": -1,
"floating": "auto_off",
"fullscreen_mode": 0,
"geometry": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
"name": "8",
"nodes": [
{
"border": "pixel",
"current_border_width": 2,
"floating": "auto_off",
"fullscreen_mode": 0,
"geometry": {
"x": 2049,
"y": 486,
"width": 553,
"height": 107
},
"layout": "splith",
"name": "Ario",
"orientation": "none",
"percent": 0.5,
"scratchpad_state": "none",
"type": "con",
"workspace_layout": "default",
"swallows": [
{
"class": "^Ario$",
"instance": "^ario$"
}
],
"sticky": False
},
"layout": "splitv",
"name": None,
"orientation": "vertical",
"percent": 0.5,
"scratchpad_state": "none",
"sticky": False,
"type": "con",
"workspace_layout": "default",
"nodes": [
{
"border": "pixel",
"current_border_width": 2,
"floating": "auto_off",
"fullscreen_mode": 0,
"geometry": {
"x": 2331,
"y": 10,
"width": 941,
"height": 1024
},
"layout": "splith",
"name": "Faster Melee - Slippi (r18)",
"orientation": "none",
"percent": 0.5,
"scratchpad_state": "none",
"sticky": False,
"type": "con",
"workspace_layout": "default",
"swallows": [
{
"class": "^Dolphin\\-emu$",
"instance": "^dolphin\\-emu$",
"title": "^Faster\\ Melee\\ \\-\\ Slippi\\ \\(r18\\)$"
}
]
{
"border": "normal",
"current_border_width": -1,
"floating": "auto_off",
"fullscreen_mode": 0,
"geometry": {
"x": 0,
"y": 0,
"width": 0,
"height": 0
},
{
"border": "pixel",
"current_border_width": 2,
"floating": "auto_off",
"fullscreen_mode": 0,
"geometry": {
"x": 2542,
"y": 344,
"width": 518,
"height": 356
"layout": "splitv",
"name": None,
"orientation": "vertical",
"percent": 0.5,
"scratchpad_state": "none",
"sticky": False,
"type": "con",
"workspace_layout": "default",
"nodes": [
{
"border": "pixel",
"current_border_width": 2,
"floating": "auto_off",
"fullscreen_mode": 0,
"geometry": {
"x": 2331,
"y": 10,
"width": 941,
"height": 1024
},
"layout": "splith",
"name": "Faster Melee - Slippi (r18)",
"orientation": "none",
"percent": 0.5,
"scratchpad_state": "none",
"sticky": False,
"type": "con",
"workspace_layout": "default",
"swallows": [
{
"class": "^Dolphin\\-emu$",
"instance": "^dolphin\\-emu$",
"title": "^Faster\\ Melee\\ \\-\\ Slippi\\ \\(r18\\)$"
}
]
},
"layout": "splith",
"name": "Dolphin NetPlay Setup",
"orientation": "none",
"percent": 0.5,
"scratchpad_state": "none",
"sticky": False,
"type": "con",
"workspace_layout": "default",
"swallows": [
{
"class": "^Dolphin\\-emu$",
"instance": "^dolphin\\-emu$",
"title": "^Dolphin\\ NetPlay\\ Setup$"
}
]
}
]
}
]
{
"border": "pixel",
"current_border_width": 2,
"floating": "auto_off",
"fullscreen_mode": 0,
"geometry": {
"x": 2542,
"y": 344,
"width": 518,
"height": 356
},
"layout": "splith",
"name": "Dolphin NetPlay Setup",
"orientation": "none",
"percent": 0.5,
"scratchpad_state": "none",
"sticky": False,
"type": "con",
"workspace_layout": "default",
"swallows": [
{
"class": "^Dolphin\\-emu$",
"instance": "^dolphin\\-emu$",
"title": "^Dolphin\\ NetPlay\\ Setup$"
}
]
}
]
}
]
}
tree = util.build_layout(workspace_container, ['class', 'instance', 'title'])
assert tree == expected_tree

Expand Down
16 changes: 8 additions & 8 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ commands =
i3-resurrect save --help
i3-resurrect restore -h
i3-resurrect restore --help
i3-resurrect save -d /tmp/i3-resurrect
i3-resurrect save -d /tmp/i3-resurrect --swallow=class,instance,title
i3-resurrect save -d /tmp/i3-resurrect --swallow=class,instance,title --layout-only
i3-resurrect save -d /tmp/i3-resurrect --swallow=class,instance,title --programs-only
i3-resurrect restore -d /tmp/i3-resurrect --programs-only
i3-resurrect restore -d /tmp/i3-resurrect --layout-only
i3-resurrect save -d /tmp/i3-resurrect -w "2 " --swallow=class,instance,title
i3-resurrect restore -d /tmp/i3-resurrect -w "2 "
i3-resurrect save -d /tmp/i3-resurrect-saves
i3-resurrect save -d /tmp/i3-resurrect-saves --swallow=class,instance,title
i3-resurrect save -d /tmp/i3-resurrect-saves --swallow=class,instance,title --layout-only
i3-resurrect save -d /tmp/i3-resurrect-saves --swallow=class,instance,title --programs-only
i3-resurrect restore -d /tmp/i3-resurrect-saves --programs-only
i3-resurrect restore -d /tmp/i3-resurrect-saves --layout-only
i3-resurrect save -d /tmp/i3-resurrect-saves -w "2 " --swallow=class,instance,title
i3-resurrect restore -d /tmp/i3-resurrect-saves -w "2 "

0 comments on commit af0ded2

Please sign in to comment.