Source code for fortrace.utility.desktop_environments.desktop_environment

import abc
import uuid

from fortrace.core.qemu_monitor import QEMUMonitorSession
from fortrace.utility.applications.application import (
    ApplicationEvent,
    ApplicationType,
    GenericApplication,
    GenericPopup,
    ParentNotifier,
)
from fortrace.utility.distribution_constants import DesktopEnvironmentType, OSType
from fortrace.utility.image_processing.image_similarity import (
    detect_newly_opened_window,
)


[docs] class DesktopEnvironment(abc.ABC): """Desktop environment of a domain. Should be implemented by all desktop environments in ForTrace. This class is responsible for managing application windows and keeping track of the one that is currently in focus. Furthermore, it is also responsible for the login on a graphical domain. """ _applications: list[GenericApplication] _active_application: GenericApplication | None _on_change: ParentNotifier def __init__( self, os_type: OSType, desktop_env: DesktopEnvironmentType, qemu_monitor_session: QEMUMonitorSession, ): """Create a Desktop environment attribute of a domain. Args: os_type: type of the operating system desktop_env: type of the desktop environment qemu_monitor_session: active QEMU monitor session to the domain """ self._os_type = os_type self._desktop_env = desktop_env self._applications = [] self._active_application = None self._user_selected = False self._session_unlocked = False self._qs = qemu_monitor_session
[docs] def is_application_open(self, application_name: str) -> bool: """Determine if an application is opened, based on its name. Args: application_name: the name of the application to test Returns: Status of the application """ for application in self._applications: if application_name == application.name: return True return False
[docs] def get_application( self, application_identifier: str | uuid.UUID ) -> GenericApplication: """Get an application based on its name or uuid Args: application_identifier: provide name if only one instance is open, else uuid Returns: handle to application """ if isinstance(application_identifier, uuid.UUID): return [ application for application in self._applications if application_identifier == application.uuid ][0] else: return [ application for application in self._applications if application_identifier == application.name ][0]
[docs] @abc.abstractmethod def open_application( self, application_type: ApplicationType, application_name: str, **kwargs ) -> GenericApplication: """Open the specified application and move focus to it. Implementation has to add application to list of applications. Args: application_type: Type of application to filter for correct sub-factory application_name: Name of the application to open **kwargs: Desktop environment specific arguments, e.g. `fortrace.utility.desktop_environments.Windows.Windows` Returns: handle to newly opened application """ pass
[docs] def close_application(self, application: GenericApplication): """Close the given application via key-combination alt-f4. Do not use the application object afterward. Args: application: application object which refers to the application that is to be closed """ self._applications.remove(application) if self._active_application != application: prev_focus = self._active_application self.focus_application(application) self._qs.send_key_combination("alt-f4") self.focus_application(prev_focus) else: self._qs.send_key_combination("alt-f4") self._active_application = None del application
[docs] @abc.abstractmethod def login(self, username: str, password: str | None): """Perform a login for the given user. Args: username: name of the user to login password: password of the given user or None if there is none Note: This call blocks until the desktop environment is ready to receive input. """ pass
[docs] def lock_screen(self): self._qs.send_key_combination("meta_l-l") self._session_unlocked = False
[docs] def show_desktop(self): """Minimized all windows and shows desktop. Can be useful when simulating multiple applications. Note: If executed twice, the previous window state is restored, however the focus of the active application is lost """ self.active_application = None self._qs.send_key_combination("meta_l-d")
[docs] def maximize_application(self): """Maximize the current focused application. Note: Useful before conducting text recognition. """ screenshot_before = self._qs.take_screenshot() self._qs.send_key_combination("meta_l-pgup") self._on_change( ApplicationEvent.APPLICATION_RESIZED, screenshot_before=screenshot_before )
[docs] def system_power_down(self): """Powers down the system using the QEMUMonitor. FIXME: On Windows guests this method may require the installation of QEMU Agent FIXME: This method should use the desktop env to power down the system """ self._qs.direct_command("system_powerdown") # if supported by system
[docs] @abc.abstractmethod def focus_application(self, application: GenericApplication): """Bring the given application into focus so that it can receive input commands. Args: application: application object to focus Returns: """ pass
@property def active_application(self) -> GenericApplication: return self._active_application @active_application.setter def active_application(self, application: GenericApplication | None): """Setter for active application which handles focus attribute. Args: application: handle to new active application """ if self._active_application is not None: self._active_application.focused = False self._active_application = application if application is not None: self._active_application.focused = True @property def qemu_monitor_session(self) -> QEMUMonitorSession: return self._qs @property def session_unlocked(self) -> bool: return self._session_unlocked def _on_change(self, event: ApplicationEvent, **kwargs): """Call from application to notify desktop environment about change of state. Graphical applications can use this method to notify the desktop environment about changes. Args: event: at least needs type of event to process **kwargs: event specific arguments must be passed as keyword arguments Examples: - Application A opens another application (e.g. open a file from the file browser) - Application closes itself by NOT using close_application """ match event: case ApplicationEvent.CLOSED: if self._active_application == kwargs["application_reference"]: self.active_application = None self._applications.remove(kwargs["application_reference"]) # TODO: in windows the focus is laid on the application to the right in self._applications case ApplicationEvent.NEW_APPLICATION_OPENED: if self._active_application is not None: self.active_application.focused = False self.active_application = kwargs["application_reference"] self._applications.append(kwargs["application_reference"]) case ApplicationEvent.FOCUS_SHIFTED: self.active_application = kwargs["application_reference"] case ApplicationEvent.FOCUS_APPLICATION: self.focus_application(kwargs["application_reference"]) self.active_application = kwargs["application_reference"] case ApplicationEvent.APPLICATION_RESIZED: self._active_application.coordinates = detect_newly_opened_window( kwargs["screenshot_before"], self._qs.take_screenshot() ) case ApplicationEvent.APPLICATION_POPUP_OPENED: popup = kwargs["popup_reference"] # type: GenericPopup application = kwargs[ "application_reference" ] # type: GenericApplication application.focused = False popup.focused = True case ApplicationEvent.APPLICATION_POPUP_CLOSED: popup = kwargs["popup_reference"] # type: GenericPopup application = kwargs[ "application_reference" ] # type: GenericApplication self._applications.remove(popup) self.active_application = application case _: raise ValueError(f"Unknown application event '{event}'")