Move window trackers into the class, get rid of more globals
This commit is contained in:
@@ -4,6 +4,14 @@ import argparse
|
|||||||
import textwrap
|
import textwrap
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
|
||||||
|
def str2bool(value):
|
||||||
|
if value.lower() == 'true':
|
||||||
|
return True
|
||||||
|
elif value.lower() == 'false':
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
raise argparse.ArgumentTypeError('Boolean value expected.')
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(prog='owocr', description=textwrap.dedent('''\
|
parser = argparse.ArgumentParser(prog='owocr', description=textwrap.dedent('''\
|
||||||
Runs OCR in the background.
|
Runs OCR in the background.
|
||||||
It can read images copied to the system clipboard or placed in a directory, images sent via a websocket or a Unix domain socket, or directly capture a screen (or a portion of it) or a window.
|
It can read images copied to the system clipboard or placed in a directory, images sent via a websocket or a Unix domain socket, or directly capture a screen (or a portion of it) or a window.
|
||||||
@@ -36,7 +44,7 @@ parser.add_argument('-sa', '--screen_capture_area', type=str, default=argparse.S
|
|||||||
help='Area to target when reading with screen capture. Can be either empty (automatic selector), a set of coordinates (x,y,width,height), "screen_N" (captures a whole screen, where N is the screen number starting from 1) or a window name (the first matching window title will be used).')
|
help='Area to target when reading with screen capture. Can be either empty (automatic selector), a set of coordinates (x,y,width,height), "screen_N" (captures a whole screen, where N is the screen number starting from 1) or a window name (the first matching window title will be used).')
|
||||||
parser.add_argument('-sd', '--screen_capture_delay_secs', type=float, default=argparse.SUPPRESS,
|
parser.add_argument('-sd', '--screen_capture_delay_secs', type=float, default=argparse.SUPPRESS,
|
||||||
help='Delay (in seconds) between screenshots when reading with screen capture.')
|
help='Delay (in seconds) between screenshots when reading with screen capture.')
|
||||||
parser.add_argument('-sw', '--screen_capture_only_active_windows', action='store_true', default=argparse.SUPPRESS,
|
parser.add_argument('-sw', '--screen_capture_only_active_windows', type=str2bool, default=argparse.SUPPRESS,
|
||||||
help="When reading with screen capture and screen_capture_area is a window name, only target the window while it's active.")
|
help="When reading with screen capture and screen_capture_area is a window name, only target the window while it's active.")
|
||||||
parser.add_argument('-sc', '--screen_capture_combo', type=str, default=argparse.SUPPRESS,
|
parser.add_argument('-sc', '--screen_capture_combo', type=str, default=argparse.SUPPRESS,
|
||||||
help='When reading with screen capture, combo to wait on for taking a screenshot instead of using the delay. As an example: "<ctrl>+<shift>+s". The list of keys can be found here: https://pynput.readthedocs.io/en/latest/keyboard.html#pynput.keyboard.Key')
|
help='When reading with screen capture, combo to wait on for taking a screenshot instead of using the delay. As an example: "<ctrl>+<shift>+s". The list of keys can be found here: https://pynput.readthedocs.io/en/latest/keyboard.html#pynput.keyboard.Key')
|
||||||
|
|||||||
205
owocr/run.py
205
owocr/run.py
@@ -296,92 +296,6 @@ class RequestHandler(socketserver.BaseRequestHandler):
|
|||||||
conn.sendall(b'False')
|
conn.sendall(b'False')
|
||||||
|
|
||||||
|
|
||||||
class MacOSWindowTracker(threading.Thread):
|
|
||||||
def __init__(self, window_id):
|
|
||||||
super().__init__(daemon=True)
|
|
||||||
self.stop = False
|
|
||||||
self.window_id = window_id
|
|
||||||
self.window_active = screencapture_window_active
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
found = True
|
|
||||||
while found and not self.stop:
|
|
||||||
found = False
|
|
||||||
is_active = False
|
|
||||||
with objc.autorelease_pool():
|
|
||||||
window_list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID)
|
|
||||||
for i, window in enumerate(window_list):
|
|
||||||
if found and window.get(kCGWindowName, '') == 'Fullscreen Backdrop':
|
|
||||||
is_active = True
|
|
||||||
break
|
|
||||||
if self.window_id == window['kCGWindowNumber']:
|
|
||||||
found = True
|
|
||||||
if i == 0 or window_list[i-1].get(kCGWindowName, '') in ('Dock', 'Color Enforcer Window'):
|
|
||||||
is_active = True
|
|
||||||
break
|
|
||||||
if not found:
|
|
||||||
window_list = CGWindowListCreateDescriptionFromArray([self.window_id])
|
|
||||||
if len(window_list) > 0:
|
|
||||||
found = True
|
|
||||||
if found and self.window_active != is_active:
|
|
||||||
on_window_activated(is_active)
|
|
||||||
self.window_active = is_active
|
|
||||||
time.sleep(0.2)
|
|
||||||
if not found:
|
|
||||||
on_window_closed(False)
|
|
||||||
|
|
||||||
|
|
||||||
class WindowsWindowTracker(threading.Thread):
|
|
||||||
def __init__(self, window_handle, only_active):
|
|
||||||
super().__init__(daemon=True)
|
|
||||||
self.stop = False
|
|
||||||
self.window_handle = window_handle
|
|
||||||
self.only_active = only_active
|
|
||||||
self.window_active = screencapture_window_active
|
|
||||||
self.window_minimized = not screencapture_window_visible
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
found = True
|
|
||||||
while not self.stop:
|
|
||||||
found = win32gui.IsWindow(self.window_handle)
|
|
||||||
if not found:
|
|
||||||
break
|
|
||||||
if self.only_active:
|
|
||||||
is_active = self.window_handle == win32gui.GetForegroundWindow()
|
|
||||||
if self.window_active != is_active:
|
|
||||||
on_window_activated(is_active)
|
|
||||||
self.window_active = is_active
|
|
||||||
else:
|
|
||||||
is_minimized = win32gui.IsIconic(self.window_handle)
|
|
||||||
if self.window_minimized != is_minimized:
|
|
||||||
on_window_minimized(is_minimized)
|
|
||||||
self.window_minimized = is_minimized
|
|
||||||
time.sleep(0.2)
|
|
||||||
if not found:
|
|
||||||
on_window_closed(False)
|
|
||||||
|
|
||||||
|
|
||||||
def get_windows_window_handle(window_title):
|
|
||||||
def callback(hwnd, window_title_part):
|
|
||||||
window_title = win32gui.GetWindowText(hwnd)
|
|
||||||
if window_title_part in window_title:
|
|
||||||
handles.append((hwnd, window_title))
|
|
||||||
return True
|
|
||||||
|
|
||||||
handle = win32gui.FindWindow(None, window_title)
|
|
||||||
if handle:
|
|
||||||
return (handle, window_title)
|
|
||||||
|
|
||||||
handles = []
|
|
||||||
win32gui.EnumWindows(callback, window_title)
|
|
||||||
for handle in handles:
|
|
||||||
_, pid = win32process.GetWindowThreadProcessId(handle[0])
|
|
||||||
if psutil.Process(pid).name().lower() not in ('cmd.exe', 'powershell.exe', 'windowsterminal.exe'):
|
|
||||||
return handle
|
|
||||||
|
|
||||||
return (None, None)
|
|
||||||
|
|
||||||
|
|
||||||
class TextFiltering:
|
class TextFiltering:
|
||||||
accurate_filtering = False
|
accurate_filtering = False
|
||||||
|
|
||||||
@@ -453,8 +367,10 @@ class TextFiltering:
|
|||||||
class ScreenshotClass:
|
class ScreenshotClass:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
screen_capture_area = config.get_general('screen_capture_area')
|
screen_capture_area = config.get_general('screen_capture_area')
|
||||||
self.macos_window_tracker = None
|
self.macos_window_tracker_instance = None
|
||||||
self.windows_window_tracker = None
|
self.windows_window_tracker_instance = None
|
||||||
|
self.screencapture_window_active = True
|
||||||
|
self.screencapture_window_visible = True
|
||||||
if screen_capture_area == '':
|
if screen_capture_area == '':
|
||||||
self.screencapture_mode = 0
|
self.screencapture_mode = 0
|
||||||
elif screen_capture_area.startswith('screen_'):
|
elif screen_capture_area.startswith('screen_'):
|
||||||
@@ -501,15 +417,14 @@ class ScreenshotClass:
|
|||||||
self.sct_params = {'top': coord_top, 'left': coord_left, 'width': coord_width, 'height': coord_height}
|
self.sct_params = {'top': coord_top, 'left': coord_left, 'width': coord_width, 'height': coord_height}
|
||||||
logger.opt(ansi=True).info(f'Selected coordinates: {coord_left},{coord_top},{coord_width},{coord_height}')
|
logger.opt(ansi=True).info(f'Selected coordinates: {coord_left},{coord_top},{coord_width},{coord_height}')
|
||||||
else:
|
else:
|
||||||
screen_capture_only_active_windows = config.get_general('screen_capture_only_active_windows')
|
self.screen_capture_only_active_windows = config.get_general('screen_capture_only_active_windows')
|
||||||
area_invalid_error = '"screen_capture_area" must be empty, "screen_N" where N is a screen number starting from 1, a valid set of coordinates, or a valid window name'
|
area_invalid_error = '"screen_capture_area" must be empty, "screen_N" where N is a screen number starting from 1, a valid set of coordinates, or a valid window name'
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
if config.get_general('screen_capture_old_macos_api') or int(platform.mac_ver()[0].split('.')[0]) < 14:
|
if config.get_general('screen_capture_old_macos_api') or int(platform.mac_ver()[0].split('.')[0]) < 14:
|
||||||
self.old_macos_screenshot_api = True
|
self.old_macos_screenshot_api = True
|
||||||
else:
|
else:
|
||||||
self.old_macos_screenshot_api = False
|
self.old_macos_screenshot_api = False
|
||||||
global screencapturekit_queue
|
self.screencapturekit_queue = queue.Queue()
|
||||||
screencapturekit_queue = queue.Queue()
|
|
||||||
CGMainDisplayID()
|
CGMainDisplayID()
|
||||||
window_list = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)
|
window_list = CGWindowListCopyWindowInfo(kCGWindowListExcludeDesktopElements, kCGNullWindowID)
|
||||||
window_titles = []
|
window_titles = []
|
||||||
@@ -535,36 +450,68 @@ class ScreenshotClass:
|
|||||||
self.window_id = window_ids[window_index]
|
self.window_id = window_ids[window_index]
|
||||||
window_title = window_titles[window_index]
|
window_title = window_titles[window_index]
|
||||||
|
|
||||||
if screen_capture_only_active_windows:
|
if self.screen_capture_only_active_windows:
|
||||||
self.macos_window_tracker = MacOSWindowTracker(self.window_id)
|
self.macos_window_tracker_instance = threading.Thread(target=self.macos_window_tracker)
|
||||||
self.macos_window_tracker.start()
|
self.macos_window_tracker_instance.start()
|
||||||
logger.opt(ansi=True).info(f'Selected window: {window_title}')
|
logger.opt(ansi=True).info(f'Selected window: {window_title}')
|
||||||
elif sys.platform == 'win32':
|
elif sys.platform == 'win32':
|
||||||
self.window_handle, window_title = get_windows_window_handle(screen_capture_area)
|
self.window_handle, window_title = self.get_windows_window_handle(screen_capture_area)
|
||||||
|
|
||||||
if not self.window_handle:
|
if not self.window_handle:
|
||||||
raise ValueError(area_invalid_error)
|
raise ValueError(area_invalid_error)
|
||||||
|
|
||||||
ctypes.windll.shcore.SetProcessDpiAwareness(1)
|
ctypes.windll.shcore.SetProcessDpiAwareness(1)
|
||||||
|
|
||||||
self.windows_window_tracker = WindowsWindowTracker(self.window_handle, screen_capture_only_active_windows)
|
self.windows_window_tracker_instance = threading.Thread(target=self.windows_window_tracker)
|
||||||
self.windows_window_tracker.start()
|
self.windows_window_tracker_instance.start()
|
||||||
logger.opt(ansi=True).info(f'Selected window: {window_title}')
|
logger.opt(ansi=True).info(f'Selected window: {window_title}')
|
||||||
else:
|
else:
|
||||||
raise ValueError('Window capture is only currently supported on Windows and macOS')
|
raise ValueError('Window capture is only currently supported on Windows and macOS')
|
||||||
|
|
||||||
def __del__(self):
|
def __del__(self):
|
||||||
if self.macos_window_tracker:
|
if self.macos_window_tracker_instance:
|
||||||
self.macos_window_tracker.stop = True
|
self.macos_window_tracker_instance.join()
|
||||||
self.macos_window_tracker.join()
|
elif self.windows_window_tracker_instance:
|
||||||
elif self.windows_window_tracker:
|
self.windows_window_tracker_instance.join()
|
||||||
self.windows_window_tracker.stop = True
|
|
||||||
self.windows_window_tracker.join()
|
def get_windows_window_handle(self, window_title):
|
||||||
|
def callback(hwnd, window_title_part):
|
||||||
|
window_title = win32gui.GetWindowText(hwnd)
|
||||||
|
if window_title_part in window_title:
|
||||||
|
handles.append((hwnd, window_title))
|
||||||
|
return True
|
||||||
|
|
||||||
|
handle = win32gui.FindWindow(None, window_title)
|
||||||
|
if handle:
|
||||||
|
return (handle, window_title)
|
||||||
|
|
||||||
|
handles = []
|
||||||
|
win32gui.EnumWindows(callback, window_title)
|
||||||
|
for handle in handles:
|
||||||
|
_, pid = win32process.GetWindowThreadProcessId(handle[0])
|
||||||
|
if psutil.Process(pid).name().lower() not in ('cmd.exe', 'powershell.exe', 'windowsterminal.exe'):
|
||||||
|
return handle
|
||||||
|
|
||||||
|
return (None, None)
|
||||||
|
|
||||||
|
def windows_window_tracker(self):
|
||||||
|
found = True
|
||||||
|
while not terminated:
|
||||||
|
found = win32gui.IsWindow(self.window_handle)
|
||||||
|
if not found:
|
||||||
|
break
|
||||||
|
if self.screen_capture_only_active_windows:
|
||||||
|
self.screencapture_window_active = self.window_handle == win32gui.GetForegroundWindow()
|
||||||
|
else:
|
||||||
|
self.screencapture_window_visible = not win32gui.IsIconic(self.window_handle)
|
||||||
|
time.sleep(0.2)
|
||||||
|
if not found:
|
||||||
|
on_window_closed(False)
|
||||||
|
|
||||||
def capture_macos_window_screenshot(self, window_id):
|
def capture_macos_window_screenshot(self, window_id):
|
||||||
def shareable_content_completion_handler(shareable_content, error):
|
def shareable_content_completion_handler(shareable_content, error):
|
||||||
if error:
|
if error:
|
||||||
screencapturekit_queue.put(None)
|
self.screencapturekit_queue.put(None)
|
||||||
return
|
return
|
||||||
|
|
||||||
target_window = None
|
target_window = None
|
||||||
@@ -574,7 +521,7 @@ class ScreenshotClass:
|
|||||||
break
|
break
|
||||||
|
|
||||||
if not target_window:
|
if not target_window:
|
||||||
screencapturekit_queue.put(None)
|
self.screencapturekit_queue.put(None)
|
||||||
return
|
return
|
||||||
|
|
||||||
with objc.autorelease_pool():
|
with objc.autorelease_pool():
|
||||||
@@ -598,15 +545,41 @@ class ScreenshotClass:
|
|||||||
|
|
||||||
def capture_image_completion_handler(image, error):
|
def capture_image_completion_handler(image, error):
|
||||||
if error:
|
if error:
|
||||||
screencapturekit_queue.put(None)
|
self.screencapturekit_queue.put(None)
|
||||||
return
|
return
|
||||||
|
|
||||||
screencapturekit_queue.put(image)
|
self.screencapturekit_queue.put(image)
|
||||||
|
|
||||||
SCShareableContent.getShareableContentWithCompletionHandler_(
|
SCShareableContent.getShareableContentWithCompletionHandler_(
|
||||||
shareable_content_completion_handler
|
shareable_content_completion_handler
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def macos_window_tracker(self):
|
||||||
|
found = True
|
||||||
|
while found and not terminated:
|
||||||
|
found = False
|
||||||
|
is_active = False
|
||||||
|
with objc.autorelease_pool():
|
||||||
|
window_list = CGWindowListCopyWindowInfo(kCGWindowListOptionOnScreenOnly, kCGNullWindowID)
|
||||||
|
for i, window in enumerate(window_list):
|
||||||
|
if found and window.get(kCGWindowName, '') == 'Fullscreen Backdrop':
|
||||||
|
is_active = True
|
||||||
|
break
|
||||||
|
if self.window_id == window['kCGWindowNumber']:
|
||||||
|
found = True
|
||||||
|
if i == 0 or window_list[i-1].get(kCGWindowName, '') in ('Dock', 'Color Enforcer Window'):
|
||||||
|
is_active = True
|
||||||
|
break
|
||||||
|
if not found:
|
||||||
|
window_list = CGWindowListCreateDescriptionFromArray([self.window_id])
|
||||||
|
if len(window_list) > 0:
|
||||||
|
found = True
|
||||||
|
if found:
|
||||||
|
self.screencapture_window_active = is_active
|
||||||
|
time.sleep(0.2)
|
||||||
|
if not found:
|
||||||
|
on_window_closed(False)
|
||||||
|
|
||||||
def __call__(self):
|
def __call__(self):
|
||||||
if self.screencapture_mode == 2:
|
if self.screencapture_mode == 2:
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
@@ -616,7 +589,7 @@ class ScreenshotClass:
|
|||||||
else:
|
else:
|
||||||
self.capture_macos_window_screenshot(self.window_id)
|
self.capture_macos_window_screenshot(self.window_id)
|
||||||
try:
|
try:
|
||||||
cg_image = screencapturekit_queue.get(timeout=0.5)
|
cg_image = self.screencapturekit_queue.get(timeout=0.5)
|
||||||
except queue.Empty:
|
except queue.Empty:
|
||||||
cg_image = None
|
cg_image = None
|
||||||
if not cg_image:
|
if not cg_image:
|
||||||
@@ -783,16 +756,6 @@ def on_window_closed(alive):
|
|||||||
terminated = True
|
terminated = True
|
||||||
|
|
||||||
|
|
||||||
def on_window_activated(active):
|
|
||||||
global screencapture_window_active
|
|
||||||
screencapture_window_active = active
|
|
||||||
|
|
||||||
|
|
||||||
def on_window_minimized(minimized):
|
|
||||||
global screencapture_window_visible
|
|
||||||
screencapture_window_visible = not minimized
|
|
||||||
|
|
||||||
|
|
||||||
def on_screenshot_combo():
|
def on_screenshot_combo():
|
||||||
if not paused:
|
if not paused:
|
||||||
img = take_screenshot()
|
img = take_screenshot()
|
||||||
@@ -917,11 +880,7 @@ def run():
|
|||||||
websocket_server_thread = WebsocketServerThread('websocket' in (read_from, read_from_secondary))
|
websocket_server_thread = WebsocketServerThread('websocket' in (read_from, read_from_secondary))
|
||||||
websocket_server_thread.start()
|
websocket_server_thread.start()
|
||||||
if 'screencapture' in (read_from, read_from_secondary):
|
if 'screencapture' in (read_from, read_from_secondary):
|
||||||
global screencapture_window_active
|
|
||||||
global screencapture_window_visible
|
|
||||||
global take_screenshot
|
global take_screenshot
|
||||||
screencapture_window_active = False
|
|
||||||
screencapture_window_visible = True
|
|
||||||
screen_capture_delay_secs = config.get_general('screen_capture_delay_secs')
|
screen_capture_delay_secs = config.get_general('screen_capture_delay_secs')
|
||||||
screen_capture_combo = config.get_general('screen_capture_combo')
|
screen_capture_combo = config.get_general('screen_capture_combo')
|
||||||
last_screenshot_time = 0
|
last_screenshot_time = 0
|
||||||
@@ -992,7 +951,7 @@ def run():
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
if (not img) and process_screenshots:
|
if (not img) and process_screenshots:
|
||||||
if (not paused) and screencapture_window_active and screencapture_window_visible and (time.time() - last_screenshot_time) > screen_capture_delay_secs:
|
if (not paused) and take_screenshot.screencapture_window_active and take_screenshot.screencapture_window_visible and (time.time() - last_screenshot_time) > screen_capture_delay_secs:
|
||||||
img = take_screenshot()
|
img = take_screenshot()
|
||||||
filter_img = True
|
filter_img = True
|
||||||
notify = False
|
notify = False
|
||||||
|
|||||||
Reference in New Issue
Block a user