Improve Element detection stability
@ -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` 的聊天泡泡和關鍵字(前提是提供了對應的模板圖片)。
|
||||||
|
|
||||||
## 開發建議
|
## 開發建議
|
||||||
|
|
||||||
### 優化方向
|
### 優化方向
|
||||||
|
|||||||
@ -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")
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7,4 +7,5 @@ numpy
|
|||||||
pyperclip
|
pyperclip
|
||||||
pygetwindow
|
pygetwindow
|
||||||
psutil
|
psutil
|
||||||
|
pywin32
|
||||||
python-dotenv
|
python-dotenv
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 462 B |
BIN
templates/corner_br_type4.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 250 B After Width: | Height: | Size: 196 B |
BIN
templates/corner_tl_type4.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.7 KiB |
BIN
templates/keyword_wolf_lower_type2.png
Normal file
|
After Width: | Height: | Size: 965 B |
BIN
templates/keyword_wolf_lower_type4.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
templates/keyword_wolf_upper_type2.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
templates/keyword_wolf_upper_type4.png
Normal file
|
After Width: | Height: | Size: 867 B |
@ -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
@ -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()
|
||||||