cursor-desktop.py 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. """Supervise the Cursor desktop using the existing uid, workspace and CUA socket contract."""
  2. import os
  3. from pathlib import Path
  4. import signal
  5. import subprocess
  6. import sys
  7. import time
  8. children = []
  9. critical = []
  10. stopping = False
  11. def stop(*_):
  12. global stopping
  13. stopping = True
  14. def spawn(argv, user=True, required=True):
  15. child = subprocess.Popen(argv, user=1000 if user else None,
  16. group=1000 if user else None,
  17. extra_groups=[] if user else None)
  18. children.append(child)
  19. if required:
  20. critical.append(child)
  21. return child
  22. signal.signal(signal.SIGTERM, stop)
  23. signal.signal(signal.SIGINT, stop)
  24. try:
  25. # Run workspace setup as its owner; no extra DAC or CHOWN capability is needed.
  26. setup = """
  27. from pathlib import Path
  28. import os
  29. root = Path('/home/cua/workspace/.browser-profiles/google-chrome')
  30. root.mkdir(parents=True, exist_ok=True)
  31. for name in ['SingletonLock', 'SingletonSocket', 'SingletonCookie']:
  32. (root / name).unlink(missing_ok=True)
  33. config = Path('/home/cua/.config')
  34. config.mkdir(exist_ok=True)
  35. link = config / 'google-chrome'
  36. if not link.is_symlink() and not link.exists():
  37. link.symlink_to(root)
  38. launchers = config / 'plank/dock1/launchers'
  39. launchers.mkdir(parents=True, exist_ok=True)
  40. for name in ['google-chrome', 'xfce4-terminal', 'thunar']:
  41. (launchers / (name + '.dockitem')).write_text('[PlankDockItemPreferences]\\nLauncher=file:///usr/share/applications/' + name + '.desktop\\n')
  42. """
  43. subprocess.run(['python3', '-c', setup], user=1000, group=1000, extra_groups=[], check=True)
  44. for path in ['/tmp/.X1-lock', '/tmp/.X11-unix/X1', '/run/user/1000/openmausbot-cua.sock', '/run/user/1000/yaoyao-dbus']:
  45. subprocess.run(['rm', '-f', path], user=1000, group=1000, extra_groups=[], check=True)
  46. os.environ['DBUS_SESSION_BUS_ADDRESS'] = 'unix:path=/run/user/1000/yaoyao-dbus'
  47. spawn(['dbus-daemon', '--session', '--nofork', '--address=' + os.environ['DBUS_SESSION_BUS_ADDRESS']])
  48. spawn(['Xvfb', ':1', '-screen', '0', '1280x800x24', '-ac', '-nolisten', 'tcp', '-noreset'])
  49. for _ in range(100):
  50. if stopping or any(p.poll() is not None for p in children):
  51. raise RuntimeError('桌面启动已中断')
  52. if subprocess.run(['xset', 'q'], user=1000, group=1000, extra_groups=[], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0:
  53. break
  54. time.sleep(0.2)
  55. else:
  56. raise RuntimeError('X display :1 did not become ready')
  57. spawn(['xfwm4', '--compositor=off'])
  58. subprocess.run(['xsetroot', '-solid', '#202734'], user=1000, group=1000, extra_groups=[])
  59. spawn(['plank'], required=False)
  60. spawn(['/usr/local/libexec/openmausbot/cua-driver', 'serve', '--socket', '/run/user/1000/openmausbot-cua.sock', '--permission-mode', 'standard'])
  61. # A visible browser gives this minimal desktop a usable initial surface.
  62. spawn(['/usr/local/bin/yaoyao-browser', 'about:blank'], required=False)
  63. if os.environ.get('YAOYAO_COMPOSE_DESKTOP_ID'):
  64. spawn(['python3', '/usr/local/libexec/yaoyao/compose_bridge.py'], user=False)
  65. while not stopping:
  66. if any(p.poll() is not None for p in critical):
  67. raise RuntimeError('桌面进程已退出,等待重新启动')
  68. time.sleep(0.5)
  69. except Exception as error:
  70. print(error, file=sys.stderr, flush=True)
  71. sys.exit(1)
  72. finally:
  73. # Stop the guest session without giving root CAP_KILL in managed containers.
  74. subprocess.run(['python3', '-c', 'import os,signal; os.kill(-1,signal.SIGTERM)'], user=1000, group=1000, extra_groups=[])
  75. for child in children:
  76. try:
  77. child.wait(timeout=3)
  78. except subprocess.TimeoutExpired:
  79. pass