Skip to content

Commit

Permalink
move docs to interface & apply black
Browse files Browse the repository at this point in the history
  • Loading branch information
gu1p committed Sep 26, 2024
1 parent 96a7fbe commit 88174b3
Show file tree
Hide file tree
Showing 11 changed files with 331 additions and 156 deletions.
2 changes: 1 addition & 1 deletion examples/run_mosaic.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@

if __name__ == "__main__":
wm = WindowManager()
mosaic(wm)
mosaic(wm)
11 changes: 6 additions & 5 deletions janela/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
from .interfaces.interface import WindowManager as WM, Monitor, Window


__all__ = ['WindowManager', 'Monitor', 'Window', 'playground']
__all__ = ["WindowManager", "Monitor", "Window", "playground"]


def WindowManager() -> WM:
if platform.system().lower() == 'windows':
if platform.system().lower() == "windows":
raise NotImplementedError("Windows is not supported yet")

if platform.system().lower() == 'linux':
if platform.system().lower() == "linux":
xdotool_path = shutil.which("xdotool")
wmctrl_path = shutil.which("wmctrl")

Expand All @@ -22,7 +23,7 @@ def WindowManager() -> WM:

return WindowManagerImpl(xdotool_path, wmctrl_path)

if platform.system().lower() == 'darwin':
if platform.system().lower() == "darwin":
raise NotImplementedError("Mac is not supported yet")

raise NotImplementedError("Unsupported platform")
raise NotImplementedError("Unsupported platform")
Empty file added janela/_impl/mac.py
Empty file.
150 changes: 27 additions & 123 deletions janela/_impl/wmctrl_xdotool_xlib.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import List, Optional
import platform

if platform.platform().lower().startswith('linux'):
if platform.platform().lower().startswith("linux"):
from Xlib import display, X

from janela.interfaces.interface import WindowManager
Expand All @@ -23,11 +23,6 @@ def __init__(self, xdotool_path: str, wmctrl_path: str):
self.display = display.Display()

def get_monitors(self) -> List[Monitor]:
"""
Get a list of monitors available on the system.
:return: A list of Monitor objects.
"""
output = run_command(["xrandr", "--current"])
monitors = []
monitor_id = 0
Expand All @@ -36,35 +31,27 @@ def get_monitors(self) -> List[Monitor]:
parts = line.split()
name = parts[0]
# Extract geometry information
geometry_part = next((p for p in parts if '+' in p and 'x' in p), None)
geometry_part = next((p for p in parts if "+" in p and "x" in p), None)
if geometry_part:
width_height, x_pos, y_pos = geometry_part.split('+')
width, height = map(int, width_height.split('x'))
width_height, x_pos, y_pos = geometry_part.split("+")
width, height = map(int, width_height.split("x"))
x, y = int(x_pos), int(y_pos)
monitors.append(Monitor(monitor_id, name, x, y, width, height, self))
monitors.append(
Monitor(monitor_id, name, x, y, width, height, self)
)
monitor_id += 1
else:
logger.warning(f"Could not parse geometry for monitor {name}")
return monitors

def get_active_window_id(self) -> str:
"""
Get the ID of the currently active window.
:return: The window ID as a hexadecimal string.
"""
decimal_id = run_command([self.xdotool_path, "getactivewindow"]).strip()
if not decimal_id.isdigit():
logger.error(f"Invalid window ID returned by xdotool: {decimal_id}")
return ""
return f"0x{int(decimal_id):x}"

def list_windows(self) -> List[Window]:
"""
List all windows currently open on the system.
:return: A list of Window objects.
"""
active_window_id = self.get_active_window_id()
output = run_command([self.wmctrl_path, "-lG"])
windows = []
Expand All @@ -83,47 +70,31 @@ def list_windows(self) -> List[Window]:
window_id_hex = f"0x{int(window_id, 16):x}"
x, y, width, height = map(int, [x, y, width, height])
is_active = window_id_hex.lower() == active_window_id.lower()
windows.append(Window(window_id_hex, name, x, y, width, height, is_active, self))
windows.append(
Window(window_id_hex, name, x, y, width, height, is_active, self)
)
except ValueError as e:
logger.error(f"Error parsing window data: {e}")
continue
return windows

def get_monitor_for_window(self, window: Window) -> Optional[Monitor]:
"""
Get the monitor that the given window is on.
:param window: The window to check.
:return: The Monitor object or None if not found.
"""
monitors = self.get_monitors()
for monitor in monitors:
if monitor.contains(window.x, window.y):
return monitor
return None

def move_window_to_position(self, window: Window, x: int, y: int):
"""
Move the window to the specified position.
:param window: The window to move.
:param x: The x-coordinate.
:param y: The y-coordinate.
"""
result = run_command([self.wmctrl_path, "-ir", window.id, "-e", f"0,{x},{y},-1,-1"])
result = run_command(
[self.wmctrl_path, "-ir", window.id, "-e", f"0,{x},{y},-1,-1"]
)
if result is not None:
window.x, window.y = x, y
else:
logger.error(f"Failed to move window {window.name} to position ({x}, {y})")

def resize_window(self, window: Window, width: int, height: int):
"""
Resize the window to the specified dimensions.
:param window: The window to resize.
:param width: The new width.
:param height: The new height.
"""
# Unmaximize the window first
self.unmaximize_window(window)

