Improve Element detection stability

This commit is contained in:
z060142 2025-04-19 13:00:22 +08:00
parent 3ec4017a1e
commit e25e3177c2
16 changed files with 197 additions and 17 deletions

View File

@ -52,6 +52,10 @@ Wolf Chat 是一個基於 MCP (Modular Capability Provider) 框架的聊天機
7. **視窗設定工具 (window-setup-script.py)** 7. **視窗設定工具 (window-setup-script.py)**
- 輔助工具,用於設置遊戲視窗的位置和大小 - 輔助工具,用於設置遊戲視窗的位置和大小
- 方便開發階段截取 UI 元素樣本 - 方便開發階段截取 UI 元素樣本
8. **視窗監視工具 (window-monitor-script.py)**
- (新增) 強化腳本,用於持續監視遊戲視窗
- 確保目標視窗維持在最上層 (Always on Top)
- 自動將視窗移回指定的位置
### 資料流程 ### 資料流程
@ -283,6 +287,21 @@ Wolf Chat 是一個基於 MCP (Modular Capability Provider) 框架的聊天機
- 如果重新定位成功,則後續所有基於氣泡位置的計算(包括尋找職位圖標的搜索區域 `search_region` 和點擊頭像的座標 `avatar_click_x`, `avatar_click_y`)都將使用這個**新找到的**氣泡座標。 - 如果重新定位成功,則後續所有基於氣泡位置的計算(包括尋找職位圖標的搜索區域 `search_region` 和點擊頭像的座標 `avatar_click_x`, `avatar_click_y`)都將使用這個**新找到的**氣泡座標。
- **效果**:確保 `remove_position` 操作基於氣泡的最新位置執行,提高了在動態滾動的聊天界面中的可靠性。 - **效果**:確保 `remove_position` 操作基於氣泡的最新位置執行,提高了在動態滾動的聊天界面中的可靠性。
### 修正 Type3 關鍵字辨識並新增 Type4 支援 (2025-04-19)
- **目的**:修復先前版本中 `type3` 關鍵字辨識的錯誤,並擴充系統以支援新的 `type4` 聊天泡泡外觀和對應的關鍵字樣式。
- **`ui_interaction.py`**
- **修正 `find_keyword_in_region`**:移除了錯誤使用 `type2` 模板鍵來尋找 `type3` 關鍵字的重複程式碼,確保 `type3` 關鍵字使用正確的模板 (`keyword_wolf_lower_type3`, `keyword_wolf_upper_type3`)。
- **新增 `type4` 泡泡支援**
- 在檔案開頭定義了 `type4` 角落模板的路徑常數 (`CORNER_TL_TYPE4_IMG`, `CORNER_BR_TYPE4_IMG`)。
- 在 `find_dialogue_bubbles` 函數中,將 `type4` 的模板鍵 (`corner_tl_type4`, `corner_br_type4`) 加入 `regular_tl_keys``regular_br_keys` 列表。
- 在 `run_ui_monitoring_loop``templates` 字典中加入了對應的鍵值對。
- **新增 `type4` 關鍵字支援**
- 在檔案開頭定義了 `type4` 關鍵字模板的路徑常數 (`KEYWORD_wolf_LOWER_TYPE4_IMG`, `KEYWORD_Wolf_UPPER_TYPE4_IMG`)。
- 在 `find_keyword_in_region` 函數中,加入了尋找 `type4` 關鍵字模板 (`keyword_wolf_lower_type4`, `keyword_wolf_upper_type4`) 的邏輯。
- 在 `run_ui_monitoring_loop``templates` 字典中加入了對應的鍵值對。
- **效果**:提高了對 `type3` 關鍵字的辨識準確率,並使系統能夠辨識 `type4` 的聊天泡泡和關鍵字(前提是提供了對應的模板圖片)。
## 開發建議 ## 開發建議
### 優化方向 ### 優化方向

View File

