GameSentenceMiner 2.7.8__py3-none-any.whl → 2.7.10__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,6 @@
1
- # area_selector.py
2
1
  import asyncio
3
- import base64
4
2
  import difflib
3
+ import json
5
4
  import logging
6
5
  import os
7
6
  import queue
@@ -9,52 +8,62 @@ import threading
9
8
  import time
10
9
  from datetime import datetime
11
10
  from logging.handlers import RotatingFileHandler
11
+ from pathlib import Path
12
12
  from tkinter import messagebox
13
13
 
14
14
  import mss
15
15
  import websockets
16
+ from rapidfuzz import fuzz
16
17
  from PIL import Image, ImageDraw
17
- import json
18
- from pathlib import Path
19
18
 
20
19
  from GameSentenceMiner import obs, util
21
20
  from GameSentenceMiner.configuration import get_config, get_app_directory
22
- from GameSentenceMiner.gametext import get_line_history
21
+ from GameSentenceMiner.electron_config import get_ocr_scan_rate, get_requires_open_window
22
+ from GameSentenceMiner.ocr.gsm_ocr_config import OCRConfig, Rectangle
23
23
  from GameSentenceMiner.owocr.owocr import screen_coordinate_picker, run
24
24
  from GameSentenceMiner.owocr.owocr.run import TextFiltering
25
- from GameSentenceMiner.electron_config import store, get_ocr_scan_rate, get_requires_open_window
25
+
26
+ from dataclasses import dataclass
27
+ from typing import List, Optional
26
28
 
27
29
  CONFIG_FILE = Path("ocr_config.json")
28
30
  DEFAULT_IMAGE_PATH = r"C:\Users\Beangate\Pictures\msedge_acbl8GL7Ax.jpg" # CHANGE THIS
29
-
30
31
  logger = logging.getLogger("GSM_OCR")
31
32
  logger.setLevel(logging.DEBUG)
32
-
33
33
  # Create a file handler for logging
34
34
  log_file = os.path.join(get_app_directory(), "logs", "ocr_log.txt")
35
35
  os.makedirs(os.path.join(get_app_directory(), "logs"), exist_ok=True)
36
-
37
36
  file_handler = RotatingFileHandler(log_file, maxBytes=1024 * 1024, backupCount=5, encoding='utf-8')
38
37
  file_handler.setLevel(logging.DEBUG)
39
-
40
38
  # Create a formatter and set it for the handler
41
39
  formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
42
40
  file_handler.setFormatter(formatter)
43
-
44
41
  # Add the handler to the logger
45
42
  logger.addHandler(file_handler)
46
43
 
44
+ console_handler = logging.StreamHandler()
45
+ console_handler.setLevel(logging.INFO)
46
+ console_handler.setFormatter(formatter)
47
+ logger.addHandler(console_handler)
48
+
49
+
47
50
  def get_new_game_cords():
48
51
  """Allows multiple coordinate selections."""
49
52
  coords_list = []
50
- while True:
51
- cords = screen_coordinate_picker.get_screen_selection()
52
- coords_list.append({"coordinates": cords})
53
- if messagebox.askyesno("Add Another Region", "Do you want to add another region?"):
54
- continue
55
- else:
56
- break
57
-
53
+ with mss.mss() as sct:
54
+ monitors = sct.monitors
55
+ monitor_map = {i: mon for i, mon in enumerate(monitors)}
56
+ while True:
57
+ selected_monitor_index, cords = screen_coordinate_picker.get_screen_selection_with_monitor(monitor_map)
58
+ selected_monitor = monitor_map[selected_monitor_index]
59
+ coords_list.append({"monitor": {"left": selected_monitor["left"], "top": selected_monitor["top"],
60
+ "width": selected_monitor["width"], "height": selected_monitor["height"],
61
+ "index": selected_monitor_index}, "coordinates": cords,
62
+ "is_excluded": False})
63
+ if messagebox.askyesno("Add Another Region", "Do you want to add another region?"):
64
+ continue
65
+ else:
66
+ break
58
67
  app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
59
68
  ocr_config_dir = app_dir / "ocr_config"
60
69
  ocr_config_dir.mkdir(parents=True, exist_ok=True)
@@ -62,13 +71,13 @@ def get_new_game_cords():
62
71
  scene = util.sanitize_filename(obs.get_current_scene())
63
72
  config_path = ocr_config_dir / f"{scene}.json"
64
73
  with open(config_path, 'w') as f:
65
- json.dump(coords_list, f, indent=4)
74
+ json.dump({"scene": scene, "window": None, "rectangles": coords_list}, f, indent=4)
66
75
  print(f"Saved OCR config to {config_path}")
67
76
  return coords_list
68
77
 
69
78
 
70
- def get_ocr_config():
71
- """Loads multiple screen capture areas from the corresponding JSON file."""
79
+ def get_ocr_config() -> OCRConfig:
80
+ """Loads and updates screen capture areas from the corresponding JSON file."""
72
81
  app_dir = Path.home() / "AppData" / "Roaming" / "GameSentenceMiner"
73
82
  ocr_config_dir = app_dir / "ocr_config"
74
83
  obs.connect_to_obs()
@@ -76,14 +85,58 @@ def get_ocr_config():
76
85
  config_path = ocr_config_dir / f"{scene}.json"
77
86
  if not config_path.exists():
78
87
  raise Exception(f"No config file found at {config_path}.")
79
-
80
- if not config_path.exists():
81
- print("Config Screen picker failed to make file. Please run again.")
82
- return
83
-
84
- with open(config_path, 'r') as f:
85
- coords_list = json.load(f)
86
- return coords_list
88
+ try:
89
+ with open(config_path, 'r', encoding="utf-8") as f:
90
+ config_data = json.load(f)
91
+ if "rectangles" in config_data and isinstance(config_data["rectangles"], list) and all(
92
+ isinstance(item, list) and len(item) == 4 for item in config_data["rectangles"]):
93
+ # Old config format, convert to new
94
+ new_rectangles = []
95
+ with mss.mss() as sct:
96
+ monitors = sct.monitors
97
+ default_monitor = monitors[1] if len(monitors) > 1 else monitors[0]
98
+ for rect in config_data["rectangles"]:
99
+ new_rectangles.append({
100
+ "monitor": {
101
+ "left": default_monitor["left"],
102
+ "top": default_monitor["top"],
103
+ "width": default_monitor["width"],
104
+ "height": default_monitor["height"],
105
+ "index": 0 # Assuming single monitor for old config
106
+ },
107
+ "coordinates": rect,
108
+ "is_excluded": False
109
+ })
110
+ for rect in config_data['excluded_rectangles']:
111
+ new_rectangles.append({
112
+ "monitor": {
113
+ "left": default_monitor["left"],
114
+ "top": default_monitor["top"],
115
+ "width": default_monitor["width"],
116
+ "height": default_monitor["height"],
117
+ "index": 0 # Assuming single monitor for old config
118
+ },
119
+ "coordinates": rect,
120
+ "is_excluded": True
121
+ })
122
+ new_config_data = {"scene": config_data.get("scene", scene), "window": config_data.get("window", None),
123
+ "rectangles": new_rectangles}
124
+ with open(config_path, 'w', encoding="utf-8") as f:
125
+ json.dump(new_config_data, f, indent=4)
126
+ return OCRConfig.from_dict(new_config_data)
127
+ elif "rectangles" in config_data and isinstance(config_data["rectangles"], list) and all(
128
+ isinstance(item, dict) and "coordinates" in item for item in config_data["rectangles"]):
129
+ logger.info("Loading new OCR config format.")
130
+ logger.info(config_data)
131
+ return OCRConfig.from_dict(config_data)
132
+ else:
133
+ raise Exception(f"Invalid config format in {config_path}.")
134
+ except json.JSONDecodeError:
135
+ print("Error decoding JSON. Please check your config file.")
136
+ return None
137
+ except Exception as e:
138
+ print(f"Error loading config: {e}")
139
+ return None
87
140
 
88
141
 
89
142
  websocket_server_thread = None
@@ -130,7 +183,8 @@ class WebsocketServerThread(threading.Thread):
130
183
 
131
184
  def send_text(self, text, line_time: datetime):
132
185
  if text:
133
- return asyncio.run_coroutine_threadsafe(self.send_text_coroutine(json.dumps({"sentence": text, "time": line_time.isoformat()})), self.loop)
186
+ return asyncio.run_coroutine_threadsafe(
187
+ self.send_text_coroutine(json.dumps({"sentence": text, "time": line_time.isoformat()})), self.loop)
134
188
 
135
189
  def stop_server(self):
136
190
  self.loop.call_soon_threadsafe(self._stop_event.set)
@@ -140,129 +194,119 @@ class WebsocketServerThread(threading.Thread):
140
194
  self._loop = asyncio.get_running_loop()