Expand All @@ -140,24 +111,16 @@ def resize_window(self, window: Window, width: int, height: int):
if result is not None:
window.width, window.height = width, height
else:
logger.error(f"Failed to resize window {window.name} to ({width}, {height})")
logger.error(
f"Failed to resize window {window.name} to ({width}, {height})"
)

def minimize_window(self, window: Window):
"""
Minimize the window.
:param window: The window to minimize.
"""
result = run_command([self.xdotool_path, "windowminimize", window.id])
if result is None:
logger.error(f"Failed to minimize window {window.name}")

def maximize_window(self, window: Window):
"""
Maximize the window.
:param window: The window to maximize.
"""
result = run_command(
[
self.wmctrl_path,
Expand All @@ -176,12 +139,6 @@ def maximize_window(self, window: Window):
logger.error(f"Failed to maximize window {window.name}")

def move_to_monitor(self, window: Window, monitor: Monitor):
"""
Move the window to the specified monitor.
:param window: The window to move.
:param monitor: The target monitor.
"""
logger.debug(f"Attempting to move window {window.name} to monitor {monitor.id}")

# Check if the window is maximized
Expand Down Expand Up @@ -223,15 +180,6 @@ def move_to_monitor(self, window: Window, monitor: Monitor):
def verify_window_move(
self, window: Window, target_monitor: Monitor, expected_x: int, expected_y: int
) -> bool:
"""
Verify that the window has moved to the expected position.
:param window: The window to check.
:param target_monitor: The target monitor.
:param expected_x: The expected x-coordinate.
:param expected_y: The expected y-coordinate.
:return: True if the window is correctly positioned, False otherwise.
"""
updated_window = self.get_window_by_id(window.id)

if updated_window is None:
Expand Down Expand Up @@ -262,119 +210,75 @@ def verify_window_move(
return True

def get_window_by_id(self, window_id: str) -> Optional[Window]:
"""
Get a window by its ID.
:param window_id: The window ID.
:return: The Window object or None if not found.
"""
windows = self.list_windows()
for window in windows:
if window.id.lower() == window_id.lower():
return window
return None

def focus_window(self, window: Window):
"""
Focus on the specified window.
:param window: The window to focus.
"""
result = run_command([self.wmctrl_path, "-ia", window.id])
if result is not None:
window.is_active = True
else:
logger.error(f"Failed to focus window {window.name}")

def close_window(self, window: Window):
"""
Close the specified window.
:param window: The window to close.
"""
result = run_command([self.wmctrl_path, "-ic", window.id])
if result is None:
logger.error(f"Failed to close window {window.name}")

def list_monitors(self) -> List[Monitor]:
"""
List all monitors sorted by name.
:return: A sorted list of Monitor objects.
"""
return sorted(self.get_monitors(), key=lambda x: x.name)

def get_active_window(self) -> Optional[Window]:
"""
Get the currently active window.
:return: The active Window object or None if not found.
"""
active_id = self.get_active_window_id()
return self.get_window_by_id(active_id)

def verify_window_positions(self):
"""
Verify the positions of all windows on all monitors.
"""
logger.debug("Verifying window positions:")
for monitor in self.list_monitors():
logger.debug(f"Monitor {monitor.id} ({monitor.name}):")
windows = [window for window in self.list_windows() if self.get_monitor_for_window(window) == monitor]
windows = [
window
for window in self.list_windows()
if self.get_monitor_for_window(window) == monitor
]
for window in windows:
logger.debug(f" - {window.name} at ({window.x}, {window.y})")

def get_window_by_name(self, name: str) -> Optional[Window]:
"""
Get a window by its name.
:param name: The name to search for.
:return: The Window object or None if not found.
"""
for window in self.list_windows():
if name.lower() in window.name.lower():
return window
return None

def get_monitor_by_id(self, monitor_id: int) -> Optional[Monitor]:
"""
Get a monitor by its ID.
:param monitor_id: The monitor ID.
:return: The Monitor object or None if not found.
"""
for monitor in self.get_monitors():
if monitor.id == monitor_id:
return monitor
return None

def is_window_maximized(self, window: Window) -> bool:
"""
Check if the window is maximized.
:param window: The window to check.
:return: True if maximized, False otherwise.
"""
try:
win = self.display.create_resource_object("window", int(window.id, 16))
wm_state = win.get_full_property(
self.display.intern_atom("_NET_WM_STATE"), X.AnyPropertyType
)
if wm_state:
atoms = wm_state.value
maximized_vert = self.display.intern_atom("_NET_WM_STATE_MAXIMIZED_VERT")
maximized_horz = self.display.intern_atom("_NET_WM_STATE_MAXIMIZED_HORZ")
maximized_vert = self.display.intern_atom(
"_NET_WM_STATE_MAXIMIZED_VERT"
)
maximized_horz = self.display.intern_atom(
"_NET_WM_STATE_MAXIMIZED_HORZ"
)
return maximized_vert in atoms and maximized_horz in atoms
except Exception as e:
logger.error(f"Error checking if window {window.name} is maximized: {e}")
return False

def unmaximize_window(self, window: Window):
"""
Unmaximize the window.
:param window: The window to unmaximize.
"""
result = run_command(
[
self.wmctrl_path,
Expand Down
2 changes: 1 addition & 1 deletion janela/interfaces/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .models import Monitor, Window
from .interface import WindowManager

__all__ = ["Monitor", "Window", "WindowManager"]
__all__ = ["Monitor", "Window", "WindowManager"]
Loading

0 comments on commit 88174b3

Please sign in to comment.