diff --git a/VoidShell/entrypoints/app.py b/VoidShell/entrypoints/app.py new file mode 100644 index 0000000..ecd42f5 --- /dev/null +++ b/VoidShell/entrypoints/app.py @@ -0,0 +1,5 @@ +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import GLib, Gtk + + diff --git a/VoidShell/services/sway/sway.py b/VoidShell/services/sway/sway.py new file mode 100644 index 0000000..c54817c --- /dev/null +++ b/VoidShell/services/sway/sway.py @@ -0,0 +1,118 @@ +from gi.repository import Gtk, Gdk, GLib, GObject +from i3ipc.aio import Connection +from i3ipc.events import WorkspaceEvent, OutputEvent, ModeEvent +from i3ipc import Event +import asyncio +from typing import Optional + +class GWorkspaceEvent(GObject.Object): + + def __init__(self, event: WorkspaceEvent): + super().__init__() + self.event = event + + def __getattr__(self, name): + return getattr(self.event, name) + + def __str__(self): + return str(self.event) + +class GOutputEvent(GObject.Object): + + def __init__(self, event: OutputEvent): + super().__init__() + self.event = event + + def __getattr__(self, name): + return getattr(self.event, name) + + def __str__(self): + return str(self.event) + +class GModeEvent(GObject.Object): + + def __init__(self, event: ModeEvent): + super().__init__() + self.event = event + + def __getattr__(self, name): + return getattr(self.event, name) + + def __str__(self): + return str(self.event) + +def async_debounce(wait): + def decorator(func): + task: asyncio.Task | None = None + + @wraps(func) + async def debounced(*args, **kwargs): + nonlocal task + if task and not task.done(): + return + + async def call_func(): + await asyncio.sleep(wait) + await func(*args, **kwargs) + + task = asyncio.create_task(call_func()) + return task + + return debounced + + return decorator + +class SwayIPC(GObject.Object, Connection): + + __instance: Optional["SwayIPC"] = None + + def __init__(self): + if SwayIPC.__instance is not None: + raise Exception("SwayIPC is a singleton") + super().__init__() + super(Connection, self).__init__(auto_reconnect=True) + SwayIPC.__instance = self + # Run async initialization + loop = asyncio.get_event_loop() + loop.create_task(self._init()) + + @staticmethod + def get_instance(): + if SwayIPC.__instance is None: + SwayIPC() + return SwayIPC.__instance + + async def _init(self): + await self.connect() + self.on(Event.WORKSPACE, self._on_workspace) + self.on(Event.OUTPUT, self._on_output) + self.on(Event.MODE, self._on_mode) + self.on(Event.SHUTDOWN, self._on_shutdown) + + async def _on_workspace(self, event): + self.emit('workspace', GWorkspaceEvent(event)) + + @async_debounce(0.1) + async def _on_output(self, event): + self.emit('output', GOutputEvent(event)) + + async def _on_mode(self, event): + self.emit('mode', GModeEvent(event)) + + async def _on_shutdown(self, event): + # exit the application + Gtk.main_quit() + + @GObject.Signal(arg_types=(GWorkspaceEvent,)) + def workspace(self, event: GWorkspaceEvent): + pass + + @GObject.Signal(arg_types=(GOutputEvent,)) + def output(self, event: GOutputEvent): + pass + + @GObject.Signal(arg_types=(GModeEvent,)) + def mode(self, event: GModeEvent): + pass + + diff --git a/VoidShell/services/sway/tree.py b/VoidShell/services/sway/tree.py new file mode 100644 index 0000000..061b31b --- /dev/null +++ b/VoidShell/services/sway/tree.py @@ -0,0 +1,539 @@ +from typing import Any +import json +from i3ipc.replies import OutputReply, WorkspaceReply +from i3ipc.aio import Connection +import asyncio +import subprocess +from .utils import OutputMatch +from .sway import SwayIPC +from gi.repository import GObject + + +class Workspace(GObject.Object): + """A class representing a Sway workspace in the format we use. + + Attributes: + index: The index of the workspace, as a string. This is the Sway workspace name, and is never displayed to the user. + name: The name of the workspace, as a string. This is the name that is displayed to the user, as defined in `workspaces.json`. + """ + + _index: int = None + _name: str = None + + _active: bool = False + _visible: bool = False + _focused: bool = False + _alerted: bool = False + + @GObject.Property + def index(self) -> int: + return self._index + + @GObject.Property + def name(self) -> str: + return self._name + + @GObject.Property + def active(self) -> bool: + return self._active + + @GObject.Property + def visible(self) -> bool: + return self._visible + + @GObject.Property + def focused(self) -> bool: + return self._focused + + @GObject.Property + def alerted(self) -> bool: + return self._alerted + + def __init__(self, dictionary: dict[str, str | int | dict[str, str]]): + super().__init__() + self.index = dictionary.get("index") + self.name = dictionary.get("name") + + self.definition = dictionary + + def update_state(self, state_update: Any): + self._active = True + self._visible = state_update.visible + self._focused = state_update.focused + self._alerted = state_update.urgent + + self.notify("active") + self.notify("visible") + self.notify("focused") + self.notify("alerted") + + def deactivate(self): + self.active = False + self.visible = False + self.focused = False + self.alerted = False + + self.notify("active") + self.notify("visible") + self.notify("focused") + self.notify("alerted") + + def __repr__(self): + return f"Workspace({self.index}, '{self.name}')" + + async def focus(self): + """Focus the workspace in Sway.""" + sway = SwayIPC.get_instance() + await sway.command(f"workspace {self.index}") + + async def move_container(self): + """Move the focused container to the workspace.""" + sway = SwayIPC.get_instance() + await sway.command(f"move container to workspace {self.index}") + + async def reassign(self, output: str): + """Reassign the workspace to a different output.""" + sway = SwayIPC.get_instance() + await sway.command(f"workspace {self.index} output {output}") + + async def relocate(self, output: str): + """Move the workspace to a different output.""" + sway = SwayIPC.get_instance() + await sway.command(f"workspace {self.index}") + await sway.command(f"move workspace to output {output}") + + +class WorkspaceGroup(GObject.Object): + """A class representing a group of workspaces. Generally maps to a monitor.""" + + name: str = None + workspaces: list[Workspace] = None + reverse: bool = False + + def __init__(self, output_data: dict[str, str]): + super().__init__() + self.name = output_data["group"] + self.output_names = output_data.get("names", []) + self.make = output_data.get("make", None) + self.model = output_data.get("model", None) + self.serial = output_data.get("serial", None) + self.position = output_data.get("position", [0, 0]) + self.mode = output_data.get("mode", None) + self.transform = output_data.get("transform", None) + self.eww_windows = output_data.get("eww_windows", []) + self.workspaces = [] + + def add_workspace(self, workspace: Workspace): + """Add a workspace to the group.""" + self.workspaces.append(workspace) + + def __iter__(self): + return iter(self.workspaces) + + def __repr__(self): + return f"WorkspaceGroup({self.name}, {repr(self.workspaces)})" + + @property + def active_workspace(self) -> Workspace: + """Returns the active workspace in the group.""" + return next( + (workspace for workspace in self.workspaces if workspace.visible), + None, + ) + + @property + def active(self) -> bool: + """Returns whether the group is active.""" + return any(workspace.focused for workspace in self.workspaces) + + async def focus(self): + """Focus the group in Sway.""" + sway = SwayIPC.get_instance() + if self.make and self.model and self.serial: + await sway.command(f"focus output {self.make} {self.model} {self.serial}") + elif len(self.output_names) > 0: + for name in self.output_names: + await sway.command(f"focus output {name}") + else: + raise ValueError( + "No output name or make/model/serial provided, cannot focus group" + ) + + async def configure(self, outputs: list[OutputReply]): + """Configure the group output in Sway.""" + transform = "" + mode = "" + selector = "" + if self.transform: + transform = f"transform {self.transform}" + if self.mode: + mode = f"mode {self.mode}" + if self.make and self.model and self.serial: + selector = f'"{self.make} {self.model} {self.serial}"' + elif len(self.output_names) > 0: + for name in self.output_names: + if name in [output.name for output in outputs]: + selector = name + break + # Configure the output + await i3.command( + f"output {selector} position {self.position[0]} {self.position[1]} {mode} {transform} enable" + ) + + async def validate(self, i3: Connection, workspaces: list[WorkspaceReply]): + """Validate that each workspace in the group is assigned to and present on the correct output.""" + ouput_name = self.get_output_name(i3) + + for workspace in self.workspaces: + # Get output name for workspace + workspace_output = next( + ( + workspace_output + for workspace_output in workspaces + if workspace_output.name == workspace.index + ), + None, + ) + if workspace_output is None: + print( + f"Workspace {workspace.index} not found in workspaces, skipping validation", + flush=True, + ) + continue + if workspace_output.output != ouput_name: + print( + f"Workspace {workspace.index} is assigned to {workspace_output.output}, not {ouput_name}, reassigning", + flush=True, + ) + await workspace.relocate(i3, ouput_name) + + async def get_output_name(self, i3: Connection) -> str: + """Get the name of the output in Sway.""" + 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: + for output in outputs: + if ( + output.make == self.make + and output.model == self.model + and output.serial == self.serial + ): + print( + f"Found output {output.name} by make, model, and serial for group {self.name}", + flush=True, + ) + 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: + for output in outputs: + if output.name in self.output_names: + print( + f"Found output {output.name} by name for group {self.name}", + flush=True, + ) + return output.name + return None + + def get_match_level(self, output: OutputReply) -> OutputMatch: + """Get the match level score for the output.""" + if self.make and self.model and self.serial: + if ( + output.make == self.make + and output.model == self.model + and output.serial == self.serial + ): + print( + f"Match level: ID_MATCH for {output.name} on group {self.name}", + flush=True, + ) + return OutputMatch.ID_MATCH + if output.name in self.output_names: + print( + f"Match level: NAME_MATCH for {output.name} on group {self.name}", + 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 + + +class WorkspaceContext: + """A class representing a context, containing all workspaces and groups within the context.""" + + name: str = None + groups: list[WorkspaceGroup] = None + + def __init__( + self, + name: str, + data: dict[str, list[dict[str, list[int]]] | list[dict[str, str]]], + workspaces: list[Workspace], + ): + self.groups = [WorkspaceGroup(output) for output in data["outputs"]] + self.name = name + for group in self.groups: + group_object = data["groups"][group.name] + if group_object.get("reverse", False): + group.reverse = True + for workspace in group_object["workspaces"]: + workspace_obj = next( + (w for w in workspaces if w.index == workspace), + None, + ) + if workspace_obj: + group.add_workspace(workspace_obj) + else: + raise Exception( + f"Error: undefined workspace {workspace} referenced in context {name} group {group.name}" + ) + primary_group_name = data.get("primary", self.groups[0].name) + self.primary_group = next( + (group for group in self.groups if group.name == primary_group_name), + None, + ) + + def add_group(self, group: WorkspaceGroup): + """Add a group to the context.""" + self.groups.append(group) + + def compatability_rank(self, outputs: list[OutputReply]) -> int: + """Get the compatability rank of the context with the given outputs.""" + + result = 0 + for group in self.groups: + group_result = max( + [group.get_match_level(output).value for output in outputs] + ) + if group_result == 0: + return 0 + result += group_result + return result + + async def deactivate(self, i3: Connection): + """Deactivate the context in Sway.""" + # First, close all EWW windows + proc = await asyncio.create_subprocess_exec("eww", "close-all") + await proc.wait() + + # Then, disable all displays + await i3.command("output * disable") + + 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 + for group in self.groups: + await group.configure(i3, outputs) + + # Then, open all EWW windows defined in the context on the appropriate windows + proc = await asyncio.create_subprocess_exec("eww", "reload") + await proc.wait() + for group in self.groups: + for window in group.eww_windows: + proc = await asyncio.create_subprocess_exec( + "eww", + "open", + window, + "--screen", + await group.get_output_name(i3), + ) + await proc.wait() + + # Finally, validate workspaces and move them if necessary + for group in self.groups: + workspaces = await i3.get_workspaces() + await group.validate(i3, workspaces) + + @property + def active_group(self) -> WorkspaceGroup: + """Returns the active group in the context.""" + return next( + (group for group in self.groups if group.active), + None, + ) + + @property + def active_workspace(self) -> Workspace: + """Returns the active workspace in the context.""" + try: + return self.active_group.active_workspace + except AttributeError: + # A context may not have any active workspaces, in which case we return None + return None + + @property + def visible_workspaces(self) -> dict[str, Workspace]: + """Returns a dictionary of all visible workspaces in the context.""" + return {group.name: group.active_workspace for group in self.groups} + + def __iter__(self): + return iter(self.groups) + + def __repr__(self): + return f"WorkspaceContext({self.name}, {repr(self.groups)})" + + def __json__(self): + return {group.name: group.__json__() for group in self.groups} + + +class WorkspaceTree: + + current_context: WorkspaceContext = None + workspaces: list[Workspace] = None + + def __init__(self, filename: str): + self.load_file(filename) + + def load_file(self, filename: str): + with open(filename, "r") as file: + initial: dict = json.load(file) + + self.contexts: list[WorkspaceContext] = [] + self.workspaces = [Workspace(workspace) for workspace in initial["workspaces"]] + + for context, context_dict in initial["contexts"].items(): + context_obj = WorkspaceContext(context, context_dict, self.workspaces) + self.contexts.append(context_obj) + # Don't override the current context if it's already set + if ( + initial.get("default_context", "default") == context + and self.current_context is None + ): + self.current_context = context_obj + + def __dict__(self): + return { + "current_context": self.current_context, + "contexts": self.contexts, + } + + def __repr__(self): + return f"WorkspaceTree({self.current_context}, {repr(self.contexts)})" + + def __json__(self): + return {self.current_context.name: self.current_context.__json__()} + + def get_workspace(self, user_index: int) -> tuple[Workspace, WorkspaceGroup] | None: + """Returns a workspace object based on the user index.""" + if user_index < 1: + raise IndexError("Workspace index must be greater than or equal to 1.") + # First, find the active workspace + active_workspace = self.current_context.active_workspace + # Next, get its index within its group, and the active group index + active_index = ( + self.current_context.active_group.workspaces.index(active_workspace) + 1 + ) + group_index = self.current_context.groups.index( + self.current_context.active_group + ) + + # If the user index is the same as the active index, or is outside the range for this group, start searching other groups + if user_index == active_index or user_index > len( + self.current_context.active_group.workspaces + ): + counter = 0 + while counter < len(self.current_context.groups): + group = self.current_context.groups[ + (group_index + counter) % len(self.current_context.groups) + ] + if ( + user_index > len(group.workspaces) + or group == self.current_context.active_group + ): + # Group doesn't have enough workspaces, or is the active group + counter += 1 + + continue + return group.workspaces[user_index - 1], group + # If we didn't find a workspace, return None + return None + else: + # If the user index is different from the active index, return the workspace in the active group + return ( + self.current_context.active_group.workspaces[user_index - 1], + self.current_context.active_group, + ) + + def update_workspaces(self, ws_sway_dict: dict): + """Updates the workspaces in the tree based on the return from sway IPC.""" + touched = set() + + for index, ws_data in ws_sway_dict.items(): + # Udpate data for all active workspaces + ws_state: Workspace = next( + (ws for ws in self.workspaces if str(ws.index) == index), + None, + ) + if ws_state: + touched.add(ws_state) + ws_state.update_state(ws_data) + else: + print(f"Warning: workspace {index} not found in tree.") + + # Deactivate any workspaces that weren't touched + for ws in self.workspaces: + 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 + 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) + + # If the top context is the current context, or the rank is 0, do nothing + if scores[0][1] == 0: + print( + "No context is compatible with the current display configuration, doing nothing.", + flush=True, + ) + return + if self.current_context is None: + await scores[0][0].activate(i3) + self.current_context = scores[0][0] + return + elif scores[0][0] == self.current_context: + print("Context is already active.", flush=True) + elif match_context_on_name and scores[0][0].name == self.current_context.name: + print("Context is already active.", flush=True) + await scores[0][0].activate(i3) + self.current_context = scores[0][0] + else: + await self.current_context.deactivate(i3) + await scores[0][0].activate(i3) + self.current_context = scores[0][0] + + async def activate_context(self, i3: Connection, name: str): + """Activates a context by name. This will fail if the current display configuration is incompatible.""" + context = next( + (context for context in self.contexts if context.name == name), + None, + ) + + outputs = await i3.get_outputs() + + score = context.compatability_rank(outputs) + if score == 0: + raise ValueError( + "Context is incompatible with current display configuration." + ) + print(f"Activating context {context.name} with score {score}.", flush=True) + if self.current_context is not None and self.current_context != context: + await self.current_context.deactivate(i3) + self.current_context = context + + await context.activate(i3) diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..3db0981 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,541 @@ +# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand. + +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + +[[package]] +name = "asyncinotify" +version = "4.2.0" +description = "A simple optionally-async python inotify library, focused on simplicity of use and operation, and leveraging modern Python features" +optional = false +python-versions = "<4,>=3.6" +files = [ + {file = "asyncinotify-4.2.0-py3-none-any.whl", hash = "sha256:23cbcb0704cc65a2009d5ddc5a70dc5be6560708d8a684bba82e03e384c6295f"}, + {file = "asyncinotify-4.2.0.tar.gz", hash = "sha256:dac1d75e16a4919c6eab84a90ff51218db622c5524a84a5c501a0b62ea7ec7ea"}, +] + +[[package]] +name = "black" +version = "24.10.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.9" +files = [ + {file = "black-24.10.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6668650ea4b685440857138e5fe40cde4d652633b1bdffc62933d0db4ed9812"}, + {file = "black-24.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1c536fcf674217e87b8cc3657b81809d3c085d7bf3ef262ead700da345bfa6ea"}, + {file = "black-24.10.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:649fff99a20bd06c6f727d2a27f401331dc0cc861fb69cde910fe95b01b5928f"}, + {file = "black-24.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:fe4d6476887de70546212c99ac9bd803d90b42fc4767f058a0baa895013fbb3e"}, + {file = "black-24.10.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5a2221696a8224e335c28816a9d331a6c2ae15a2ee34ec857dcf3e45dbfa99ad"}, + {file = "black-24.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9da3333530dbcecc1be13e69c250ed8dfa67f43c4005fb537bb426e19200d50"}, + {file = "black-24.10.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4007b1393d902b48b36958a216c20c4482f601569d19ed1df294a496eb366392"}, + {file = "black-24.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:394d4ddc64782e51153eadcaaca95144ac4c35e27ef9b0a42e121ae7e57a9175"}, + {file = "black-24.10.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b5e39e0fae001df40f95bd8cc36b9165c5e2ea88900167bddf258bacef9bbdc3"}, + {file = "black-24.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d37d422772111794b26757c5b55a3eade028aa3fde43121ab7b673d050949d65"}, + {file = "black-24.10.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:14b3502784f09ce2443830e3133dacf2c0110d45191ed470ecb04d0f5f6fcb0f"}, + {file = "black-24.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:30d2c30dc5139211dda799758559d1b049f7f14c580c409d6ad925b74a4208a8"}, + {file = "black-24.10.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cbacacb19e922a1d75ef2b6ccaefcd6e93a2c05ede32f06a21386a04cedb981"}, + {file = "black-24.10.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1f93102e0c5bb3907451063e08b9876dbeac810e7da5a8bfb7aeb5a9ef89066b"}, + {file = "black-24.10.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ddacb691cdcdf77b96f549cf9591701d8db36b2f19519373d60d31746068dbf2"}, + {file = "black-24.10.0-cp313-cp313-win_amd64.whl", hash = "sha256:680359d932801c76d2e9c9068d05c6b107f2584b2a5b88831c83962eb9984c1b"}, + {file = "black-24.10.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:17374989640fbca88b6a448129cd1745c5eb8d9547b464f281b251dd00155ccd"}, + {file = "black-24.10.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:63f626344343083322233f175aaf372d326de8436f5928c042639a4afbbf1d3f"}, + {file = "black-24.10.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfa1d0cb6200857f1923b602f978386a3a2758a65b52e0950299ea014be6800"}, + {file = "black-24.10.0-cp39-cp39-win_amd64.whl", hash = "sha256:2cd9c95431d94adc56600710f8813ee27eea544dd118d45896bb734e9d7a0dc7"}, + {file = "black-24.10.0-py3-none-any.whl", hash = "sha256:3bb2b7a1f7b685f85b11fed1ef10f8a9148bceb49853e47a294a3dd963c1dd7d"}, + {file = "black-24.10.0.tar.gz", hash = "sha256:846ea64c97afe3bc677b761787993be4991810ecc7a4a937816dd6bddedc4875"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.10)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "executing" +version = "2.1.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.8" +files = [ + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + +[[package]] +name = "i3ipc" +version = "2.2.1" +description = "An improved Python library to control i3wm and sway" +optional = false +python-versions = ">=3.4.0" +files = [ + {file = "i3ipc-2.2.1-py3-none-any.whl", hash = "sha256:c0b898223d50d42c90c818deb5033d1304c582755547dee7d15df3e3781bc690"}, + {file = "i3ipc-2.2.1.tar.gz", hash = "sha256:e880d7d7147959ead5cb34764f08b97b41385b36eb8256e8af1ce163dbcccce8"}, +] + +[package.dependencies] +python-xlib = "*" + +[[package]] +name = "ipython" +version = "8.29.0" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.10" +files = [ + {file = "ipython-8.29.0-py3-none-any.whl", hash = "sha256:0188a1bd83267192123ccea7f4a8ed0a78910535dbaa3f37671dca76ebd429c8"}, + {file = "ipython-8.29.0.tar.gz", hash = "sha256:40b60e15b22591450eef73e40a027cf77bd652e757523eebc5bd7c7c498290eb"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5.13.0" + +[package.extras] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] +kernel = ["ipykernel"] +matplotlib = ["matplotlib"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] + +[[package]] +name = "jedi" +version = "0.19.2" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.2-py2.py3-none-any.whl", hash = "sha256:a8ef22bde8490f57fe5c7681a3c83cb58874daf72b4784de3cce5b6ef6edb5b9"}, + {file = "jedi-0.19.2.tar.gz", hash = "sha256:4770dc3de41bde3966b02eb84fbcf557fb33cce26ad23da12c742fb50ecb11f0"}, +] + +[package.dependencies] +parso = ">=0.8.4,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<9.0.0)"] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "netifaces" +version = "0.11.0" +description = "Portable network interface information." +optional = false +python-versions = "*" +files = [ + {file = "netifaces-0.11.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:eb4813b77d5df99903af4757ce980a98c4d702bbcb81f32a0b305a1537bdf0b1"}, + {file = "netifaces-0.11.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:5f9ca13babe4d845e400921973f6165a4c2f9f3379c7abfc7478160e25d196a4"}, + {file = "netifaces-0.11.0-cp27-cp27m-win32.whl", hash = "sha256:7dbb71ea26d304e78ccccf6faccef71bb27ea35e259fb883cfd7fd7b4f17ecb1"}, + {file = "netifaces-0.11.0-cp27-cp27m-win_amd64.whl", hash = "sha256:0f6133ac02521270d9f7c490f0c8c60638ff4aec8338efeff10a1b51506abe85"}, + {file = "netifaces-0.11.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:08e3f102a59f9eaef70948340aeb6c89bd09734e0dca0f3b82720305729f63ea"}, + {file = "netifaces-0.11.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c03fb2d4ef4e393f2e6ffc6376410a22a3544f164b336b3a355226653e5efd89"}, + {file = "netifaces-0.11.0-cp34-cp34m-win32.whl", hash = "sha256:73ff21559675150d31deea8f1f8d7e9a9a7e4688732a94d71327082f517fc6b4"}, + {file = "netifaces-0.11.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:815eafdf8b8f2e61370afc6add6194bd5a7252ae44c667e96c4c1ecf418811e4"}, + {file = "netifaces-0.11.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:50721858c935a76b83dd0dd1ab472cad0a3ef540a1408057624604002fcfb45b"}, + {file = "netifaces-0.11.0-cp35-cp35m-win32.whl", hash = "sha256:c9a3a47cd3aaeb71e93e681d9816c56406ed755b9442e981b07e3618fb71d2ac"}, + {file = "netifaces-0.11.0-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:aab1dbfdc55086c789f0eb37affccf47b895b98d490738b81f3b2360100426be"}, + {file = "netifaces-0.11.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c37a1ca83825bc6f54dddf5277e9c65dec2f1b4d0ba44b8fd42bc30c91aa6ea1"}, + {file = "netifaces-0.11.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:28f4bf3a1361ab3ed93c5ef360c8b7d4a4ae060176a3529e72e5e4ffc4afd8b0"}, + {file = "netifaces-0.11.0-cp36-cp36m-win32.whl", hash = "sha256:2650beee182fed66617e18474b943e72e52f10a24dc8cac1db36c41ee9c041b7"}, + {file = "netifaces-0.11.0-cp36-cp36m-win_amd64.whl", hash = "sha256:cb925e1ca024d6f9b4f9b01d83215fd00fe69d095d0255ff3f64bffda74025c8"}, + {file = "netifaces-0.11.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:84e4d2e6973eccc52778735befc01638498781ce0e39aa2044ccfd2385c03246"}, + {file = "netifaces-0.11.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18917fbbdcb2d4f897153c5ddbb56b31fa6dd7c3fa9608b7e3c3a663df8206b5"}, + {file = "netifaces-0.11.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:48324183af7f1bc44f5f197f3dad54a809ad1ef0c78baee2c88f16a5de02c4c9"}, + {file = "netifaces-0.11.0-cp37-cp37m-win32.whl", hash = "sha256:8f7da24eab0d4184715d96208b38d373fd15c37b0dafb74756c638bd619ba150"}, + {file = "netifaces-0.11.0-cp37-cp37m-win_amd64.whl", hash = "sha256:2479bb4bb50968089a7c045f24d120f37026d7e802ec134c4490eae994c729b5"}, + {file = "netifaces-0.11.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3ecb3f37c31d5d51d2a4d935cfa81c9bc956687c6f5237021b36d6fdc2815b2c"}, + {file = "netifaces-0.11.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:96c0fe9696398253f93482c84814f0e7290eee0bfec11563bd07d80d701280c3"}, + {file = "netifaces-0.11.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c92ff9ac7c2282009fe0dcb67ee3cd17978cffbe0c8f4b471c00fe4325c9b4d4"}, + {file = "netifaces-0.11.0-cp38-cp38-win32.whl", hash = "sha256:d07b01c51b0b6ceb0f09fc48ec58debd99d2c8430b09e56651addeaf5de48048"}, + {file = "netifaces-0.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:469fc61034f3daf095e02f9f1bbac07927b826c76b745207287bc594884cfd05"}, + {file = "netifaces-0.11.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5be83986100ed1fdfa78f11ccff9e4757297735ac17391b95e17e74335c2047d"}, + {file = "netifaces-0.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:54ff6624eb95b8a07e79aa8817288659af174e954cca24cdb0daeeddfc03c4ff"}, + {file = "netifaces-0.11.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:841aa21110a20dc1621e3dd9f922c64ca64dd1eb213c47267a2c324d823f6c8f"}, + {file = "netifaces-0.11.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e76c7f351e0444721e85f975ae92718e21c1f361bda946d60a214061de1f00a1"}, + {file = "netifaces-0.11.0.tar.gz", hash = "sha256:043a79146eb2907edf439899f262b3dfe41717d34124298ed281139a8b93ca32"}, +] + +[[package]] +name = "packaging" +version = "24.2" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"}, + {file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"}, +] + +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "platformdirs" +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.48" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "6.1.0" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "psutil-6.1.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff34df86226c0227c52f38b919213157588a678d049688eded74c76c8ba4a5d0"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:c0e0c00aa18ca2d3b2b991643b799a15fc8f0563d2ebb6040f64ce8dc027b942"}, + {file = "psutil-6.1.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:000d1d1ebd634b4efb383f4034437384e44a6d455260aaee2eca1e9c1b55f047"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5cd2bcdc75b452ba2e10f0e8ecc0b57b827dd5d7aaffbc6821b2a9a242823a76"}, + {file = "psutil-6.1.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:045f00a43c737f960d273a83973b2511430d61f283a44c96bf13a6e829ba8fdc"}, + {file = "psutil-6.1.0-cp27-none-win32.whl", hash = "sha256:9118f27452b70bb1d9ab3198c1f626c2499384935aaf55388211ad982611407e"}, + {file = "psutil-6.1.0-cp27-none-win_amd64.whl", hash = "sha256:a8506f6119cff7015678e2bce904a4da21025cc70ad283a53b099e7620061d85"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6e2dcd475ce8b80522e51d923d10c7871e45f20918e027ab682f94f1c6351688"}, + {file = "psutil-6.1.0-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:0895b8414afafc526712c498bd9de2b063deaac4021a3b3c34566283464aff8e"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9dcbfce5d89f1d1f2546a2090f4fcf87c7f669d1d90aacb7d7582addece9fb38"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:498c6979f9c6637ebc3a73b3f87f9eb1ec24e1ce53a7c5173b8508981614a90b"}, + {file = "psutil-6.1.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d905186d647b16755a800e7263d43df08b790d709d575105d419f8b6ef65423a"}, + {file = "psutil-6.1.0-cp36-cp36m-win32.whl", hash = "sha256:6d3fbbc8d23fcdcb500d2c9f94e07b1342df8ed71b948a2649b5cb060a7c94ca"}, + {file = "psutil-6.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:1209036fbd0421afde505a4879dee3b2fd7b1e14fee81c0069807adcbbcca747"}, + {file = "psutil-6.1.0-cp37-abi3-win32.whl", hash = "sha256:1ad45a1f5d0b608253b11508f80940985d1d0c8f6111b5cb637533a0e6ddc13e"}, + {file = "psutil-6.1.0-cp37-abi3-win_amd64.whl", hash = "sha256:a8fb3752b491d246034fa4d279ff076501588ce8cbcdbb62c32fd7a377d996be"}, + {file = "psutil-6.1.0.tar.gz", hash = "sha256:353815f59a7f64cdaca1c0307ee13558a0512f6db064e92fe833784f08539c7a"}, +] + +[package.extras] +dev = ["black", "check-manifest", "coverage", "packaging", "pylint", "pyperf", "pypinfo", "pytest-cov", "requests", "rstcheck", "ruff", "sphinx", "sphinx_rtd_theme", "toml-sort", "twine", "virtualenv", "wheel"] +test = ["pytest", "pytest-xdist", "setuptools"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pycairo" +version = "1.27.0" +description = "Python interface for cairo" +optional = false +python-versions = ">=3.9" +files = [ + {file = "pycairo-1.27.0-cp310-cp310-win32.whl", hash = "sha256:e20f431244634cf244ab6b4c3a2e540e65746eed1324573cf291981c3e65fc05"}, + {file = "pycairo-1.27.0-cp310-cp310-win_amd64.whl", hash = "sha256:03bf570e3919901572987bc69237b648fe0de242439980be3e606b396e3318c9"}, + {file = "pycairo-1.27.0-cp311-cp311-win32.whl", hash = "sha256:9a9b79f92a434dae65c34c830bb9abdbd92654195e73d52663cbe45af1ad14b2"}, + {file = "pycairo-1.27.0-cp311-cp311-win_amd64.whl", hash = "sha256:d40a6d80b15dacb3672dc454df4bc4ab3988c6b3f36353b24a255dc59a1c8aea"}, + {file = "pycairo-1.27.0-cp312-cp312-win32.whl", hash = "sha256:e2239b9bb6c05edae5f3be97128e85147a155465e644f4d98ea0ceac7afc04ee"}, + {file = "pycairo-1.27.0-cp312-cp312-win_amd64.whl", hash = "sha256:27cb4d3a80e3b9990af552818515a8e466e0317063a6e61585533f1a86f1b7d5"}, + {file = "pycairo-1.27.0-cp313-cp313-win32.whl", hash = "sha256:01505c138a313df2469f812405963532fc2511fb9bca9bdc8e0ab94c55d1ced8"}, + {file = "pycairo-1.27.0-cp313-cp313-win_amd64.whl", hash = "sha256:b0349d744c068b6644ae23da6ada111c8a8a7e323b56cbce3707cba5bdb474cc"}, + {file = "pycairo-1.27.0-cp39-cp39-win32.whl", hash = "sha256:f9ca8430751f1fdcd3f072377560c9e15608b9a42d61375469db853566993c9b"}, + {file = "pycairo-1.27.0-cp39-cp39-win_amd64.whl", hash = "sha256:1b1321652a6e27c4de3069709b1cae22aed2707fd8c5e889c04a95669228af2a"}, + {file = "pycairo-1.27.0.tar.gz", hash = "sha256:5cb21e7a00a2afcafea7f14390235be33497a2cce53a98a19389492a60628430"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pygobject" +version = "3.50.0" +description = "Python bindings for GObject Introspection" +optional = false +python-versions = "<4.0,>=3.9" +files = [ + {file = "pygobject-3.50.0.tar.gz", hash = "sha256:4500ad3dbf331773d8dedf7212544c999a76fc96b63a91b3dcac1e5925a1d103"}, +] + +[package.dependencies] +pycairo = ">=1.16" + +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + +[[package]] +name = "python-xlib" +version = "0.33" +description = "Python X Library" +optional = false +python-versions = "*" +files = [ + {file = "python-xlib-0.33.tar.gz", hash = "sha256:55af7906a2c75ce6cb280a584776080602444f75815a7aff4d287bb2d7018b32"}, + {file = "python_xlib-0.33-py2.py3-none-any.whl", hash = "sha256:c3534038d42e0df2f1392a1b30a15a4ff5fdc2b86cfa94f072bf11b10a164398"}, +] + +[package.dependencies] +six = ">=1.10.0" + +[[package]] +name = "sdbus" +version = "0.13.0" +description = "Modern Python D-Bus library. Based on sd-bus from libsystemd." +optional = false +python-versions = ">=3.7" +files = [ + {file = "sdbus-0.13.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcc88e70e76723234fd0987edccbcc3fe5f4e99245eb30a6b362ef7f882fd8a3"}, + {file = "sdbus-0.13.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:199a4c321fc5b4cded57a110f82625f6541909c40e733f574d12afc5ace149a7"}, + {file = "sdbus-0.13.0.tar.gz", hash = "sha256:801bd46608ee82614d42960c8ba8ae9300edb1bf5bbeb534bc8fd21f13d2c20e"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[metadata] +lock-version = "2.0" +python-versions = "^3.12" +content-hash = "cd01259969fc6940f43ef71ecdd04f6072c71e918c264b5b29a197198be92748" diff --git a/pyproject.toml b/pyproject.toml index a99f918..31dc340 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,6 +8,13 @@ readme = "README.md" [tool.poetry.dependencies] python = "^3.12" +i3ipc = "^2.2.1" +sdbus = "^0.13.0" +asyncinotify = "^4.2.0" +pygobject = "^3.50.0" +psutil = "^6.1.0" +netifaces = "^0.11.0" +python-dotenv = "^1.0.1" [tool.poetry.group.dev.dependencies] ipython = "^8.29.0"