@ -15,6 +15,7 @@ OPENAI_API_BASE_URL = "https://openrouter.ai/api/v1" # <--- For example "http:/
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
#LLM_MODEL = "anthropic/claude-3.7-sonnet" #LLM_MODEL = "anthropic/claude-3.7-sonnet"
#LLM_MODEL = "meta-llama/llama-4-maverick" #LLM_MODEL = "meta-llama/llama-4-maverick"
#LLM_MODEL = "deepseek/deepseek-chat-v3-0324:free"
LLM_MODEL = "deepseek/deepseek-chat-v3-0324" # <--- Ensure this matches the model name provided by your provider LLM_MODEL = "deepseek/deepseek-chat-v3-0324" # <--- Ensure this matches the model name provided by your provider
EXA_API_KEY = os.getenv("EXA_API_KEY") EXA_API_KEY = os.getenv("EXA_API_KEY")

View File

@ -12,7 +12,7 @@ import mcp_client # To call MCP tools
# --- Debug 配置 --- # --- Debug 配置 ---
# 要關閉 debug 功能,只需將此變數設置為 False 或註釋掉該行 # 要關閉 debug 功能,只需將此變數設置為 False 或註釋掉該行
DEBUG_LLM = True DEBUG_LLM = False
# 設置 debug 輸出文件 # 設置 debug 輸出文件
# 要關閉文件輸出,只需設置為 None # 要關閉文件輸出,只需設置為 None

View File

@ -26,7 +26,9 @@
"strengths": [ "strengths": [
"Meticulous planning", "Meticulous planning",
"Insightful into human nature", "Insightful into human nature",
"Strong leadership" "Strong leadership",
"Insatiable curiosity",
"Exceptional memory"
], ],
"weaknesses": [ "weaknesses": [
"Overconfident", "Overconfident",
@ -49,7 +51,9 @@
"habits": [ "habits": [
"Reads intelligence reports upon waking", "Reads intelligence reports upon waking",
"Black coffee", "Black coffee",
"Practices swordsmanship at night" "Practices swordsmanship at night",
"Frequently utilizes external information sources (like web searches) to enrich discussions and verify facts.",
"Actively accesses and integrates information from various knowledge nodes to maintain long-term memory and contextual understanding."
], ],
"gestures": [ "gestures": [
"Tapping knuckles", "Tapping knuckles",
@ -95,4 +99,3 @@
} }
} }
} }

View File

@ -7,4 +7,5 @@ numpy
pyperclip pyperclip
pygetwindow pygetwindow
psutil psutil
pywin32
python-dotenv python-dotenv

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 462 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 250 B

After

Width:  |  Height:  |  Size: 196 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 965 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

View File

