From 455976a0187c36ca676c4ec3f34e305b97dfab80 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Tue, 21 Feb 2023 22:59:43 +0530 Subject: [PATCH 1/8] install windows lib if on windows --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 908e9e2..3a01449 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,10 @@ urllib3 -winshell imutils numpy opencv_python Pillow -pypiwin32 +pypiwin32 ; platform_system=="Windows" +winshell ; platform_system=="Windows" ttkthemes requests beautifulsoup4 From 6000e9022e6d41b67dfc3a3985c4596b1e6afba7 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Tue, 21 Feb 2023 23:01:31 +0530 Subject: [PATCH 2/8] created interface and proxy for os services --- fishy/__main__.py | 2 + fishy/osservices/__init__.py | 0 fishy/osservices/linux.py | 29 +++++++++++++ fishy/osservices/os_services.py | 76 +++++++++++++++++++++++++++++++++ fishy/osservices/windows.py | 29 +++++++++++++ 5 files changed, 136 insertions(+) create mode 100644 fishy/osservices/__init__.py create mode 100644 fishy/osservices/linux.py create mode 100644 fishy/osservices/os_services.py create mode 100644 fishy/osservices/windows.py diff --git a/fishy/__main__.py b/fishy/__main__.py index 68eb611..80600bc 100644 --- a/fishy/__main__.py +++ b/fishy/__main__.py @@ -16,6 +16,7 @@ from fishy.helper.active_poll import active from fishy.helper.config import config from fishy.helper.hotkey.hotkey_process import hotkey from fishy.helper.migration import Migration +from fishy.osservices.os_services import os_services def check_window_name(title): @@ -56,6 +57,7 @@ def initialize(window_to_hide): def main(): print("launching please wait...") + os_services.init() config.init() if not check_eula(): return diff --git a/fishy/osservices/__init__.py b/fishy/osservices/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/fishy/osservices/linux.py b/fishy/osservices/linux.py new file mode 100644 index 0000000..8cc82fd --- /dev/null +++ b/fishy/osservices/linux.py @@ -0,0 +1,29 @@ +from typing import Tuple + +from fishy.osservices.os_services import IOSServices + + +class Linux(IOSServices): + def hide_terminal(self): + pass + + def create_shortcut(self): + pass + + def get_documents_path(self) -> str: + pass + + def is_admin(self) -> bool: + pass + + def get_eso_config_path(self) -> str: + pass + + def is_eso_active(self) -> bool: + pass + + def get_monitor_rect(self): + pass + + def get_game_window_rect(self) -> Tuple[int, int, int, int]: + pass diff --git a/fishy/osservices/os_services.py b/fishy/osservices/os_services.py new file mode 100644 index 0000000..10b9ac7 --- /dev/null +++ b/fishy/osservices/os_services.py @@ -0,0 +1,76 @@ +import logging +from abc import ABC, abstractmethod +from typing import Tuple, Optional +import platform + + +class IOSServices(ABC): + + @abstractmethod + def hide_terminal(self): + """ + :return: hides the terminal used to launch fishy + """ + + @abstractmethod + def create_shortcut(self): + """ + creates a new shortcut on desktop + """ + + @abstractmethod + def get_documents_path(self) -> str: + """ + :return: documents path to save config file + """ + + @abstractmethod + def is_admin(self) -> bool: + """ + :return: true if has elevated rights + """ + + @abstractmethod + def get_eso_config_path(self) -> str: + """ + :return: path location of the ElderScrollsOnline Folder (in documents) which has "live" folder in it + """ + + @abstractmethod + def is_eso_active(self) -> bool: + """ + :return: true if eso is the active window + """ + + @abstractmethod + def get_monitor_rect(self): + """ + :return: [top, left, height, width] of monitor which has game running in it + """ + + @abstractmethod + def get_game_window_rect(self) -> Tuple[int, int, int, int]: + """ + :return: location of the game window without any frame + """ + + +class MyMetaclass(type): + def __getattr__(cls, name): + return getattr(os_services._instance, name) + + +class os_services(metaclass=MyMetaclass): + _instance: Optional[IOSServices] = None + + @staticmethod + def init(): + os_name = platform.system() + if os_name == "Windows": + from fishy.osservices.windows import Windows + os_services._instance = Windows() + elif os_name == "Linux": + from fishy.osservices.linux import Linux + os_services._instance = Linux() + else: + logging.error("Platform not supported") diff --git a/fishy/osservices/windows.py b/fishy/osservices/windows.py new file mode 100644 index 0000000..85924bd --- /dev/null +++ b/fishy/osservices/windows.py @@ -0,0 +1,29 @@ +from typing import Tuple + +from fishy.osservices.os_services import IOSServices + + +class Windows(IOSServices): + def hide_terminal(self): + pass + + def create_shortcut(self): + pass + + def get_documents_path(self) -> str: + pass + + def is_admin(self) -> bool: + pass + + def get_eso_config_path(self) -> str: + pass + + def is_eso_active(self) -> bool: + pass + + def get_monitor_rect(self): + pass + + def get_game_window_rect(self) -> Tuple[int, int, int, int]: + pass From 0de6b547771e9cd979c91e46cc31d46c8c0278f2 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Tue, 21 Feb 2023 23:04:58 +0530 Subject: [PATCH 3/8] implemented methods for windows --- fishy/osservices/windows.py | 111 ++++++++++++++++++++++++++++++------ 1 file changed, 95 insertions(+), 16 deletions(-) diff --git a/fishy/osservices/windows.py b/fishy/osservices/windows.py index 85924bd..796bd22 100644 --- a/fishy/osservices/windows.py +++ b/fishy/osservices/windows.py @@ -1,29 +1,108 @@ +import ctypes +import logging +import math +import os +import sys from typing import Tuple +import pywintypes +import win32api +import win32con +import win32gui +import winshell +from win32com.client import Dispatch +from win32comext.shell import shell, shellcon +from win32gui import GetForegroundWindow, GetWindowText + + +from ctypes import windll + +from fishy.helper import manifest_file from fishy.osservices.os_services import IOSServices +def _check_window_name(title): + titles = ["Command Prompt", "PowerShell", "Fishy"] + for t in titles: + if t in title: + return True + return False + + class Windows(IOSServices): - def hide_terminal(self): - pass - - def create_shortcut(self): - pass - - def get_documents_path(self) -> str: - pass - def is_admin(self) -> bool: - pass - - def get_eso_config_path(self) -> str: - pass + try: + is_admin = os.getuid() == 0 + except AttributeError: + is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 + return is_admin def is_eso_active(self) -> bool: - pass + return GetWindowText(GetForegroundWindow()) == "Elder Scrolls Online" + + # noinspection PyBroadException + def create_shortcut(self, anti_ghosting=False): + try: + desktop = winshell.desktop() + path = os.path.join(desktop, "Fishybot ESO.lnk") + _shell = Dispatch('WScript.Shell') + shortcut = _shell.CreateShortCut(path) + + if anti_ghosting: + shortcut.TargetPath = r"C:\Windows\System32\cmd.exe" + python_dir = os.path.join(os.path.dirname(sys.executable), "pythonw.exe") + shortcut.Arguments = f"/C start /affinity 1 /low {python_dir} -m fishy" + else: + shortcut.TargetPath = os.path.join(os.path.dirname(sys.executable), "python.exe") + shortcut.Arguments = "-m fishy" + + shortcut.IconLocation = manifest_file("icon.ico") + shortcut.save() + + logging.info("Shortcut created") + except Exception: + logging.error("Couldn't create shortcut") + + def __init__(self): + self.to_hide = win32gui.GetForegroundWindow() + + def hide_terminal(self): + if _check_window_name(win32gui.GetWindowText(self.to_hide)): + win32gui.ShowWindow(self.to_hide, win32con.SW_HIDE) + + def get_documents_path(self) -> str: + return shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0) + + def get_eso_config_path(self) -> str: + # noinspection PyUnresolvedReferences + from win32com.shell import shell, shellcon + documents = shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0) + return os.path.join(documents, "Elder Scrolls Online") def get_monitor_rect(self): - pass + # noinspection PyUnresolvedReferences + try: + hwnd = win32gui.FindWindow(None, "Elder Scrolls Online") + monitor = windll.user32.MonitorFromWindow(hwnd, 2) + monitor_info = win32api.GetMonitorInfo(monitor) + return monitor_info["Monitor"] + except pywintypes.error: + return None def get_game_window_rect(self) -> Tuple[int, int, int, int]: - pass + hwnd = win32gui.FindWindow(None, "Elder Scrolls Online") + monitor_rect = self.get_monitor_rect() + + rect = win32gui.GetWindowRect(hwnd) + client_rect = win32gui.GetClientRect(hwnd) + windowOffset = math.floor(((rect[2] - rect[0]) - client_rect[2]) / 2) + fullscreen = monitor_rect[3] == (rect[3] - rect[1]) + title_offset = ((rect[3] - rect[1]) - client_rect[3]) - windowOffset if not fullscreen else 0 + + game_rect = ( + rect[0] + windowOffset - monitor_rect[0], + rect[1] + title_offset - monitor_rect[1], + rect[2] - windowOffset - monitor_rect[0], + rect[3] - windowOffset - monitor_rect[1] + ) + return game_rect From c5d1cb67cf7131d0678e2fa00a9025653b62ee4d Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Tue, 21 Feb 2023 23:05:44 +0530 Subject: [PATCH 4/8] decoupled os_calls from fishy to use os_services instead --- fishy/__main__.py | 35 ++++-------- fishy/engine/common/window_server.py | 59 ++++++++------------ fishy/engine/fullautofisher/engine.py | 11 ++-- fishy/engine/semifisher/fishing_event.py | 6 +-- fishy/gui/main_gui.py | 3 +- fishy/helper/__init__.py | 2 +- fishy/helper/config.py | 6 ++- fishy/helper/helper.py | 68 +++--------------------- 8 files changed, 55 insertions(+), 135 deletions(-) diff --git a/fishy/__main__.py b/fishy/__main__.py index 80600bc..c2c7183 100644 --- a/fishy/__main__.py +++ b/fishy/__main__.py @@ -1,11 +1,6 @@ -import ctypes import logging -import os import sys -import win32con -import win32gui - import fishy from fishy.gui import GUI, splash, update_dialog, check_eula from fishy import helper, web @@ -19,19 +14,13 @@ from fishy.helper.migration import Migration from fishy.osservices.os_services import os_services -def check_window_name(title): - titles = ["Command Prompt", "PowerShell", "Fishy"] - for t in titles: - if t in title: - return True - return False - - # noinspection PyBroadException -def initialize(window_to_hide): +def initialize(): Migration.migrate() - helper.create_shortcut_first() + if not config.get("shortcut_created", False): + os_services.create_shortcut(False) + config.set("shortcut_created", True) new_session = web.get_session() @@ -39,15 +28,11 @@ def initialize(window_to_hide): logging.error("Couldn't create a session, some features might not work") logging.debug(f"created session {new_session}") - try: - is_admin = os.getuid() == 0 - except AttributeError: - is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 - if is_admin: + if os_services.is_admin(): logging.info("Running with admin privileges") - if not config.get("debug", False) and check_window_name(win32gui.GetWindowText(window_to_hide)): - win32gui.ShowWindow(window_to_hide, win32con.SW_HIDE) + if not config.get("debug", False): + os_services.hide_terminal() helper.install_thread_excepthook() sys.excepthook = helper.unhandled_exception_logging @@ -73,20 +58,18 @@ def main(): update_dialog.check_update(gui) logger.connect(gui) - window_to_hide = win32gui.GetForegroundWindow() - bot = EngineEventHandler(lambda: gui) gui = GUI(lambda: bot, on_gui_load) hotkey.start() logging.info(f"Fishybot v{fishy.__version__}") - initialize(window_to_hide) + initialize() gui.start() active.start() - bot.start_event_handler() # main thread loop + bot.start_event_handler() # main thread loop hotkey.stop() active.stop() diff --git a/fishy/engine/common/window_server.py b/fishy/engine/common/window_server.py index b1abd37..e6fc693 100644 --- a/fishy/engine/common/window_server.py +++ b/fishy/engine/common/window_server.py @@ -1,18 +1,14 @@ import logging -import math from enum import Enum from threading import Thread import numpy as np -import pywintypes -import win32api -import win32gui -from ctypes import windll from mss import mss from mss.base import MSSBase from fishy.helper.helper import print_exc +from fishy.osservices.os_services import os_services class Status(Enum): @@ -27,10 +23,11 @@ class WindowServer: """ Screen: np.ndarray = None windowOffset = None - hwnd = None status = Status.STOPPED sct: MSSBase = None - monitor_top_left = None + + crop = None + monitor_id = -1 def init(): @@ -38,47 +35,35 @@ def init(): Executed once before the main loop, Finds the game window, and calculates the offset to remove the title bar """ - # noinspection PyUnresolvedReferences - try: - WindowServer.hwnd = win32gui.FindWindow(None, "Elder Scrolls Online") + WindowServer.status = Status.RUNNING + WindowServer.sct = mss() - monitor_id = windll.user32.MonitorFromWindow(WindowServer.hwnd, 2) - WindowServer.monitor_top_left = win32api.GetMonitorInfo(monitor_id)["Monitor"][:2] + WindowServer.crop = os_services.get_game_window_rect() - rect = win32gui.GetWindowRect(WindowServer.hwnd) - client_rect = win32gui.GetClientRect(WindowServer.hwnd) - WindowServer.windowOffset = math.floor(((rect[2] - rect[0]) - client_rect[2]) / 2) - WindowServer.status = Status.RUNNING - WindowServer.sct = mss() - - except pywintypes.error: + monitor_rect = os_services.get_monitor_rect() + if monitor_rect is None: logging.error("Game window not found") WindowServer.status = Status.CRASHED + for i, m in enumerate(WindowServer.sct.monitors): + if m["top"] == monitor_rect[0] and m["left"] == monitor_rect[1]: + WindowServer.monitor_id = i + + +def get_screenshot(): + sct_img = WindowServer.sct.grab(WindowServer.sct.monitors[WindowServer.monitor_id]) + # noinspection PyTypeChecker + return np.array(sct_img) + def loop(): """ Executed in the start of the main loop finds the game window location and captures it """ - - sct_img = WindowServer.sct.grab(WindowServer.sct.monitors[1]) - # noinspection PyTypeChecker - temp_screen = np.array(sct_img) - - rect = win32gui.GetWindowRect(WindowServer.hwnd) - client_rect = win32gui.GetClientRect(WindowServer.hwnd) - - fullscreen = sct_img.size.height == (rect[3] - rect[1]) - title_offset = ((rect[3] - rect[1]) - client_rect[3]) - WindowServer.windowOffset if not fullscreen else 0 - crop = ( - rect[0] + WindowServer.windowOffset - WindowServer.monitor_top_left[0], - rect[1] + title_offset - WindowServer.monitor_top_left[1], - rect[2] - WindowServer.windowOffset - WindowServer.monitor_top_left[0], - rect[3] - WindowServer.windowOffset - WindowServer.monitor_top_left[1] - ) - - WindowServer.Screen = temp_screen[crop[1]:crop[3], crop[0]:crop[2]] if not fullscreen else temp_screen + ss = get_screenshot() + crop = WindowServer.crop + WindowServer.Screen = ss[crop[1]:crop[3], crop[0]:crop[2]] if WindowServer.Screen.size == 0: logging.error("Don't minimize or drag game window outside the screen") diff --git a/fishy/engine/fullautofisher/engine.py b/fishy/engine/fullautofisher/engine.py index 3ce9a54..76b24c0 100644 --- a/fishy/engine/fullautofisher/engine.py +++ b/fishy/engine/fullautofisher/engine.py @@ -16,7 +16,8 @@ from fishy.engine.fullautofisher.mode.recorder import Recorder from fishy.engine.semifisher import fishing_mode from fishy.engine.semifisher.fishing_mode import FishingMode from fishy.helper.config import config -from fishy.helper.helper import wait_until, is_eso_active, sign, print_exc +from fishy.helper.helper import wait_until, sign, print_exc +from fishy.osservices.os_services import os_services mse = mouse.Controller() kb = keyboard.Controller() @@ -52,9 +53,9 @@ class FullAuto(IEngine): return # block thread until game window becomes active - if not is_eso_active(): + if not os_services.is_eso_active(): logging.info("Waiting for eso window to be active...") - wait_until(lambda: is_eso_active() or not self.start) + wait_until(lambda: os_services.is_eso_active() or not self.start) if self.start: logging.info("starting in 2 secs...") time.sleep(2) @@ -87,8 +88,8 @@ class FullAuto(IEngine): def stop_on_inactive(self): def func(): logging.debug("stop on inactive started") - wait_until(lambda: not is_eso_active() or not self.start) - if self.start and not is_eso_active(): + wait_until(lambda: not os_services.is_eso_active() or not self.start) + if self.start and not os_services.is_eso_active(): self.turn_off() logging.debug("stop on inactive stopped") Thread(target=func).start() diff --git a/fishy/engine/semifisher/fishing_event.py b/fishy/engine/semifisher/fishing_event.py index dfcc290..d442b1e 100644 --- a/fishy/engine/semifisher/fishing_event.py +++ b/fishy/engine/semifisher/fishing_event.py @@ -12,10 +12,10 @@ from playsound import playsound from fishy import web from fishy.engine.semifisher import fishing_mode -from fishy.engine.semifisher.fishing_mode import FishingMode, State +from fishy.engine.semifisher.fishing_mode import State from fishy.helper import helper from fishy.helper.config import config -from fishy.helper.helper import is_eso_active +from fishy.osservices.os_services import os_services class FishEvent: @@ -44,7 +44,7 @@ def _fishing_sleep(waittime, lower_limit_ms=16, upper_limit_ms=1375): def if_eso_is_focused(func): def wrapper(): - if not is_eso_active(): + if not os_services.is_eso_active(): logging.warning("ESO window is not focused") return func() diff --git a/fishy/gui/main_gui.py b/fishy/gui/main_gui.py index 48ccd20..46fcaa7 100644 --- a/fishy/gui/main_gui.py +++ b/fishy/gui/main_gui.py @@ -15,6 +15,7 @@ from ..helper.config import config from .discord_login import discord_login from ..helper.hotkey.hotkey_process import hotkey from ..helper.hotkey.process import Key +from ..osservices.os_services import os_services if typing.TYPE_CHECKING: from . import GUI @@ -46,7 +47,7 @@ def _create(gui: 'GUI'): gui.login.set(1 if login > 0 else 0) state = tk.DISABLED if login == -1 else tk.ACTIVE filemenu.add_checkbutton(label="Login", command=lambda: discord_login(gui), variable=gui.login, state=state) - filemenu.add_command(label="Create Shortcut", command=lambda: helper.create_shortcut(False)) + filemenu.add_command(label="Create Shortcut", command=lambda: os_services.create_shortcut(False)) # filemenu.add_command(label="Create Anti-Ghost Shortcut", command=lambda: helper.create_shortcut(True)) def _toggle_mode(): diff --git a/fishy/helper/__init__.py b/fishy/helper/__init__.py index 686e1a3..e9d5cb5 100644 --- a/fishy/helper/__init__.py +++ b/fishy/helper/__init__.py @@ -1,5 +1,5 @@ from .config import Config -from .helper import (addon_exists, create_shortcut, create_shortcut_first, +from .helper import (addon_exists, get_addonversion, get_savedvarsdir, install_addon, install_thread_excepthook, manifest_file, not_implemented, open_web, playsound_multiple, diff --git a/fishy/helper/config.py b/fishy/helper/config.py index 6a050a6..fe1a745 100644 --- a/fishy/helper/config.py +++ b/fishy/helper/config.py @@ -10,15 +10,17 @@ from typing import Optional from event_scheduler import EventScheduler +from fishy.osservices.os_services import os_services + def filename(): - from fishy.helper.helper import get_documents name = "fishy_config.json" _filename = os.path.join(os.environ["HOMEDRIVE"], os.environ["HOMEPATH"], "Documents", name) if os.path.exists(_filename): return _filename - return os.path.join(get_documents(), name) + # fallback for onedrive documents + return os.path.join(os_services.get_documents(), name) temp_file = os.path.join(os.environ["TEMP"], "fishy_config.BAK") diff --git a/fishy/helper/helper.py b/fishy/helper/helper.py index 6e7215c..99fb43f 100644 --- a/fishy/helper/helper.py +++ b/fishy/helper/helper.py @@ -14,15 +14,13 @@ from uuid import uuid1 from zipfile import ZipFile import requests -import winshell from playsound import playsound -from win32com.client import Dispatch -from win32comext.shell import shell, shellcon -from win32gui import GetForegroundWindow, GetWindowText + import fishy from fishy.constants import libgps, lam2, fishyqr, libmapping, libdl, libchatmsg from fishy.helper.config import config +from fishy.osservices.os_services import os_services def playsound_multiple(path, count=2): @@ -110,55 +108,14 @@ def manifest_file(rel_path): return os.path.join(os.path.dirname(fishy.__file__), rel_path) -def create_shortcut_first(): - from .config import config - - if not config.get("shortcut_created", False): - create_shortcut(False) - config.set("shortcut_created", True) - - -# noinspection PyBroadException -def create_shortcut(anti_ghosting: bool): - """ - creates a new shortcut on desktop - """ - try: - desktop = winshell.desktop() - path = os.path.join(desktop, "Fishybot ESO.lnk") - - _shell = Dispatch('WScript.Shell') - shortcut = _shell.CreateShortCut(path) - - if anti_ghosting: - shortcut.TargetPath = r"C:\Windows\System32\cmd.exe" - python_dir = os.path.join(os.path.dirname(sys.executable), "pythonw.exe") - shortcut.Arguments = f"/C start /affinity 1 /low {python_dir} -m fishy" - else: - shortcut.TargetPath = os.path.join(os.path.dirname(sys.executable), "python.exe") - shortcut.Arguments = "-m fishy" - - shortcut.IconLocation = manifest_file("icon.ico") - shortcut.save() - - logging.info("Shortcut created") - except Exception: - print_exc() - logging.error("Couldn't create shortcut") - - def get_savedvarsdir(): - # noinspection PyUnresolvedReferences - from win32com.shell import shell, shellcon - documents = shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0) - return os.path.join(documents, "Elder Scrolls Online", "live", "SavedVariables") + eso_path = os_services.get_eso_config_path() + return os.path.join(eso_path, "live", "SavedVariables") def get_addondir(): - # noinspection PyUnresolvedReferences - from win32com.shell import shell, shellcon - documents = shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0) - return os.path.join(documents, "Elder Scrolls Online", "live", "Addons") + eso_path = os_services.get_eso_config_path() + return os.path.join(eso_path, "live", "Addons") def addon_exists(name, url=None, v=None): @@ -222,19 +179,10 @@ def remove_addon(name, url=None, v=None): return 0 -def get_documents(): - return shell.SHGetFolderPath(0, shellcon.CSIDL_PERSONAL, None, 0) - - def log_raise(msg): logging.error(msg) raise Exception(msg) - -def is_eso_active(): - return GetWindowText(GetForegroundWindow()) == "Elder Scrolls Online" - - # noinspection PyProtectedMember,PyUnresolvedReferences def _get_id(thread): # returns id of the respective thread @@ -252,8 +200,8 @@ def kill_thread(thread): if res > 1: ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0) print('Exception raise failure') - - + + def print_exc(): logging.error(traceback.format_exc()) traceback.print_exc() From 4f90df9079ca071f47691e2b0741256360946e4c Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Tue, 21 Feb 2023 23:22:11 +0530 Subject: [PATCH 5/8] generalized class instance --- fishy/osservices/os_services.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fishy/osservices/os_services.py b/fishy/osservices/os_services.py index 10b9ac7..3260ce7 100644 --- a/fishy/osservices/os_services.py +++ b/fishy/osservices/os_services.py @@ -55,12 +55,12 @@ class IOSServices(ABC): """ -class MyMetaclass(type): +class ClassInstance(type): def __getattr__(cls, name): - return getattr(os_services._instance, name) + return getattr(cls._instance, name) -class os_services(metaclass=MyMetaclass): +class os_services(metaclass=ClassInstance): _instance: Optional[IOSServices] = None @staticmethod From a5499475f6d95c20d96238bf32071839af7dc484 Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Wed, 22 Feb 2023 00:56:22 +0530 Subject: [PATCH 6/8] generalized ClassInstance even more to be used as a decorator --- fishy/osservices/os_services.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/fishy/osservices/os_services.py b/fishy/osservices/os_services.py index 3260ce7..e4c52bb 100644 --- a/fishy/osservices/os_services.py +++ b/fishy/osservices/os_services.py @@ -1,4 +1,7 @@ +import inspect import logging +import re +import sys from abc import ABC, abstractmethod from typing import Tuple, Optional import platform @@ -55,12 +58,28 @@ class IOSServices(ABC): """ -class ClassInstance(type): - def __getattr__(cls, name): - return getattr(cls._instance, name) +# todo move this into helper and use for config and similar places +# but make sure other fishy stuff is not imported while importing helper +# to do that, move everything which uses fishy stuff into a different helper script +def singleton_proxy(instance_name): + def decorator(root_cls): + if not hasattr(root_cls, instance_name): + raise AttributeError(f"{instance_name} not found in {root_cls}") + + class SingletonProxy(type): + def __getattr__(cls, name): + return getattr(getattr(cls, instance_name), name) + + class NewClass(root_cls, metaclass=SingletonProxy): + ... + + return NewClass + + return decorator -class os_services(metaclass=ClassInstance): +@singleton_proxy("_instance") +class os_services: _instance: Optional[IOSServices] = None @staticmethod From 901ce6c3466238f148d60c17280b7269ae26595d Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Mon, 6 Mar 2023 23:18:09 +0530 Subject: [PATCH 7/8] handled case when game is not running --- fishy/engine/common/window_server.py | 21 ++++++++++-------- fishy/osservices/linux.py | 4 ++-- fishy/osservices/os_services.py | 2 +- fishy/osservices/windows.py | 32 ++++++++++++++++------------ 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/fishy/engine/common/window_server.py b/fishy/engine/common/window_server.py index e6fc693..2c0e128 100644 --- a/fishy/engine/common/window_server.py +++ b/fishy/engine/common/window_server.py @@ -39,9 +39,9 @@ def init(): WindowServer.sct = mss() WindowServer.crop = os_services.get_game_window_rect() - monitor_rect = os_services.get_monitor_rect() - if monitor_rect is None: + + if monitor_rect is None or WindowServer.crop is None: logging.error("Game window not found") WindowServer.status = Status.CRASHED @@ -50,10 +50,15 @@ def init(): WindowServer.monitor_id = i -def get_screenshot(): +def get_cropped_screenshot(): sct_img = WindowServer.sct.grab(WindowServer.sct.monitors[WindowServer.monitor_id]) # noinspection PyTypeChecker - return np.array(sct_img) + ss = np.array(sct_img) + crop = WindowServer.crop + cropped_ss = ss[crop[1]:crop[3], crop[0]:crop[2]] + if cropped_ss.size == 0: + return None + return cropped_ss def loop(): @@ -61,12 +66,10 @@ def loop(): Executed in the start of the main loop finds the game window location and captures it """ - ss = get_screenshot() - crop = WindowServer.crop - WindowServer.Screen = ss[crop[1]:crop[3], crop[0]:crop[2]] + WindowServer.Screen = get_cropped_screenshot() - if WindowServer.Screen.size == 0: - logging.error("Don't minimize or drag game window outside the screen") + if WindowServer.Screen is None: + logging.error("Couldn't find the game window") WindowServer.status = Status.CRASHED diff --git a/fishy/osservices/linux.py b/fishy/osservices/linux.py index 8cc82fd..a028910 100644 --- a/fishy/osservices/linux.py +++ b/fishy/osservices/linux.py @@ -1,4 +1,4 @@ -from typing import Tuple +from typing import Tuple, Optional from fishy.osservices.os_services import IOSServices @@ -25,5 +25,5 @@ class Linux(IOSServices): def get_monitor_rect(self): pass - def get_game_window_rect(self) -> Tuple[int, int, int, int]: + def get_game_window_rect(self) -> Optional[Tuple[int, int, int, int]]: pass diff --git a/fishy/osservices/os_services.py b/fishy/osservices/os_services.py index e4c52bb..182d9ba 100644 --- a/fishy/osservices/os_services.py +++ b/fishy/osservices/os_services.py @@ -52,7 +52,7 @@ class IOSServices(ABC): """ @abstractmethod - def get_game_window_rect(self) -> Tuple[int, int, int, int]: + def get_game_window_rect(self) -> Optional[Tuple[int, int, int, int]]: """ :return: location of the game window without any frame """ diff --git a/fishy/osservices/windows.py b/fishy/osservices/windows.py index 796bd22..c7dad66 100644 --- a/fishy/osservices/windows.py +++ b/fishy/osservices/windows.py @@ -3,7 +3,7 @@ import logging import math import os import sys -from typing import Tuple +from typing import Tuple, Optional import pywintypes import win32api @@ -89,20 +89,24 @@ class Windows(IOSServices): except pywintypes.error: return None - def get_game_window_rect(self) -> Tuple[int, int, int, int]: + def get_game_window_rect(self) -> Optional[Tuple[int, int, int, int]]: hwnd = win32gui.FindWindow(None, "Elder Scrolls Online") monitor_rect = self.get_monitor_rect() - rect = win32gui.GetWindowRect(hwnd) - client_rect = win32gui.GetClientRect(hwnd) - windowOffset = math.floor(((rect[2] - rect[0]) - client_rect[2]) / 2) - fullscreen = monitor_rect[3] == (rect[3] - rect[1]) - title_offset = ((rect[3] - rect[1]) - client_rect[3]) - windowOffset if not fullscreen else 0 + # noinspection PyUnresolvedReferences + try: + rect = win32gui.GetWindowRect(hwnd) + client_rect = win32gui.GetClientRect(hwnd) + windowOffset = math.floor(((rect[2] - rect[0]) - client_rect[2]) / 2) + fullscreen = monitor_rect[3] == (rect[3] - rect[1]) + title_offset = ((rect[3] - rect[1]) - client_rect[3]) - windowOffset if not fullscreen else 0 - game_rect = ( - rect[0] + windowOffset - monitor_rect[0], - rect[1] + title_offset - monitor_rect[1], - rect[2] - windowOffset - monitor_rect[0], - rect[3] - windowOffset - monitor_rect[1] - ) - return game_rect + game_rect = ( + rect[0] + windowOffset - monitor_rect[0], + rect[1] + title_offset - monitor_rect[1], + rect[2] - windowOffset - monitor_rect[0], + rect[3] - windowOffset - monitor_rect[1] + ) + return game_rect + except pywintypes.error: + return None From 47c0ce7413bbfdfc2e984fe4d715e95e7812138a Mon Sep 17 00:00:00 2001 From: Adam Saudagar Date: Mon, 6 Mar 2023 23:32:39 +0530 Subject: [PATCH 8/8] removed Linux temprorily until linux.py is implemented --- fishy/__main__.py | 5 ++++- fishy/engine/common/window_server.py | 1 + fishy/osservices/os_services.py | 19 ++++++++++++------- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/fishy/__main__.py b/fishy/__main__.py index c2c7183..68fd28e 100644 --- a/fishy/__main__.py +++ b/fishy/__main__.py @@ -42,7 +42,10 @@ def initialize(): def main(): print("launching please wait...") - os_services.init() + if not os_services.init(): + print("platform not supported") + return + config.init() if not check_eula(): return diff --git a/fishy/engine/common/window_server.py b/fishy/engine/common/window_server.py index 2c0e128..ec043ab 100644 --- a/fishy/engine/common/window_server.py +++ b/fishy/engine/common/window_server.py @@ -44,6 +44,7 @@ def init(): if monitor_rect is None or WindowServer.crop is None: logging.error("Game window not found") WindowServer.status = Status.CRASHED + return for i, m in enumerate(WindowServer.sct.monitors): if m["top"] == monitor_rect[0] and m["left"] == monitor_rect[1]: diff --git a/fishy/osservices/os_services.py b/fishy/osservices/os_services.py index 182d9ba..03f5a9a 100644 --- a/fishy/osservices/os_services.py +++ b/fishy/osservices/os_services.py @@ -7,6 +7,7 @@ from typing import Tuple, Optional import platform + class IOSServices(ABC): @abstractmethod @@ -46,7 +47,7 @@ class IOSServices(ABC): """ @abstractmethod - def get_monitor_rect(self): + def get_monitor_rect(self) -> Optional[Tuple[int, int]]: """ :return: [top, left, height, width] of monitor which has game running in it """ @@ -83,13 +84,17 @@ class os_services: _instance: Optional[IOSServices] = None @staticmethod - def init(): + def init() -> bool: os_name = platform.system() if os_name == "Windows": from fishy.osservices.windows import Windows os_services._instance = Windows() - elif os_name == "Linux": - from fishy.osservices.linux import Linux - os_services._instance = Linux() - else: - logging.error("Platform not supported") + return True + + # todo uncomment after linux.py is implemented + # if os_name == "Linux": + # from fishy.osservices.linux import Linux + # os_services._instance = Linux() + # return True + + return False