From 81a3b4fd5f0cbad5f23dcd5d93b20523733461ee Mon Sep 17 00:00:00 2001 From: David Sangrey Date: Tue, 18 Jun 2024 09:51:36 -0400 Subject: [PATCH] [1805] Pull In Code from 1808 --- monitor.py | 75 +++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 18 deletions(-) diff --git a/monitor.py b/monitor.py index 8b55a2b9..85f56a4c 100644 --- a/monitor.py +++ b/monitor.py @@ -15,7 +15,7 @@ import sys import threading from calendar import timegm from collections import defaultdict -from os import SEEK_END, SEEK_SET, listdir +from os import SEEK_END, SEEK_SET, listdir, environ from os.path import basename, expanduser, getctime, isdir, join from time import gmtime, localtime, mktime, sleep, strftime, strptime, time from typing import TYPE_CHECKING, Any, BinaryIO, MutableMapping @@ -35,18 +35,16 @@ MAX_NAVROUTE_DISCREPANCY = 5 # Timestamp difference in seconds MAX_FCMATERIALS_DISCREPANCY = 5 # Timestamp difference in seconds if sys.platform == 'win32': - import ctypes - from ctypes.wintypes import BOOL, HWND, LPARAM + import win32process + import win32con + import win32security import win32gui import win32api - + import pywintypes from watchdog.events import FileSystemEventHandler, FileSystemEvent from watchdog.observers import Observer from watchdog.observers.api import BaseObserver - EnumWindowsProc = ctypes.WINFUNCTYPE(BOOL, HWND, LPARAM) - GetProcessHandleFromHwnd = ctypes.windll.oleacc.GetProcessHandleFromHwnd - else: # Linux's inotify doesn't work over CIFS or NFS, so poll FileSystemEventHandler = object # dummy @@ -113,6 +111,13 @@ class EDLogs(FileSystemEventHandler): # be >= for Live, and < for Legacy. self.live_galaxy_base_version = semantic_version.Version('4.0.0') + if sys.platform == 'win32': + # Get the SID of the user we're running as for later use in + # `game_running()` + self.user_sid, self.user_domain, self.user_type = win32security.LookupAccountName( + None, environ['USERNAME'] + ) + self.__init_state() def __init_state(self) -> None: @@ -2123,23 +2128,57 @@ class EDLogs(FileSystemEventHandler): if sys.platform == 'win32': def WindowTitle(h): # noqa: N802 if h: - length = win32gui.GetWindowTextLength(h) + 1 - buf = ctypes.create_unicode_buffer(length) - if win32gui.GetWindowText(h): - return buf.value + return win32gui.GetWindowText(h) return None - def callback(hWnd, lParam): # noqa: N803 - name = WindowTitle(hWnd) + def callback(hwnd, hwnds): # noqa: N803 + name = WindowTitle(hwnd) if name and name.startswith('Elite - Dangerous'): - handle = GetProcessHandleFromHwnd(hWnd) - if handle: # If GetProcessHandleFromHwnd succeeds then the app is already running as this user - win32api.CloseHandle(handle) - return False # stop enumeration + # We've found a window that *looks* like an ED game process, but now we need to check + # if it's owned by the current user. + + # Get the process_id of the window we found + # + thread_id, process_id = win32process.GetWindowThreadProcessId(hwnd) + + # Use that to get a process handle + # + # The first arg can't simply be `0`, and `win32con.PROCESS_TERMINATE` works + handle = win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION, False, process_id) + if handle: + # We got the handle OK, now we need a token for it + process_token = win32security.OpenProcessToken(handle, win32security.TOKEN_QUERY) + # So we can use that to get information about the User + token_information, i = win32security.GetTokenInformation( + process_token, win32security.TokenUser + ) + # And lastly check if token_information, which should be a PySID object, matches + # that of the current user we looked up in `__init__()`. + if token_information == self.user_sid: + # This can be used to convert the token to username, domain name, and account type + # user, domain, name_use = win32security.LookupAccountSid(None, token_information) + hwnds.append(hwnd) + return False # Indicate window found, so stop iterating return True - return not win32gui.EnumWindows(EnumWindowsProc(callback), 0) + # Ref: + ed_windows: list[int] = [] + try: + win32gui.EnumWindows(callback, ed_windows) + + except pywintypes.error as e: + # Ref: + # Because False is returned in the callback to indicate "found the window, stop + # processing", this causes EnumWindows() to return `0`, which is generically + # treated as an error, so exception is raised. + # So, check the exception's .winerror, and ignore if `0`. + if e.winerror != 0: + logger.exception("EnumWindows exception:") + + bacon = bool(ed_windows) + print(bacon) + return bacon return False