141
195
  self._stop_event = stop_event = asyncio.Event()
142
196
  self._event.set()
143
- self.server = start_server = websockets.serve(self.server_handler, get_config().general.websocket_uri.split(":")[0], get_config().general.websocket_uri.split(":")[1], max_size=1000000000)
197
+ self.server = start_server = websockets.serve(self.server_handler,
198
+ get_config().general.websocket_uri.split(":")[0],
199
+ get_config().general.websocket_uri.split(":")[1],
200
+ max_size=1000000000)
144
201
  async with start_server:
145
202
  await stop_event.wait()
203
+
146
204
  asyncio.run(main())
147
205
 
206
+
148
207
  all_cords = None
149
208
  rectangles = None
150
209
 
151
- def text_callback(text, rectangle, time):
152
- global twopassocr, ocr2, last_oneocr_results
210
+ def do_second_ocr(ocr1_text, rectangle_index, time, img):
211
+ global twopassocr, ocr2, last_ocr1_results, last_ocr2_results
212
+ last_result = ([], -1)
213
+
214
+ previous_ocr1_text = last_ocr1_results[rectangle_index]
215
+ if fuzz.ratio(previous_ocr1_text, ocr1_text) >= 80:
216
+ logger.info("Seems like the same text, not doing 2nd pass")
217
+ last_ocr1_results[rectangle_index] = ocr1_text
218
+ try:
219
+ orig_text, text = run.process_and_write_results(img, None, None, last_result, TextFiltering(),
220
+ engine=ocr2)
221
+ previous_ocr2_text = last_ocr2_results[rectangle_index]
222
+ if fuzz.ratio(previous_ocr2_text, text) >= 80:
223
+ logger.info("Seems like the same text from previous ocr2 result, not sending")
224
+ return
225
+ last_ocr2_results[rectangle_index] = text
226
+ if get_config().advanced.ocr_sends_to_clipboard:
227
+ import pyperclip
228
+ pyperclip.copy(text)
229
+ websocket_server_thread.send_text(text, time)
230
+ except json.JSONDecodeError:
231
+ print("Invalid JSON received.")
232
+ except Exception as e:
233
+ logger.exception(e)
234
+ print(f"Error processing message: {e}")
235
+
236
+
237
+ last_oneocr_results_to_check = {} # Store last OCR result for each rectangle
238
+ last_oneocr_times = {} # Store last OCR time for each rectangle
239
+ text_stable_start_times = {} # Store the start time when text becomes stable for each rectangle
240
+ TEXT_APPEARENCE_DELAY = get_ocr_scan_rate() * 1000 + 500 # Adjust as needed
241
+
242
+ def text_callback(text, rectangle_index, time, img=None):
243
+ global twopassocr, ocr2, last_oneocr_results_to_check, last_oneocr_times, text_stable_start_times
244
+
245
+ current_time = time if time else datetime.now()
246
+
247
+ previous_text = last_oneocr_results_to_check.get(rectangle_index, "")
248
+
153
249
  if not text:
250
+ if previous_text:
251
+ if rectangle_index in text_stable_start_times:
252
+ stable_time = text_stable_start_times[rectangle_index]
253
+ if twopassocr:
254
+ do_second_ocr(previous_text, rectangle_index, time, img)
255
+ else:
256
+ previous_ocr1_text = last_ocr1_results[rectangle_index]
257
+ if fuzz.ratio(previous_ocr1_text, text) >= 80:
258
+ logger.info("Seems like the same text, not sending")
259
+ last_ocr1_results[rectangle_index] = text
260
+ websocket_server_thread.send_text(previous_text, stable_time)
261
+ del text_stable_start_times[rectangle_index]
262
+ del last_oneocr_results_to_check[rectangle_index]
154
263
  return
155
- if not twopassocr or not ocr2:
156
- websocket_server_thread.send_text(text, time if time else datetime.now())
264
+
265
+ if rectangle_index not in last_oneocr_results_to_check:
266
+ last_oneocr_results_to_check[rectangle_index] = text
267
+ last_oneocr_times[rectangle_index] = current_time
268
+ text_stable_start_times[rectangle_index] = current_time
157
269
  return