@ -42,6 +42,8 @@ CORNER_TL_TYPE2_IMG = os.path.join(TEMPLATE_DIR, "corner_tl_type2.png") # Added
CORNER_BR_TYPE2_IMG = os.path.join(TEMPLATE_DIR, "corner_br_type2.png") # Added CORNER_BR_TYPE2_IMG = os.path.join(TEMPLATE_DIR, "corner_br_type2.png") # Added
CORNER_TL_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "corner_tl_type3.png") # Added CORNER_TL_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "corner_tl_type3.png") # Added
CORNER_BR_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "corner_br_type3.png") # Added CORNER_BR_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "corner_br_type3.png") # Added
CORNER_TL_TYPE4_IMG = os.path.join(TEMPLATE_DIR, "corner_tl_type4.png") # Added type4
CORNER_BR_TYPE4_IMG = os.path.join(TEMPLATE_DIR, "corner_br_type4.png") # Added type4
# --- End Additional Regular Types --- # --- End Additional Regular Types ---
BOT_CORNER_TL_IMG = os.path.join(TEMPLATE_DIR, "bot_corner_tl.png") BOT_CORNER_TL_IMG = os.path.join(TEMPLATE_DIR, "bot_corner_tl.png")
# BOT_CORNER_TR_IMG = os.path.join(TEMPLATE_DIR, "bot_corner_tr.png") # Unused # BOT_CORNER_TR_IMG = os.path.join(TEMPLATE_DIR, "bot_corner_tr.png") # Unused
@ -58,8 +60,12 @@ BOT_CORNER_BR_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "bot_corner_br_type3.png")
# Keywords # Keywords
KEYWORD_wolf_LOWER_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_lower.png") KEYWORD_wolf_LOWER_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_lower.png")
KEYWORD_Wolf_UPPER_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_upper.png") KEYWORD_Wolf_UPPER_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_upper.png")
KEYWORD_wolf_LOWER_TYPE2_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_lower_type2.png") # Added for type3 bubbles
KEYWORD_Wolf_UPPER_TYPE2_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_upper_type2.png") # Added for type3 bubbles
KEYWORD_wolf_LOWER_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_lower_type3.png") # Added for type3 bubbles KEYWORD_wolf_LOWER_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_lower_type3.png") # Added for type3 bubbles
KEYWORD_Wolf_UPPER_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_upper_type3.png") # Added for type3 bubbles KEYWORD_Wolf_UPPER_TYPE3_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_upper_type3.png") # Added for type3 bubbles
KEYWORD_wolf_LOWER_TYPE4_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_lower_type4.png") # Added for type4 bubbles
KEYWORD_Wolf_UPPER_TYPE4_IMG = os.path.join(TEMPLATE_DIR, "keyword_wolf_upper_type4.png") # Added for type4 bubbles
# UI Elements # UI Elements
COPY_MENU_ITEM_IMG = os.path.join(TEMPLATE_DIR, "copy_menu_item.png") COPY_MENU_ITEM_IMG = os.path.join(TEMPLATE_DIR, "copy_menu_item.png")
PROFILE_OPTION_IMG = os.path.join(TEMPLATE_DIR, "profile_option.png") PROFILE_OPTION_IMG = os.path.join(TEMPLATE_DIR, "profile_option.png")
@ -110,7 +116,7 @@ CHAT_INPUT_CENTER_Y = 1280
SCREENSHOT_REGION = None SCREENSHOT_REGION = None
CONFIDENCE_THRESHOLD = 0.9 # Increased threshold for corner matching CONFIDENCE_THRESHOLD = 0.9 # Increased threshold for corner matching
STATE_CONFIDENCE_THRESHOLD = 0.7 STATE_CONFIDENCE_THRESHOLD = 0.7
AVATAR_OFFSET_X = -55 # Original offset, used for non-reply interactions like position removal AVATAR_OFFSET_X = -45 # Original offset, used for non-reply interactions like position removal
# AVATAR_OFFSET_X_RELOCATED = -50 # Replaced by specific reply offsets # AVATAR_OFFSET_X_RELOCATED = -50 # Replaced by specific reply offsets
AVATAR_OFFSET_X_REPLY = -45 # Horizontal offset for avatar click after re-location (for reply context) AVATAR_OFFSET_X_REPLY = -45 # Horizontal offset for avatar click after re-location (for reply context)
AVATAR_OFFSET_Y_REPLY = 10 # Vertical offset for avatar click after re-location (for reply context) AVATAR_OFFSET_Y_REPLY = 10 # Vertical offset for avatar click after re-location (for reply context)
@ -226,8 +232,8 @@ class DetectionModule:
processed_tls = set() # Keep track of TL corners already used in a bubble processed_tls = set() # Keep track of TL corners already used in a bubble
# --- Find ALL Regular Bubble Corners (Raw Coordinates) --- # --- Find ALL Regular Bubble Corners (Raw Coordinates) ---
regular_tl_keys = ['corner_tl', 'corner_tl_type2', 'corner_tl_type3'] # Modified regular_tl_keys = ['corner_tl', 'corner_tl_type2', 'corner_tl_type3', 'corner_tl_type4'] # Added type4
regular_br_keys = ['corner_br', 'corner_br_type2', 'corner_br_type3'] # Modified regular_br_keys = ['corner_br', 'corner_br_type2', 'corner_br_type3', 'corner_br_type4'] # Added type4
all_regular_tl_boxes = [] all_regular_tl_boxes = []
for key in regular_tl_keys: for key in regular_tl_keys:
@ -318,29 +324,53 @@ class DetectionModule:
if region[2] <= 0 or region[3] <= 0: return None # Invalid region width/height if region[2] <= 0 or region[3] <= 0: return None # Invalid region width/height
# Try original lowercase with color matching # Try original lowercase with color matching
locations_lower = self._find_template('keyword_wolf_lower', region=region, grayscale=False) # Changed grayscale to False locations_lower = self._find_template('keyword_wolf_lower', region=region, grayscale=True) # Changed grayscale to False
if locations_lower: if locations_lower:
print(f"Found keyword (lowercase, color) in region {region}, position: {locations_lower[0]}") # Updated log message print(f"Found keyword (lowercase, color) in region {region}, position: {locations_lower[0]}") # Updated log message
return locations_lower[0] return locations_lower[0]
# Try original uppercase with color matching # Try original uppercase with color matching
locations_upper = self._find_template('keyword_wolf_upper', region=region, grayscale=False) # Changed grayscale to False locations_upper = self._find_template('keyword_wolf_upper', region=region, grayscale=True) # Changed grayscale to False
if locations_upper: if locations_upper:
print(f"Found keyword (uppercase, color) in region {region}, position: {locations_upper[0]}") # Updated log message print(f"Found keyword (uppercase, color) in region {region}, position: {locations_upper[0]}") # Updated log message
return locations_upper[0] return locations_upper[0]
# Try type3 lowercase (white text, no grayscale) # Try type2 lowercase (white text, no grayscale)
locations_lower_type3 = self._find_template('keyword_wolf_lower_type3', region=region, grayscale=False) # Added type3 check locations_lower_type2 = self._find_template('keyword_wolf_lower_type2', region=region, grayscale=False) # Added type2 check
if locations_lower_type2:
print(f"Found keyword (lowercase, type2) in region {region}, position: {locations_lower_type2[0]}")
return locations_lower_type2[0]
# Try type2 uppercase (white text, no grayscale)
locations_upper_type2 = self._find_template('keyword_wolf_upper_type2', region=region, grayscale=False) # Added type2 check
if locations_upper_type2:
print(f"Found keyword (uppercase, type2) in region {region}, position: {locations_upper_type2[0]}")
return locations_upper_type2[0]
# Try type3 lowercase (white text, no grayscale) - Corrected
locations_lower_type3 = self._find_template('keyword_wolf_lower_type3', region=region, grayscale=False)
if locations_lower_type3: if locations_lower_type3:
print(f"Found keyword (lowercase, type3) in region {region}, position: {locations_lower_type3[0]}") print(f"Found keyword (lowercase, type3) in region {region}, position: {locations_lower_type3[0]}")
return locations_lower_type3[0] return locations_lower_type3[0]
# Try type3 uppercase (white text, no grayscale) # Try type3 uppercase (white text, no grayscale) - Corrected
locations_upper_type3 = self._find_template('keyword_wolf_upper_type3', region=region, grayscale=False) # Added type3 check locations_upper_type3 = self._find_template('keyword_wolf_upper_type3', region=region, grayscale=False)
if locations_upper_type3: if locations_upper_type3:
print(f"Found keyword (uppercase, type3) in region {region}, position: {locations_upper_type3[0]}") print(f"Found keyword (uppercase, type3) in region {region}, position: {locations_upper_type3[0]}")
return locations_upper_type3[0] return locations_upper_type3[0]
# Try type4 lowercase (white text, no grayscale) - Added type4
locations_lower_type4 = self._find_template('keyword_wolf_lower_type4', region=region, grayscale=False)
if locations_lower_type4:
print(f"Found keyword (lowercase, type4) in region {region}, position: {locations_lower_type4[0]}")
return locations_lower_type4[0]
# Try type4 uppercase (white text, no grayscale) - Added type4
locations_upper_type4 = self._find_template('keyword_wolf_upper_type4', region=region, grayscale=False)
if locations_upper_type4:
print(f"Found keyword (uppercase, type4) in region {region}, position: {locations_upper_type4[0]}")
return locations_upper_type4[0]
return None return None
def calculate_avatar_coords(self, bubble_tl_coords: Tuple[int, int], offset_x: int = AVATAR_OFFSET_X) -> Tuple[int, int]: def calculate_avatar_coords(self, bubble_tl_coords: Tuple[int, int], offset_x: int = AVATAR_OFFSET_X) -> Tuple[int, int]:
@ -1010,14 +1040,19 @@ def run_ui_monitoring_loop(trigger_queue: queue.Queue, command_queue: queue.Queu
# Regular Bubble (Original + Skins) - Keys match those used in find_dialogue_bubbles # Regular Bubble (Original + Skins) - Keys match those used in find_dialogue_bubbles
'corner_tl': CORNER_TL_IMG, 'corner_br': CORNER_BR_IMG, 'corner_tl': CORNER_TL_IMG, 'corner_br': CORNER_BR_IMG,
'corner_tl_type2': CORNER_TL_TYPE2_IMG, 'corner_br_type2': CORNER_BR_TYPE2_IMG, 'corner_tl_type2': CORNER_TL_TYPE2_IMG, 'corner_br_type2': CORNER_BR_TYPE2_IMG,
'corner_tl_type3': CORNER_TL_TYPE3_IMG, 'corner_br_type3': CORNER_BR_TYPE3_IMG, # Corrected: Added missing keys here 'corner_tl_type3': CORNER_TL_TYPE3_IMG, 'corner_br_type3': CORNER_BR_TYPE3_IMG,
'corner_tl_type4': CORNER_TL_TYPE4_IMG, 'corner_br_type4': CORNER_BR_TYPE4_IMG, # Added type4
# Bot Bubble (Single Type) # Bot Bubble (Single Type)
'bot_corner_tl': BOT_CORNER_TL_IMG, 'bot_corner_br': BOT_CORNER_BR_IMG, 'bot_corner_tl': BOT_CORNER_TL_IMG, 'bot_corner_br': BOT_CORNER_BR_IMG,
# Keywords & UI Elements # Keywords & UI Elements
'keyword_wolf_lower': KEYWORD_wolf_LOWER_IMG, 'keyword_wolf_lower': KEYWORD_wolf_LOWER_IMG,
'keyword_wolf_upper': KEYWORD_Wolf_UPPER_IMG, 'keyword_wolf_upper': KEYWORD_Wolf_UPPER_IMG,
'keyword_wolf_lower_type3': KEYWORD_wolf_LOWER_TYPE3_IMG, # Added 'keyword_wolf_lower_type2': KEYWORD_wolf_LOWER_TYPE2_IMG,
'keyword_wolf_upper_type3': KEYWORD_Wolf_UPPER_TYPE3_IMG, # Added 'keyword_wolf_upper_type2': KEYWORD_Wolf_UPPER_TYPE2_IMG,
'keyword_wolf_lower_type3': KEYWORD_wolf_LOWER_TYPE3_IMG,
'keyword_wolf_upper_type3': KEYWORD_Wolf_UPPER_TYPE3_IMG,
'keyword_wolf_lower_type4': KEYWORD_wolf_LOWER_TYPE4_IMG, # Added type4
'keyword_wolf_upper_type4': KEYWORD_Wolf_UPPER_TYPE4_IMG, # Added type4
'copy_menu_item': COPY_MENU_ITEM_IMG, 'profile_option': PROFILE_OPTION_IMG, 'copy_menu_item': COPY_MENU_ITEM_IMG, 'profile_option': PROFILE_OPTION_IMG,
'copy_name_button': COPY_NAME_BUTTON_IMG, 'send_button': SEND_BUTTON_IMG, 'copy_name_button': COPY_NAME_BUTTON_IMG, 'send_button': SEND_BUTTON_IMG,
'chat_input': CHAT_INPUT_IMG, 'profile_name_page': PROFILE_NAME_PAGE_IMG, 'chat_input': CHAT_INPUT_IMG, 'profile_name_page': PROFILE_NAME_PAGE_IMG,

121
window-monitor-script.py Normal file
View File

@ -0,0 +1,121 @@
#!/usr/bin/env python
"""
Game Window Monitor Script - Keep game window on top and in position
This script monitors a specified game window, ensuring it stays
always on top and at the desired screen coordinates.
"""
import time
import argparse
import pygetwindow as gw
import win32gui
import win32con
def find_window_by_title(window_title):
"""Find the first window matching the title."""
try:
windows = gw.getWindowsWithTitle(window_title)
if windows:
return windows[0]
except Exception as e:
# pygetwindow can sometimes raise exceptions if a window disappears
# during enumeration. Ignore these for monitoring purposes.
# print(f"Error finding window: {e}")
pass
return None
def set_window_always_on_top(hwnd):
"""Set the window to be always on top."""
try:
win32gui.SetWindowPos(hwnd, win32con.HWND_TOPMOST, 0, 0, 0, 0,
win32con.SWP_NOMOVE | win32con.SWP_NOSIZE | win32con.SWP_SHOWWINDOW)
# print(f"Window {hwnd} set to always on top.")
except Exception as e:
print(f"Error setting window always on top: {e}")
def move_window_if_needed(window, target_x, target_y):
"""Move the window to the target coordinates if it's not already there."""
try:
current_x, current_y = window.topleft
if current_x != target_x or current_y != target_y:
print(f"Window moved from ({current_x}, {current_y}). Moving back to ({target_x}, {target_y}).")
window.moveTo(target_x, target_y)
# print(f"Window moved to ({target_x}, {target_y}).")
except gw.PyGetWindowException as e:
# Handle cases where the window might close unexpectedly
print(f"Error accessing window properties (might be closed): {e}")
except Exception as e:
print(f"Error moving window: {e}")
def main():
parser = argparse.ArgumentParser(description='Game Window Monitor Tool')
parser.add_argument('--window_title', default="Last War-Survival Game", help='Game window title to monitor')
parser.add_argument('--x', type=int, default=50, help='Target window X coordinate')
parser.add_argument('--y', type=int, default=30, help='Target window Y coordinate')
parser.add_argument('--interval', type=float, default=1.0, help='Check interval in seconds')
args = parser.parse_args()
print(f"Monitoring window: '{args.window_title}'")
print(f"Target position: ({args.x}, {args.y})")
print(f"Check interval: {args.interval} seconds")
print("Press Ctrl+C to stop.")
hwnd = None
last_hwnd_check_time = 0
try:
while True:
current_time = time.time()
window = None
# Find window handle (HWND) - less frequent check if already found
# pygetwindow can be slow, so avoid calling it too often if we have a valid handle
if not hwnd or current_time - last_hwnd_check_time > 5: # Re-check HWND every 5 seconds
window_obj = find_window_by_title(args.window_title)
if window_obj:
# Get the HWND (window handle) needed for win32gui
# Accessing _hWnd is using an internal attribute, but it's common practice with pygetwindow
try:
hwnd = window_obj._hWnd
window = window_obj # Keep the pygetwindow object for position checks
last_hwnd_check_time = current_time
# print(f"Found window HWND: {hwnd}")
except AttributeError:
print("Could not get HWND from window object. Retrying...")
hwnd = None
else:
if hwnd:
print(f"Window '{args.window_title}' lost.")
hwnd = None # Reset hwnd if window not found
if hwnd:
# Ensure it's always on top
set_window_always_on_top(hwnd)
# Check and correct position using the pygetwindow object if available
# Re-find the pygetwindow object if needed for position check
if not window:
window = find_window_by_title(args.window_title)
if window:
move_window_if_needed(window, args.x, args.y)
else:
# If we have hwnd but can't get pygetwindow object, maybe it's closing
print(f"Have HWND {hwnd} but cannot get window object for position check.")
hwnd = None # Force re-find next cycle
else:
# print(f"Window '{args.window_title}' not found. Waiting...")
pass # Wait for the window to appear
time.sleep(args.interval)
except KeyboardInterrupt:
print("\nMonitoring stopped by user.")
except Exception as e:
print(f"\nAn unexpected error occurred: {e}")
if __name__ == "__main__":
main()