import time
from fortrace.core.qemu_monitor import QEMUMonitorSession
from fortrace.utility.applications.application import (
ApplicationEvent,
ApplicationType,
GenericApplication,
)
from fortrace.utility.applications.application_factory import get_application
from fortrace.utility.desktop_environments.desktop_environment import DesktopEnvironment
from fortrace.utility.distribution_constants import DesktopEnvironmentType, OSType
from fortrace.utility.exceptions import ConfigurationError, DesktopEnvironmentException
from fortrace.utility.image_processing.image_similarity import (
detect_newly_opened_window,
)
from fortrace.utility.image_processing.text_detection import (
detect_and_recognize_text,
text_line_contains,
)
from fortrace.utility.logger_helper import setup_logger
logger = setup_logger(__name__)
[docs]
class Plasma(DesktopEnvironment):
"""Implementation of KDE Plasma desktop environment"""
def __init__(
self,
qemu_monitor_session: QEMUMonitorSession,
):
super().__init__(
OSType.LINUX, DesktopEnvironmentType.KDE_Plasma, qemu_monitor_session
)
self._application_launcher_coordinates = None
[docs]
def open_application(
self, application_type: ApplicationType, application_name: str, **kwargs
) -> GenericApplication:
"""Opens a specified application with Plasma's Application Launcher.
Args:
application_type:
application_name:
**kwargs:
Todo:
Currently, this method only works with a light theme, since the application
launcher cannot be recognized properly in a dark theme
Returns:
"""
before = (
self._qs.take_screenshot()
) # take screenshot when application is not opened
if not self._application_launcher_coordinates:
self._qs.send_key_combination("meta_l")
time.sleep(2)
for _ in range(5):
windows_menu_coordinates = detect_newly_opened_window(
before, self._qs.take_screenshot()
)
if windows_menu_coordinates:
self._application_launcher_coordinates = windows_menu_coordinates
logger.debug(
"Set application launcher coordinates to %s",
self._application_launcher_coordinates,
)
break
time.sleep(2)
else:
raise DesktopEnvironmentException(
"Cannot determine application launcher coordinates"
)
else:
self._qs.send_key_combination("meta_l")
for _ in range(5): # detect if Windows menu has loaded
text = detect_and_recognize_text(
self._qs.take_screenshot(), self._application_launcher_coordinates
)[1]
if text_line_contains(
text, ["Favorites", "Applications", "Places"], "ignore_case"
):
logger.debug("Application launcher opened")
break
time.sleep(5)
else:
logger.error("Could not open application launcher within 25 seconds")
raise DesktopEnvironmentException(
"Application launcher window is not responding as expected"
)
self._qs.send_text(application_name)
# crop out search bar to search only results window for application name
launcher_result_window = list(self._application_launcher_coordinates)
launcher_result_window[1] += 45
launcher_result_window = tuple(launcher_result_window)
for _ in range(5):
if text_line_contains(
detect_and_recognize_text(
self._qs.take_screenshot(),
launcher_result_window,
)[1],
application_name,
"sequence",
):
break
elif text_line_contains(
detected := detect_and_recognize_text(
self._qs.take_screenshot(), self._application_launcher_coordinates
)[1],
["No matches"],
"ignore_case",
):
raise DesktopEnvironmentException(
f"Application {application_name} not found!"
)
logger.debug(
"Search for application has not completed yet. Will wait longer."
)
time.sleep(5)
else:
logger.warning(
"Could not find %s with application launchers search. Maybe a typo? "
"Or the search hang up.",
application_name,
)
logger.warning("Detected strings: %s", detected)
raise DesktopEnvironmentException(
f"Application {application_name} not found!"
)
logger.info("Open application %s", application_name)
self._qs.send_key_combination("ret")
application = get_application(
application_type, application_name, self._qs, self._on_change, **kwargs
)
time.sleep(5)
for _ in range(5):
coordinates = detect_newly_opened_window(before, self._qs.take_screenshot())
if coordinates is None:
logger.debug(
"Application %s is currently not opened. Will wait.",
application_name,
)
else:
break
time.sleep(5)
else:
raise DesktopEnvironmentException(
"Failed to open application %s", application_name
)
application.coordinates = coordinates
return application
[docs]
def login(self, username: str, password: str | None):
self._qs.send_text(password, True)
for i in range(5):
text = detect_and_recognize_text(self._qs.take_screenshot())[1]
if text_line_contains(text, "Login failed"):
raise ConfigurationError(
f"The provided password '{password}' is not correct"
)
time.sleep(2)
self._session_unlocked = True
# TODO: determine when Desktop env is ready
time.sleep(10)
self._qs.mouse.init()
[docs]
def focus_application(self, application: GenericApplication):
if application.focused:
return
self._qs.send_key_combination("alt-spc") # open KRunner
time.sleep(2)
self._qs.send_text(f"Windows {application.name}", True)
self._on_change(
ApplicationEvent.FOCUS_SHIFTED, application_reference=application
)
[docs]
def command_krunner(self, command: str):
"""Send a command to KRunner.
Args:
command: the command to send
Notes:
This method does only send a command to KRunner and does not take care of
newly opened windows or other focus shifts that might happen.
"""
self._qs.send_key_combination("alt-spc") # open Krunner
time.sleep(2)
logger.info("Executing command %s in KRunner", command)
self._qs.send_text(command, True)