158
- with mss.mss() as sct:
159
- line_time = time if time else datetime.now()
160
- logger.info(f"Received message: {text}, ATTEMPTING LENS OCR")
161
- if rectangles:
162
- cords = rectangles[rectangle]
163
- i = rectangle
164
- else:
165
- i = 0
166
- mon = sct.monitors
167
- cords = [mon[1]['left'], mon[1]['top'], mon[1]['width'], mon[1]['height']]
168
- similarity = difflib.SequenceMatcher(None, last_oneocr_results[i], text).ratio()
169
- if similarity > .8:
170
- return
171
- logger.debug(f"Similarity for region {i}: {similarity}")
172
- last_oneocr_results[i] = text
173
- last_result = ([], -1)
174
- try:
175
- sct_params = {'left': cords[0], 'top': cords[1], 'width': cords[2], 'height': cords[3]}
176
- sct_img = sct.grab(sct_params)
177
- img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
178
- img = img.convert("RGBA")
179
- draw = ImageDraw.Draw(img)
180
- for exclusion in ocr_config.get("excluded_rectangles", []):
181
- left, top, right, bottom = exclusion
182
- draw.rectangle((left, top, right, bottom), fill=(0, 0, 0, 0))
183
- # draw.rectangle((left, top, right, bottom), fill=(0,0,0))
184
- orig_text, text = run.process_and_write_results(img, None, None, last_result, TextFiltering(),
185
- engine=ocr2)
186
- if ":gsm_prefix:" in text:
187
- text = text.split(":gsm_prefix:")[1]
188
- if get_config().advanced.ocr_sends_to_clipboard:
189
- import pyperclip
190
- pyperclip.copy(text)
191
- websocket_server_thread.send_text(text, line_time)
192
- except json.JSONDecodeError:
193
- print("Invalid JSON received.")
194
- except Exception as e:
195
- logger.exception(e)
196
- print(f"Error processing message: {e}")
270
+
271
+ stable = text_stable_start_times.get(rectangle_index)
272
+
273
+ if stable:
274
+ time_since_stable_ms = int((current_time - stable).total_seconds() * 1000)
275
+
276
+ if time_since_stable_ms >= TEXT_APPEARENCE_DELAY:
277
+ last_oneocr_results_to_check[rectangle_index] = text
278
+ last_oneocr_times[rectangle_index] = current_time
279
+ else:
280
+ last_oneocr_results_to_check[rectangle_index] = text
281
+ last_oneocr_times[rectangle_index] = current_time
197
282
 
198
283
  done = False
199
284
 
200
- def run_oneocr(ocr_config, i):
285
+
286
+ def run_oneocr(ocr_config: OCRConfig, i):
201
287
  global done
288
+ rect_config = ocr_config.rectangles[i]
289
+ coords = rect_config.coordinates
290
+ monitor_config = rect_config.monitor
291
+ exclusions = (rect.coordinates for rect in list(filter(lambda x: x.is_excluded, ocr_config.rectangles)))
292
+ screen_area = ",".join(str(c) for c in coords)
202
293
  run.run(read_from="screencapture", write_to="callback",
203
- screen_capture_area=",".join(str(c) for c in ocr_config['rectangles'][i]) if ocr_config['rectangles'] else 'screen_1',
204
- screen_capture_window=ocr_config.get("window", None),
294
+ screen_capture_area=screen_area,
295
+ # screen_capture_monitor=monitor_config['index'],
296
+ screen_capture_window=ocr_config.window,
205
297
  screen_capture_only_active_windows=get_requires_open_window(),
206
298
  screen_capture_delay_secs=get_ocr_scan_rate(), engine=ocr1,
207
299
  text_callback=text_callback,
208
- screen_capture_exclusions=ocr_config.get('excluded_rectangles', None),
300
+ screen_capture_exclusions=exclusions,
209
301
  rectangle=i)
210
302
  done = True
211
303
 
212
304
 
213
- # async def websocket_client():
214
- # uri = "ws://localhost:7331" # Replace with your hosted websocket address
215
- # print("Connecting to WebSocket...")
216
- # async with websockets.connect(uri) as websocket:
217
- # print("Connected to WebSocket.")
218
- #
219
- # try:
220
- # while True:
221
- # message = await websocket.recv()
222
- # if not message:
223
- # continue
224
- # line_time = datetime.now()
225
- # get_line_history().add_secondary_line(message)
226
- # print(f"Received message: {message}, ATTEMPTING LENS OCR")
227
- # if ":gsm_prefix:" in message:
228
- # i = int(message.split(":gsm_prefix:")[0])
229
- # cords = all_cords[i] if i else all_cords[0]
230
- # similarity = difflib.SequenceMatcher(None, last_oneocr_results[i], message).ratio()
231
- # if similarity > .8:
232
- # continue
233
- # print(f"Similarity for region {i}: {similarity}")
234
- # last_oneocr_results[i] = message
235
- # last_result = ([], -1)
236
- # try:
237
- # sct_params = {'top': cords[1], 'left': cords[0], 'width': cords[2], 'height': cords[3]}
238
- # with mss.mss() as sct:
239
- # sct_img = sct.grab(sct_params)
240
- # img = Image.frombytes('RGB', sct_img.size, sct_img.bgra, 'raw', 'BGRX')
241
- # draw = ImageDraw.Draw(img)
242
- # for exclusion in ocr_config.get("excluded_rectangles", []):
243
- # exclusion = tuple(exclusion)
244
- # draw.rectangle(exclusion, fill="black")
245
- # orig_text, text = run.process_and_write_results(img, "results.txt", None, last_result, TextFiltering(), engine="glens")
246
- # if ":gsm_prefix:" in text:
247
- # text = text.split(":gsm_prefix:")[1]
248
- # websocket_server_thread.send_text(text, line_time)
249
- # except json.JSONDecodeError:
250
- # print("Invalid JSON received.")
251
- # except Exception as e:
252
- # logger.exception(e)
253
- # print(f"Error processing message: {e}")
254
- # except websockets.exceptions.ConnectionClosed:
255
- # print("WebSocket connection closed.")
256
- # except Exception as e:
257
- # print(f"WebSocket error: {e}")
258
-
259
-
260
305
  if __name__ == "__main__":
261
306
  global ocr1, ocr2, twopassocr
262
307
  import sys
263
308
 
264
309
  args = sys.argv[1:]
265
-
266
310
  if len(args) == 3:
267
311
  ocr1 = args[0]
268
312
  ocr2 = args[1]
@@ -277,34 +321,35 @@ if __name__ == "__main__":
277
321
  twopassocr = False
278
322
  else:
279
323
  ocr1 = "oneocr"
280
-
324
+ ocr2 = "glens"
325
+ twopassocr = True
281
326
  logger.info(f"Received arguments: ocr1={ocr1}, ocr2={ocr2}, twopassocr={twopassocr}")
282
327
  global ocr_config
283
- ocr_config = get_ocr_config()
284
- rectangles = ocr_config['rectangles']
285
- last_oneocr_results = [""] * len(rectangles) if rectangles else [""]
286
- oneocr_threads = []
287
- run.init_config(False)
288
- if rectangles:
289
- for i, rectangle in enumerate(rectangles):
290
- thread = threading.Thread(target=run_oneocr, args=(ocr_config,i,), daemon=True)
291
- oneocr_threads.append(thread)
292
- thread.start()
328
+ ocr_config: OCRConfig = get_ocr_config()
329
+ if ocr_config and ocr_config.rectangles:
330
+ rectangles = ocr_config.rectangles
331
+ last_ocr1_results = [""] * len(rectangles) if rectangles else [""]
332
+ last_ocr2_results = [""] * len(rectangles) if rectangles else [""]
333
+ oneocr_threads = []
334
+ run.init_config(False)
335
+ if rectangles:
336
+ for i, rectangle in enumerate(rectangles):
337
+ thread = threading.Thread(target=run_oneocr, args=(ocr_config, i,), daemon=True)
338
+ oneocr_threads.append(thread)
339
+ thread.start()
340
+ else:
341
+ single_ocr_thread = threading.Thread(target=run_oneocr, args=(ocr_config, 0,), daemon=True)
342
+ oneocr_threads.append(single_ocr_thread)
343
+ single_ocr_thread.start()
344
+ websocket_server_thread = WebsocketServerThread(read=True)
345
+ websocket_server_thread.start()
346
+ try:
347
+ while not done:
348
+ time.sleep(1)
349
+ except KeyboardInterrupt as e:
350
+ pass
351
+ for thread in oneocr_threads:
352
+ thread.join()
353
+ # asyncio.run(websocket_client())
293
354
  else:
294
- single_ocr_thread = threading.Thread(target=run_oneocr, args=(ocr_config, 0,), daemon=True)
295
- oneocr_threads.append(single_ocr_thread)
296
- single_ocr_thread.start()
297
-
298
- websocket_server_thread = WebsocketServerThread(read=True)
299
- websocket_server_thread.start()
300
-
301
- try:
302
- while not done:
303
- time.sleep(1)
304
- except KeyboardInterrupt as e:
305
- pass
306
-
307
- for thread in oneocr_threads:
308
- thread.join()
309
-
310
- # asyncio.run(websocket_client())
355
+ print("Failed to load OCR configuration. Please check the logs.")
@@ -571,7 +571,7 @@ def process_and_write_results(img_or_path, write_to, notifications, last_result,
571
571
  elif write_to == 'clipboard':
572
572
  pyperclipfix.copy(text)
573
573
  elif write_to == "callback":
574
- txt_callback(text, rectangle, start_time)
574
+ txt_callback(text, rectangle, start_time, img_or_path)
575
575
  elif write_to:
576
576
  with Path(write_to).open('a', encoding='utf-8') as f:
577
577
  f.write(text + '\n')
@@ -1026,6 +1026,7 @@ def run(read_from=None,
1026
1026
  else:
1027
1027
  try:
1028
1028
  coord_left, coord_top, right, bottom = win32gui.GetWindowRect(window_handle)
1029
+
1029
1030
  coord_width = right - coord_left
1030
1031
  coord_height = bottom - coord_top
1031
1032
 
@@ -1045,12 +1046,18 @@ def run(read_from=None,
1045
1046
  on_window_closed(False)
1046
1047
  break
1047
1048
 
1048
- rand = random.randint(1, 10)
1049
+ # rand = random.randint(1, 10)
1049
1050
 
1050
1051
  img = Image.frombuffer('RGB', (bmpinfo['bmWidth'], bmpinfo['bmHeight']), bmpstr, 'raw', 'BGRX', 0, 1)
1051
1052
 
1053
+ # if rand == 1:
1054
+ # img.show()
1055
+
1052
1056
  if custom_left:
1053
- img = img.crop((custom_left, custom_top, custom_width, custom_height))
1057
+ img = img.crop((custom_left, custom_top, custom_left + custom_width, custom_top + custom_height))
1058
+
1059
+ # if rand == 1:
1060
+ # img.show()
1054
1061
 
1055
1062
  win32gui.DeleteObject(save_bitmap.GetHandle())
1056
1063
  save_dc.DeleteDC()
@@ -1064,8 +1071,8 @@ def run(read_from=None,
1064
1071
  img = img.convert("RGBA")
1065
1072
  draw = ImageDraw.Draw(img)
1066
1073
  for exclusion in screen_capture_exclusions:
1067
- left, top, right, bottom = exclusion
1068
- draw.rectangle((left, top, right, bottom), fill=(0, 0, 0, 0))
1074
+ left, top, width, height = exclusion
1075
+ draw.rectangle((left, top, left + width, top + height), fill=(0, 0, 0, 0))
1069
1076
  # draw.rectangle((left, top, right, bottom), fill=(0, 0, 0))
1070
1077
  # img.save(os.path.join(get_temporary_directory(), 'screencapture.png'), 'png')
1071
1078
  res, _ = process_and_write_results(img, write_to, notifications, last_result, filtering, rectangle=rectangle, start_time=start_time)
@@ -55,12 +55,12 @@ class ScreenSelector:
55
55
  def on_release(event):
56
56
  nonlocal start_x, start_y
57
57
  end_x, end_y = event.x, event.y
58
-
59
- x1 = min(start_x, end_x)
60
- y1 = min(start_y, end_y)
61
- x2 = max(start_x, end_x)
62
- y2 = max(start_y, end_y)
63
-
58
+
59
+ x1 = min(start_x, end_x)
60
+ y1 = min(start_y, end_y)
61
+ x2 = max(start_x, end_x)
62
+ y2 = max(start_y, end_y)
63
+
64
64
  self.on_select(monitor, (x1, y1, x2 - x1, y2 - y1))
65
65
 
66
66
  canvas.bind('<ButtonPress-1>', on_click)
@@ -90,11 +90,19 @@ def get_screen_selection():
90
90
  with Manager() as manager:
91
91
  res = manager.dict()
92
92
  process = Process(target=run_screen_selector, args=(res,))
93
-
94
- process.start()
93
+
94
+ process.start()
95
95
  process.join()
96
96
 
97
97
  if 'monitor' in res and 'coordinates' in res:
98
98
  return res.copy()
99
99
  else:
100
100
  return False
101
+
102
+ if __name__ == "__main__":
103
+ selection = get_screen_selection()
104
+ if selection:
105
+ print(f"Selected monitor: {selection['monitor']}")
106
+ print(f"Selected coordinates: {selection['coordinates']}")
107
+ else:
108
+ print("No selection made or process was interrupted.")