251 lines
9.1 KiB
Python
251 lines
9.1 KiB
Python
import abc
|
|
import collections.abc
|
|
import logging
|
|
import traceback
|
|
from typing import Optional, Protocol
|
|
|
|
import i3ipc
|
|
|
|
import common
|
|
import transformations
|
|
|
|
|
|
class Layout(abc.ABC):
|
|
i3: i3ipc.Connection
|
|
workspace_id: int
|
|
n_masters: int
|
|
active_transformations: set[transformations.Transformation]
|
|
|
|
@abc.abstractmethod
|
|
def layout(self, i3: i3ipc.Connection, event: Optional[i3ipc.Event]) -> None:
|
|
pass
|
|
|
|
def __init__(self,
|
|
workspace_id: int,
|
|
n_masters: int = 1,
|
|
transforms: collections.abc.Set[transformations.Transformation] = frozenset()):
|
|
self.workspace_id = workspace_id
|
|
self.n_masters = n_masters
|
|
self.active_transformations = set(transforms)
|
|
self.old_workspace: i3ipc.Con = None
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{type(self).__name__}({self.workspace_id}, {self.n_masters})"
|
|
|
|
def increment_masters(self) -> int:
|
|
self.n_masters += 1
|
|
logging.debug(f"Incremented n_masters for workspace {self.workspace_id} to {self.n_masters}.")
|
|
return self.n_masters
|
|
|
|
def decrement_masters(self) -> int:
|
|
self.n_masters = max(self.n_masters - 1, 1)
|
|
logging.debug(f"Decremented n_masters for workspace {self.workspace_id} to {self.n_masters}.")
|
|
return self.n_masters
|
|
|
|
def move(self, i3: i3ipc.Connection, direction: str) -> None:
|
|
focused_window = common.get_focused_window(i3)
|
|
i3.command(f"focus {direction}")
|
|
new_window = common.get_focused_window(i3)
|
|
focused_window.command(f"swap container with con_id {new_window.id}")
|
|
focused_window.command("focus")
|
|
|
|
def workspace(self, i3: i3ipc.Connection) -> i3ipc.Con:
|
|
return i3.get_tree().find_by_id(self.workspace_id)
|
|
|
|
def transform_command(self, command: str) -> str:
|
|
if transformations.Transformation.TRANSPOSE in self.active_transformations:
|
|
command = transformations.transpose_command_transformation(command)
|
|
|
|
if transformations.Transformation.REFLECTX in self.active_transformations:
|
|
command = transformations.reflectx_command_transformation(command)
|
|
|
|
if transformations.Transformation.REFLECTY in self.active_transformations:
|
|
command = transformations.reflecty_command_transformation(command)
|
|
|
|
return command
|
|
|
|
def refetch_container(self, i3: i3ipc.Connection) -> None:
|
|
self.old_workspace = common.refetch_container(i3, self.old_workspace)
|
|
|
|
|
|
class LayoutConstructionProtocol(Protocol):
|
|
def __call__(self,
|
|
workspace_id: int,
|
|
n_masters: int = ...,
|
|
transforms: collections.abc.Set[transformations.Transformation] = ...) -> Layout: ...
|
|
|
|
|
|
LAYOUTS: dict[str, LayoutConstructionProtocol] = {}
|
|
|
|
WORKSPACE_LAYOUTS: dict[str, Layout] = {}
|
|
|
|
DEFAULT_LAYOUT = "tall"
|
|
|
|
|
|
def get_layout(workspace: i3ipc.Con) -> Layout:
|
|
if workspace.id not in WORKSPACE_LAYOUTS:
|
|
WORKSPACE_LAYOUTS[workspace.id] = LAYOUTS[DEFAULT_LAYOUT](workspace_id=workspace.id)
|
|
logging.debug(
|
|
f"Workspace {workspace.id} has no layout, setting default {WORKSPACE_LAYOUTS[workspace.id]}.")
|
|
workspace_layout = WORKSPACE_LAYOUTS[workspace.id]
|
|
logging.debug(f"Retreived workspace layout {workspace_layout} for workspace {workspace.id}.")
|
|
return workspace_layout
|
|
|
|
|
|
def set_layout(i3: i3ipc.Connection,
|
|
event: i3ipc.Event,
|
|
layout: str) -> None:
|
|
workspace = common.get_focused_workspace(i3)
|
|
current_layout = get_layout(workspace)
|
|
WORKSPACE_LAYOUTS[workspace.id] = LAYOUTS[layout](
|
|
workspace_id=workspace.id,
|
|
n_masters=current_layout.n_masters,
|
|
transforms=current_layout.active_transformations)
|
|
logging.debug(f"Changing layout of workspace {workspace.id} from {current_layout} to {layout} .")
|
|
i3.command("mode default")
|
|
|
|
workspace_layout = get_layout(workspace)
|
|
workspace_layout.layout(i3, event)
|
|
|
|
|
|
def layout_dispatcher(i3: i3ipc.Connection, event: i3ipc.Event) -> None:
|
|
try:
|
|
logging.debug(f"Received layout event: {event.ipc_data}")
|
|
workspace = (common.get_workspace_of_event(i3, event) or
|
|
common.get_focused_workspace(i3))
|
|
if workspace is None:
|
|
logging.debug("Event had no associated workspace and there is no focused workpace. Returning.")
|
|
return
|
|
|
|
logging.debug(f"Applying to workspace {workspace.id}.")
|
|
layout = get_layout(workspace)
|
|
i3.enable_command_buffering()
|
|
layout.layout(i3, event)
|
|
i3.disable_command_buffering()
|
|
except Exception as ex:
|
|
traceback.print_exc()
|
|
|
|
|
|
def increment_masters_dispatcher(i3: i3ipc.Connection, event: i3ipc.Event) -> None:
|
|
workspace = common.get_focused_workspace(i3)
|
|
if workspace is None:
|
|
logging.debug("Event had no associated workspace and there is no focused workpace. Returning.")
|
|
return
|
|
|
|
logging.debug(f"Applying to workspace {workspace.id}.")
|
|
layout = get_layout(workspace)
|
|
layout.increment_masters()
|
|
layout.layout(i3, None)
|
|
|
|
|
|
def decrement_masters_dispatcher(i3: i3ipc.Connection, event: i3ipc.Event) -> None:
|
|
workspace = common.get_focused_workspace(i3)
|
|
if workspace is None:
|
|
logging.debug("Event had no associated workspace and there is no focused workpace. Returning.")
|
|
return
|
|
|
|
logging.debug(f"Applying to workspace {workspace.id}.")
|
|
layout = get_layout(workspace)
|
|
layout.decrement_masters()
|
|
layout.layout(i3, None)
|
|
|
|
|
|
def move_dispatcher(i3: i3ipc.Connection, event: i3ipc.Event, direction: str) -> None:
|
|
workspace = common.get_focused_workspace(i3)
|
|
if workspace is None:
|
|
logging.debug("Event had no associated workspace and there is no focused workpace. Returning.")
|
|
return
|
|
|
|
logging.debug(f"Applying to workspace {workspace.id}.")
|
|
layout = get_layout(workspace)
|
|
layout.move(i3, direction)
|
|
|
|
|
|
def transformation_dispatcher(i3: i3ipc.Connection, event: i3ipc.Event,
|
|
transformation: transformations.Transformation) -> None:
|
|
workspace = common.get_focused_workspace(i3)
|
|
if workspace is None:
|
|
logging.debug("Event had no associated workspace and there is no focused workpace. Returning.")
|
|
return
|
|
|
|
logging.debug(f"Applying to workspace {workspace.id}.")
|
|
layout = get_layout(workspace)
|
|
if transformation in layout.active_transformations:
|
|
logging.debug(f"Removing transformation {transformation} from workspace {workspace.id}.")
|
|
layout.active_transformations.remove(transformation)
|
|
else:
|
|
logging.debug(f"Adding transformation {transformation} to workspace {workspace.id}.")
|
|
layout.active_transformations.add(transformation)
|
|
logging.debug(f"Workspace {workspace.id} now has transformations {layout.active_transformations}.")
|
|
globals()[transformation.value.lower()](i3, event)
|
|
layout.layout(i3, None)
|
|
|
|
|
|
def transpose_dispatcher(i3: i3ipc.Connection, event: i3ipc.Event) -> None:
|
|
transformation_dispatcher(i3, event, transformations.Transformation.TRANSPOSE)
|
|
|
|
|
|
def reflectx_dispatcher(i3: i3ipc.Connection, event: i3ipc.Event) -> None:
|
|
transformation_dispatcher(i3, event, transformations.Transformation.REFLECTX)
|
|
|
|
|
|
def reflecty_dispatcher(i3: i3ipc.Connection, event: i3ipc.Event) -> None:
|
|
transformation_dispatcher(i3, event, transformations.Transformation.REFLECTY)
|
|
|
|
|
|
# def collapse_splits(workspace: i3ipc.Con) -> None:
|
|
# if len(workspace.nodes) == 1 and len(workspace.nodes[0].nodes) == 1:
|
|
# child = workspace.nodes[0].nodes[0]
|
|
# child.command("split none")
|
|
|
|
|
|
def relayout_old_workspace(i3: i3ipc.Connection, new_workspace: i3ipc.Con) -> None:
|
|
old_workspace = common.get_focused_workspace(i3)
|
|
logging.debug(f"Detected container move from workspace {old_workspace.id} to {new_workspace.id}.")
|
|
|
|
# Necessary for move left/right between outputs.
|
|
if old_workspace.id == new_workspace.id:
|
|
i3.command("workspace back_and_forth")
|
|
old_workspace = common.get_focused_workspace(i3)
|
|
i3.command("workspace back_and_forth")
|
|
logging.debug(f"Old and new workspaces were identical, actual old workspace is {old_workspace.id}.")
|
|
|
|
old_workspace_layout = get_layout(old_workspace)
|
|
old_workspace_layout.layout(i3, None)
|
|
|
|
|
|
def transpose(i3: i3ipc.Connection, event: i3ipc.Event) -> None:
|
|
workspace = common.get_focused_workspace(i3)
|
|
layout = get_layout(workspace)
|
|
orig_transformations = layout.active_transformations
|
|
if transformations.Transformation.REFLECTX in orig_transformations:
|
|
reflectx(i3, event)
|
|
if transformations.Transformation.REFLECTY in orig_transformations:
|
|
reflecty(i3, event)
|
|
|
|
transformations.transpose_container(i3, common.get_focused_workspace(i3))
|
|
|
|
if transformations.Transformation.REFLECTX in orig_transformations:
|
|
reflectx(i3, event)
|
|
if transformations.Transformation.REFLECTY in orig_transformations:
|
|
reflecty(i3, event)
|
|
|
|
|
|
def reflectx(i3: i3ipc.Connection, event: i3ipc.Event) -> None:
|
|
transformations.reflect_container(i3, common.get_focused_workspace(i3), {"splith"})
|
|
|
|
|
|
def reflecty(i3: i3ipc.Connection, event: i3ipc.Event) -> None:
|
|
transformations.reflect_container(i3, common.get_focused_workspace(i3), {"splitv"})
|
|
|
|
|
|
def fullscreen_dispatcher(i3: i3ipc.Connection, event: i3ipc.Event) -> None:
|
|
workspace = common.get_focused_workspace(i3)
|
|
if workspace is None:
|
|
logging.debug("Event had no associated workspace and there is no focused workpace. Returning.")
|
|
return
|
|
|
|
logging.debug(f"Applying to workspace {workspace.id}.")
|
|
i3.command("fullscreen")
|
|
layout = get_layout(workspace)
|
|
layout.refetch_container(i3)
|