Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions libs/python/computer-server/computer_server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,17 @@

__version__: str = "0.1.0"

# Explicitly export Server for static type checkers
from .server import Server as Server # noqa: F401

__all__ = ["Server", "run_cli"]


def __getattr__(name: str):
if name == "Server":
from .server import Server

return Server
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")


def run_cli() -> None:
"""Entry point for CLI"""
from .cli import main
Expand Down
33 changes: 16 additions & 17 deletions libs/python/computer-server/computer_server/handlers/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,6 @@

OS_TYPE = get_current_os()

if OS_TYPE == "android":
from .android import (
AndroidAccessibilityHandler,
AndroidAutomationHandler,
AndroidDesktopHandler,
AndroidFileHandler,
AndroidWindowHandler,
)
elif OS_TYPE == "darwin":
from computer_server.diorama.macos import MacOSDioramaHandler

from .macos import MacOSAccessibilityHandler, MacOSAutomationHandler
elif OS_TYPE == "linux":
from .linux import LinuxAccessibilityHandler, LinuxAutomationHandler
elif OS_TYPE == "windows":
from .windows import WindowsAccessibilityHandler, WindowsAutomationHandler

from .generic import GenericDesktopHandler, GenericFileHandler, GenericWindowHandler


Expand Down Expand Up @@ -81,6 +64,14 @@ def create_handlers() -> Tuple[
GenericWindowHandler(),
)
elif OS_TYPE == "android":
from .android import (
AndroidAccessibilityHandler,
AndroidAutomationHandler,
AndroidDesktopHandler,
AndroidFileHandler,
AndroidWindowHandler,
)

return (
AndroidAccessibilityHandler(),
AndroidAutomationHandler(),
Expand All @@ -90,6 +81,10 @@ def create_handlers() -> Tuple[
AndroidWindowHandler(),
)
elif OS_TYPE == "darwin":
from computer_server.diorama.macos import MacOSDioramaHandler

from .macos import MacOSAccessibilityHandler, MacOSAutomationHandler

return (
MacOSAccessibilityHandler(),
MacOSAutomationHandler(),
Expand All @@ -99,6 +94,8 @@ def create_handlers() -> Tuple[
GenericWindowHandler(),
)
elif OS_TYPE == "linux":
from .linux import LinuxAccessibilityHandler, LinuxAutomationHandler

return (
LinuxAccessibilityHandler(),
LinuxAutomationHandler(),
Expand All @@ -108,6 +105,8 @@ def create_handlers() -> Tuple[
GenericWindowHandler(),
)
elif OS_TYPE == "windows":
from .windows import WindowsAccessibilityHandler, WindowsAutomationHandler

return (
WindowsAccessibilityHandler(),
WindowsAutomationHandler(),
Expand Down
28 changes: 1 addition & 27 deletions libs/python/computer-server/computer_server/handlers/macos.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from ApplicationServices import kAXValueCGSizeType # type: ignore
from ApplicationServices import kAXVisibleChildrenAttribute # type: ignore
from ApplicationServices import kAXWindowsAttribute # type: ignore
from PIL import Image, ImageGrab
from pynput.keyboard import Controller as KeyboardController
from pynput.keyboard import Key
from pynput.mouse import Button
Expand All @@ -57,33 +58,6 @@

logger = logging.getLogger(__name__)

# Trigger accessibility permissions prompt on macOS
try:
# Source - https://stackoverflow.com/a/17134
# Posted by Andreas
# Retrieved 2025-12-03, License - CC BY-SA 4.0
# Attempt to create and post a mouse event to trigger the permissions prompt
# This will cause macOS to show "Python would like to control this computer using accessibility features"
current_pos = CGEventGetLocation(CGEventCreate(None))
p = CGPoint()
p.x = current_pos.x
p.y = current_pos.y

me = CGEventCreateMouseEvent(None, kCGEventMouseMoved, p, 0)
if me:
CGEventPost(kCGHIDEventTap, me)
CFRelease(me)
except Exception as e:
logger.debug(f"Failed to trigger accessibility permissions prompt: {e}")

# Trigger screen recording prompt on macOS
try:
from PIL import Image, ImageGrab

ImageGrab.grab()
except Exception as e:
logger.debug(f"Failed to trigger screenshot permissions prompt: {e}")


# Fix: pynput's MouseController.position setter uses CGEventPost with
# kCGEventMouseMoved internally, which silently fails in macOS VMs running
Expand Down
39 changes: 39 additions & 0 deletions libs/python/computer-server/tests/test_import_safety.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import os
import platform
import subprocess
import sys

import pytest


def _assert_import_exits_cleanly(import_statement: str):
env = os.environ.copy()
for key in ("CUA_BACKEND", "CUA_VNC_HOST", "CUA_VNC_PORT", "CUA_VNC_PASSWORD"):
env.pop(key, None)

result = subprocess.run(
[sys.executable, "-X", "faulthandler", "-c", f"{import_statement}; print('ok')"],
capture_output=True,
env=env,
text=True,
timeout=20,
)
Comment on lines +2 to +20

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Isolate subprocess env to prevent flaky import-safety tests.

Line 9 currently inherits the parent environment, so pre-set CUA_BACKEND / CUA_VNC_* can alter HandlerFactory.create_handlers() and fail this test for non-import-safety reasons.

🔧 Suggested fix
+import os
 import platform
 import subprocess
 import sys
@@
 def _assert_import_exits_cleanly(import_statement: str):
+    env = os.environ.copy()
+    for key in ("CUA_BACKEND", "CUA_VNC_HOST", "CUA_VNC_PORT", "CUA_VNC_PASSWORD"):
+        env.pop(key, None)
+
     result = subprocess.run(
         [sys.executable, "-X", "faulthandler", "-c", f"{import_statement}; print('ok')"],
         capture_output=True,
         text=True,
         timeout=20,
+        env=env,
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/python/computer-server/tests/test_import_safety.py` around lines 1 - 14,
The test helper _assert_import_exits_cleanly inherits the parent environment
causing pre-set CUA_* variables to affect imports; fix it by creating a
sanitized env for subprocess.run (import os), e.g. copy os.environ then remove
any keys that start with "CUA_" (or explicitly pop "CUA_BACKEND" and
"CUA_VNC_*"), and pass that filtered env via the env= parameter to
subprocess.run so the child process runs in an isolated environment.


assert result.returncode == 0, result.stdout + result.stderr
assert "ok" in result.stdout


def test_package_import_does_not_import_native_handlers_at_teardown():
_assert_import_exits_cleanly("import computer_server")


def test_generic_handler_import_does_not_import_native_handlers_at_teardown():
_assert_import_exits_cleanly("import computer_server.handlers.generic")


@pytest.mark.skipif(platform.system() != "Darwin", reason="macOS handler requires AppKit")
def test_macos_handler_creation_exits_cleanly_at_teardown():
_assert_import_exits_cleanly(
"from computer_server.handlers.factory import HandlerFactory; "
"HandlerFactory.create_handlers()"
)
Loading