From c439ad41aaef7722f86fb4640da58e1eb7de72ca Mon Sep 17 00:00:00 2001 From: Ezri Brimhall Date: Wed, 5 Feb 2025 14:35:46 -0700 Subject: [PATCH] Improved output name selection and context scoring --- sway_context_manager/interface.py | 11 ++++++-- sway_context_manager/workspace_tree.py | 36 ++++++++++++++------------ 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/sway_context_manager/interface.py b/sway_context_manager/interface.py index 01a7eb1..d0ea29f 100644 --- a/sway_context_manager/interface.py +++ b/sway_context_manager/interface.py @@ -1,10 +1,8 @@ from sdbus import ( DbusInterfaceCommonAsync, dbus_method_async, - dbus_property_async, dbus_signal_async, ) -import os from .workspace_tree import WorkspaceTree from i3ipc.aio import Connection import json @@ -25,6 +23,15 @@ class ContextMngrInterface( self.workspace_tree = workspace_tree self.connection = connection + @dbus_method_async(input_signature="", result_signature="as") + async def get_available_contexts(self) -> list[str]: + """ + Request a list of contexts compatible with the current monitor configuration, sorted by score. + """ + scores = await self.workspace_tree.score_contexts(self.connection) + contexts = [score[0].name for score in scores if score[1] > 0] + return contexts + @dbus_method_async(input_signature="s", result_signature="s") async def request_context(self, context: str) -> str: """Request a context switch. This will fail if the current monitor configuration is not compatible with the requested context.""" diff --git a/sway_context_manager/workspace_tree.py b/sway_context_manager/workspace_tree.py index 91e3444..e029e58 100644 --- a/sway_context_manager/workspace_tree.py +++ b/sway_context_manager/workspace_tree.py @@ -3,8 +3,8 @@ import json from i3ipc.replies import OutputReply, WorkspaceReply from i3ipc.aio import Connection import asyncio -import subprocess from .utils import OutputMatch +import itertools DEFAULT_TERMINAL = "alacritty" @@ -90,6 +90,7 @@ class WorkspaceGroup: name: str = None workspaces: list[Workspace] = None reverse: bool = False + output_name: str = None def __init__(self, output_data: dict[str, str]): self.name = output_data["group"] @@ -156,10 +157,12 @@ class WorkspaceGroup: mode = f"mode {self.mode}" if self.make and self.model and self.serial: selector = f'"{self.make} {self.model} {self.serial}"' + await self.get_output_name(i3, force=True) elif len(self.output_names) > 0: for name in self.output_names: if name in [output.name for output in outputs]: selector = name + self.output_name = selector break # Configure the output await i3.command( @@ -193,8 +196,10 @@ class WorkspaceGroup: ) await workspace.relocate(i3, ouput_name) - async def get_output_name(self, i3: Connection) -> str: + async def get_output_name(self, i3: Connection, force: bool = False) -> str: """Get the name of the output in Sway.""" + if not force and self.output_name is not None: + return self.output_name outputs = await i3.get_outputs() # If we have make, model, and serial, search by those first if self.make and self.model and self.serial: @@ -208,6 +213,7 @@ class WorkspaceGroup: f"Found output {output.name} by make, model, and serial for group {self.name}", flush=True, ) + self.output_name = output.name return output.name # If we don't find an exact match for the output, search by name if we have any if len(self.output_names) > 0: @@ -217,6 +223,7 @@ class WorkspaceGroup: f"Found output {output.name} by name for group {self.name}", flush=True, ) + self.output_name = output.name return output.name return None @@ -239,9 +246,6 @@ class WorkspaceGroup: flush=True, ) return OutputMatch.NAME_MATCH - print( - f"Match level: NO_MATCH for {output.name} on group {self.name}", flush=True - ) return OutputMatch.NO_MATCH @@ -293,6 +297,7 @@ class WorkspaceContext: [group.get_match_level(output).value for output in outputs] ) if group_result == 0: + print(f"Context {self.name}: failed to match group {group.name}") return 0 result += group_result return result @@ -308,9 +313,6 @@ class WorkspaceContext: async def activate(self, i3: Connection): """Activate the context in Sway.""" - defined_displays = [ - f"{group.make} {group.model} {group.serial}" for group in self.groups - ] outputs = await i3.get_outputs() # Configure all displays defined in the context @@ -335,11 +337,13 @@ class WorkspaceContext: "eww", "open", window, + "--id", + f"{group.name}-{window}", "--screen", await group.get_output_name(i3), "--arg", f"group={group.name}", - *extra_args, + *list(itertools.chain(*extra_args)), ) await proc.wait() @@ -479,19 +483,19 @@ class WorkspaceTree: if ws not in touched: ws.deactivate() - async def update_context(self, i3: Connection, match_context_on_name: bool = False): - """Activates a new context in Sway based on the current display configuration.""" - # First, get the current display configuration + async def score_contexts(self, i3: Connection): outputs = await i3.get_outputs() - print(outputs, flush=True) - # Next, calculate match scores for each context scores = [ (context, context.compatability_rank(outputs)) for context in self.contexts ] - print([f"{context.name}: {score}" for context, score in scores], flush=True) - # Sort the scores by rank scores.sort(key=lambda x: x[1], reverse=True) + return scores + + async def update_context(self, i3: Connection, match_context_on_name: bool = False): + """Activates a new context in Sway based on the current display configuration.""" + # Get the scores + scores = await self.score_contexts(i3) # If the top context is the current context, or the rank is 0, do nothing if scores[0][1] == 0: