breezy-desktop/ui/src/virtualdisplaymanager.py

121 lines
3.9 KiB
Python

import gi
import json
import os
import signal
import subprocess
import time
from pathlib import Path
import logging
logger = logging.getLogger('breezy_ui')
gi.require_version('GLib', '2.0')
from gi.repository import GLib, GObject
from .files import get_bin_home
class VirtualDisplayManager(GObject.GObject):
__gproperties__ = {
'displays': (object, 'Displays', 'A list of the displays', GObject.ParamFlags.READWRITE)
}
_instance = None
@staticmethod
def get_instance():
if not VirtualDisplayManager._instance:
VirtualDisplayManager._instance = VirtualDisplayManager()
return VirtualDisplayManager._instance
def __init__(self):
GObject.GObject.__init__(self)
self.shm_path = Path("/dev/shm/breezy_virtual_displays.json")
self._load_displays()
self._prune_dead_display_processes()
GLib.timeout_add_seconds(15, self._prune_dead_display_processes)
def _process_dead(self, pid):
if (not os.path.exists(f"/proc/{pid}")):
return True
try:
if (os.waitpid(pid, os.WNOHANG) == (pid, 0)):
return True
except ChildProcessError:
# process isn't tied to the current process, it's not dead if it's still open
return False
return False
def _prune_dead_display_processes(self):
new_displays = [disp for disp in self.displays if not self._process_dead(disp['pid'])]
if new_displays != self.displays:
self.set_property('displays', new_displays)
self._save_processes()
return GLib.SOURCE_CONTINUE
def create_virtual_display(self, width, height, framerate):
try:
process = subprocess.Popen(
[f"{get_bin_home()}/virtualdisplay", "--width", str(int(round(width))), "--height", str(int(round(height))), "--framerate", str(framerate)],
start_new_session=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
if process.returncode is not None:
logger.error(f"Failed to create virtual display: {process.stderr.read()}")
return
self.displays.append({
'pid': process.pid,
'width': width,
'height': height
})
self.set_property('displays', self.displays)
self._save_processes()
except Exception as e:
logger.error(f"Failed to create virtual display: {e}")
def destroy_virtual_display(self, pid: str) -> bool:
try:
# Send SIGTERM to allow graceful shutdown
os.killpg(pid, signal.SIGTERM)
self.set_property('displays', [disp for disp in self.displays if disp['pid'] != pid])
self._save_processes()
return True
except ProcessLookupError:
# Process already gone, delete pid from list
self.set_property('displays', [disp for disp in self.displays if disp['pid'] != pid])
self._save_processes()
return True
except Exception as e:
logger.error(f"Failed to kill process {pid}: {e}")
return False
def _save_processes(self):
with open(self.shm_path, 'w') as f:
json.dump(self.displays, f)
def _load_displays(self):
displays = []
if self.shm_path.exists():
try:
with open(self.shm_path, 'r') as f:
displays = json.load(f)
except Exception:
displays = []
self.set_property('displays', displays)
def do_set_property(self, prop, value):
if prop.name == 'displays':
self.displays = value
def do_get_property(self, prop):
if prop.name == 'displays':
return self.displays