From 5e28902cb45c889ea17b9c2bad58a694503ca212 Mon Sep 17 00:00:00 2001 From: Liang Jiaqing Date: Fri, 10 Apr 2026 22:17:24 +0800 Subject: [PATCH] cleanup: rm user.js, untrack copilot_proxy.pyw; ljqCtrl: DPI via GDI + GrabWindow(hwnd) --- .gitignore | 1 + assets/copilot_proxy.pyw | 185 --------------- assets/ljq_web_driver.user.js | 422 ---------------------------------- memory/ljqCtrl.py | 35 ++- 4 files changed, 17 insertions(+), 626 deletions(-) delete mode 100644 assets/copilot_proxy.pyw delete mode 100644 assets/ljq_web_driver.user.js diff --git a/.gitignore b/.gitignore index cfc3867..0371600 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,7 @@ restore_commit.txt sche_tasks/ # CDP Bridge 密钥配置(首次运行自动生成) assets/tmwd_cdp_bridge/config.js +assets/copilot_proxy.pyw **log.* # Reflect (ignore new files, whitelist existing) diff --git a/assets/copilot_proxy.pyw b/assets/copilot_proxy.pyw deleted file mode 100644 index e2af17f..0000000 --- a/assets/copilot_proxy.pyw +++ /dev/null @@ -1,185 +0,0 @@ -""" -Copilot Local Proxy - TK GUI -本地 OpenAI 兼容代理,自动管理 Copilot token 并转发请求 -""" -import tkinter as tk -from tkinter import scrolledtext -import threading, json, os, time, uuid -from http.server import HTTPServer, BaseHTTPRequestHandler -import requests - -# ============ Config ============ -OAUTH_PATH = os.path.join(os.path.expanduser('~'), '.copilot_oauth.json') -COPILOT_TOKEN_URL = 'https://api.github.com/copilot_internal/v2/token' -COPILOT_API_BASE = 'https://api.githubcopilot.com' -PROXY = {'https': 'http://127.0.0.1:2082'} -LOCAL_PORT = 15432 -REFRESH_MARGIN = 120 # 提前120秒刷新 - -COPILOT_HEADERS = { - 'Editor-Version': 'vscode/1.110.1', - 'Editor-Plugin-Version': 'copilot-chat/0.38.2', - 'User-Agent': 'GitHubCopilotChat/0.38.2', - 'Copilot-Integration-Id': 'vscode-chat', - 'openai-intent': 'conversation-panel', -} - - -# ============ Token Manager ============ -class TokenManager: - def __init__(self, log_fn=print): - self.copilot_token = None - self.expires_at = 0 - self.log = log_fn - self._lock = threading.Lock() - with open(OAUTH_PATH) as f: - self.access_token = json.load(f)['access_token'] - self.log(f"[Token] OAuth token loaded: ***{self.access_token[-6:]}") - - def get_token(self): - with self._lock: - if time.time() < self.expires_at - REFRESH_MARGIN: - return self.copilot_token - return self._refresh() - - def _refresh(self): - self.log("[Token] Refreshing copilot token...") - try: - resp = requests.get(COPILOT_TOKEN_URL, headers={ - 'Authorization': f'token {self.access_token}', - 'User-Agent': 'GitHubCopilotChat/0.38.2', - 'Accept': 'application/json', - }, proxies=PROXY, timeout=15) - resp.raise_for_status() - data = resp.json() - self.copilot_token = data['token'] - self.expires_at = data['expires_at'] - remain = int(self.expires_at - time.time()) - self.log(f"[Token] Refreshed OK, expires in {remain}s") - return self.copilot_token - except Exception as e: - self.log(f"[Token] Refresh FAILED: {e}") - return self.copilot_token - - -# ============ Proxy Handler ============ -class ProxyHandler(BaseHTTPRequestHandler): - token_mgr: TokenManager = None - log_fn = print - - def do_POST(self): - try: - length = int(self.headers.get('Content-Length', 0)) - body = json.loads(self.rfile.read(length)) if length else {} - model = body.get('model', '?') - stream = body.get('stream', False) - self.log_fn(f"[Req] {model} stream={stream}") - - token = self.token_mgr.get_token() - if not token: - self._error(503, "No copilot token available") - return - - headers = {**COPILOT_HEADERS, - 'Authorization': f'Bearer {token}', - 'Content-Type': 'application/json', - 'x-request-id': str(uuid.uuid4())} - - path = self.path - if path.startswith('/v1/'): - path = path[3:] # strip /v1 prefix - target = f"{COPILOT_API_BASE}{path}" - resp = requests.post(target, headers=headers, json=body, - proxies=PROXY, timeout=120, stream=stream) - - if stream and 'text/event-stream' in resp.headers.get('content-type', ''): - self.send_response(resp.status_code) - self.send_header('Content-Type', 'text/event-stream') - self.send_header('Cache-Control', 'no-cache') - self.end_headers() - for chunk in resp.iter_content(chunk_size=None): - if chunk: - self.wfile.write(chunk) - self.wfile.flush() - else: - self.send_response(resp.status_code) - self.send_header('Content-Type', 'application/json') - self.end_headers() - self.wfile.write(resp.content) - - self.log_fn(f"[Resp] {resp.status_code}") - except Exception as e: - self.log_fn(f"[Error] {e}") - self._error(502, str(e)) - - def do_GET(self): - try: - token = self.token_mgr.get_token() - headers = {**COPILOT_HEADERS, 'Authorization': f'Bearer {token}'} - path = self.path - if path.startswith('/v1/'): - path = path[3:] - target = f"{COPILOT_API_BASE}{path}" - resp = requests.get(target, headers=headers, proxies=PROXY, timeout=15) - self.send_response(resp.status_code) - self.send_header('Content-Type', 'application/json') - self.end_headers() - self.wfile.write(resp.content) - except Exception as e: - self._error(502, str(e)) - - def _error(self, code, msg): - self.send_response(code) - self.send_header('Content-Type', 'application/json') - self.end_headers() - self.wfile.write(json.dumps({'error': msg}).encode()) - - def log_message(self, fmt, *args): - pass - - -# ============ TK GUI ============ -class App: - def __init__(self): - self.root = tk.Tk() - self.root.title("Copilot Proxy") - self.root.geometry("520x360") - self.root.resizable(False, False) - - frm = tk.Frame(self.root) - frm.pack(fill='x', padx=8, pady=4) - self.status_var = tk.StringVar(value="Starting...") - tk.Label(frm, textvariable=self.status_var, fg='blue', anchor='w').pack(side='left') - tk.Label(frm, text=f":{LOCAL_PORT}", fg='gray').pack(side='right') - - self.log_area = scrolledtext.ScrolledText( - self.root, height=18, state='disabled', font=('Consolas', 9)) - self.log_area.pack(fill='both', expand=True, padx=8, pady=4) - - self.token_mgr = TokenManager(log_fn=self.log) - threading.Thread(target=self._run_server, daemon=True).start() - - def log(self, msg): - ts = time.strftime('%H:%M:%S') - def _append(): - self.log_area.config(state='normal') - self.log_area.insert('end', f"[{ts}] {msg}\n") - self.log_area.see('end') - self.log_area.config(state='disabled') - self.root.after(0, _append) - - def _run_server(self): - ProxyHandler.token_mgr = self.token_mgr - ProxyHandler.log_fn = self.log - server = HTTPServer(('127.0.0.1', LOCAL_PORT), ProxyHandler) - self.log(f"[Server] Listening on http://127.0.0.1:{LOCAL_PORT}") - self.root.after(0, lambda: self.status_var.set(f"Running 127.0.0.1:{LOCAL_PORT}")) - self.token_mgr.get_token() - server.serve_forever() - - def run(self): - self.root.mainloop() - - -if __name__ == '__main__': - App().run() diff --git a/assets/ljq_web_driver.user.js b/assets/ljq_web_driver.user.js deleted file mode 100644 index f02ef29..0000000 --- a/assets/ljq_web_driver.user.js +++ /dev/null @@ -1,422 +0,0 @@ -// ==UserScript== -// @name ljq_web_driver -// @namespace http://tampermonkey.net/ -// @version 0.40 -// @description Execute JS via ljq_web_driver -// @require https://code.jquery.com/jquery-3.6.0.min.js -// @author You -// @match *://*/* -// @grant GM_setValue -// @grant GM_getValue -// @grant GM_xmlhttpRequest -// @grant GM_openInTab -// @grant unsafeWindow -// @connect 127.0.0.1 -// @run-at document-start -// ==/UserScript== - - -(function() { - 'use strict'; - const log_prefix = "ljq_driver: "; - if (document.querySelector('[data-testid="stApp"],.stApp')) return; - if (/\s*Streamlit\s*<\/title>|window\.prerenderReady=!1|You need to enable JavaScript to run this app\./i.test(document.documentElement?.outerHTML || '')) return; - - if (window.self !== window.top) { - window.addEventListener('message',e=>{if(e.data?.type==='ljq_exec'){try{let r=eval(e.data.code);parent.postMessage({type:'ljq_result',id:e.data.id,result:String(r)},'*')}catch(err){parent.postMessage({type:'ljq_result',id:e.data.id,error:err.message},'*')}}}); - return; - } - - const wsUrl = 'ws://127.0.0.1:18765'; - const httpUrl = 'http://127.0.0.1:18766/'; - - function isWebSocketServerAlive(callback) { - GM_xmlhttpRequest({ - method: 'GET', - url: 'http://127.0.0.1:18765/', - onload: () => callback(true), - onerror: () => callback(false) - }); - } - - let ws; - let sid; - if (window.opener && window.name && window.name.startsWith('ljq_')) { - sid = null; - console.log(log_prefix + `检测到opener,丢弃继承的window.name: ${window.name}`); - window.name = ''; - } else { - sid = (window.name && window.name.startsWith('ljq_')) ? window.name : null; - } - if (!sid) { - sid = `ljq_${Date.now().toString().slice(-2)}${Math.random().toString(36).slice(2, 4)}`; - window.name = sid; - console.log(log_prefix + `创建新会话ID: ${sid}`); - } else { - console.log(log_prefix + `使用现有会话ID: ${sid}`); - } - - // 保存会话ID - GM_setValue('sid', sid); - - // 获取或创建状态指示器 - function getIndicator() { - // 检查现有指示器 - let ind = document.getElementById('ljq-ind'); - - // 删除重复指示器 - const dups = document.querySelectorAll('[id="ljq-ind"]'); - if (dups.length > 1) { - for (let i = 1; i < dups.length; i++) { - dups[i].remove(); - } - ind = dups[0]; - } - - // 创建新指示器 - if (!ind && document.body) { - ind = document.createElement('div'); - ind.id = 'ljq-ind'; - ind.style.cssText = ` - position: fixed;bottom: 10px; - right: 10px;background-color: #f44336; - color: white;padding: 8px 12px; - border-radius: 6px;font-size: 14px; - font-weight: bold;z-index: 9999; - transition: background-color 0.3s; - cursor: pointer;box-shadow: 0 3px 6px rgba(0,0,0,0.25); - `; - ind.innerText = log_prefix + '正在连接...'; - - ind.addEventListener('click', () => alert(`会话ID: ${sid}\n当前URL: ${location.href}`)); - document.body.appendChild(ind); - } - - return ind; - } - - // 更新状态 - function updateStatus(status, msg) { - if (!document.body) return setTimeout(() => updateStatus(status, msg), 100); - - const ind = getIndicator(); - if (!ind) return; - - if (status === 'ok') { - ind.style.backgroundColor = '#4CAF50'; - ind.innerText = log_prefix + '连接成功'; - } else if (status === 'disc') { - ind.style.backgroundColor = '#f44336'; - ind.innerText = log_prefix + '连接断开'; - } else if (status === 'conn') { - ind.style.backgroundColor = '#2196F3'; - ind.innerText = log_prefix + '正在连接(HTTP)'; - } else if (status === 'err') { - ind.style.backgroundColor = '#FF9800'; - ind.innerText = log_prefix + `发生错误 (${msg})`; - } else if (status === 'exec') { - ind.style.backgroundColor = '#2196F3'; - ind.innerText = log_prefix + '正在执行指令...'; - } - } - - function handleError(id, error, errorSource) { - console.error(`${errorSource}错误:`, error); - updateStatus('err', error.message); - - const errorMessage = { - type: 'error', - id: id, - sessionId: sid, - error: { - name: error.name, - message: error.message, - stack: error.stack, - source: errorSource - } - }; - - if (typeof ws !== 'undefined' && ws && ws.readyState === WebSocket.OPEN) { - ws.send(JSON.stringify(errorMessage)); - } else { - GM_xmlhttpRequest({ - method: "POST", - url: httpUrl + "api/result", - headers: {"Content-Type": "application/json"}, - data: JSON.stringify(errorMessage), - onload: function(response) {console.log("错误信息已通过HTTP发送", response);}, - onerror: function(err) {console.error("发送错误信息失败", err);} - }); - } - } - - function smartProcessResult(result) { - // 处理 null 和原始类型 - if (result === null || result === undefined || typeof result !== 'object') { - return result; - } - - // 1. 处理 jQuery 对象 - 强制转换为HTML字符串数组 - if (typeof jQuery !== 'undefined' && result instanceof jQuery) { - const elements = []; - for (let i = 0; i < result.length; i++) { - if (result[i] && result[i].nodeType === 1) { - elements.push(result[i].outerHTML); - } - } - return elements; // 始终返回数组 - } - - // 2. 处理 NodeList 和 HTMLCollection - if (result instanceof NodeList || result instanceof HTMLCollection) { - const elements = []; - for (let i = 0; i < result.length; i++) { - if (result[i] && result[i].nodeType === 1) { - elements.push(result[i].outerHTML); - } - } - return elements; - } - - // 3. 处理单个 DOM 元素 - if (result.nodeType === 1) { - return result.outerHTML; - } - - // 4. 检查是否是具有数字索引和length属性的类数组对象 - if (!Array.isArray(result) && - typeof result === 'object' && - 'length' in result && - typeof result.length === 'number') { - - // 检查第一个元素是否是DOM节点 - const firstElement = result[0]; - if (firstElement && firstElement.nodeType === 1) { - const elements = []; - const length = Math.min(result.length, 100); - - for (let i = 0; i < length; i++) { - const elem = result[i]; - if (elem && elem.nodeType === 1) { - elements.push(elem.outerHTML); - } - } - - return elements; - } - } - - // 5. 处理普通对象和数组 - 使用标准序列化 - try { - return JSON.parse(JSON.stringify(result, function(key, value) { - if (typeof value === 'object' && value !== null) { - if (value.nodeType === 1) { - return value.outerHTML; - } - if (value === window || value === document) { - return '[Object]'; - } - } - return value; - })); - } catch (e) { - console.error("序列化对象失败:", e); - return `[无法序列化的对象: ${e.message}]`; - } - } - - // 防止重复初始化 - if (window.ljq_init) return; - window.ljq_init = true; - - function connecthttp() { - if (window.use_ws) return; - updateStatus('conn'); - GM_xmlhttpRequest({ - method: "POST", - url: httpUrl + "api/longpoll", - headers: {"Content-Type": "application/json"}, - data: JSON.stringify({ - type: 'ready', - url: location.href, - sessionId: sid - }), - onload: async function(resp) { - if (resp.status === 200) { - let data = JSON.parse(resp.responseText); - console.log(log_prefix + '接收到数据:', data); - if (data.id === "" && data.ret === "use ws") return; - if (data.id === "") return setTimeout(connecthttp, 100); - const response = await executeCode(data); - - if (response.error) { - handleError(data.id, response.error, '执行代码'); - } else { - GM_xmlhttpRequest({ - method: "POST", - url: httpUrl + "api/result", - headers: {"Content-Type": "application/json"}, - data: JSON.stringify({ - type: 'result', - id: data.id, - sessionId: sid, - result: response.result - }) - }); - } - } else { - console.error(log_prefix + '请求失败,状态码:', resp.status); - updateStatus('err', '请求失败'); - } - setTimeout(connecthttp, 1000); - }, - onerror: function(err) { - console.error(log_prefix + '请求错误', err); - updateStatus('err', '请求失败'); - setTimeout(connecthttp, 5000); - }, - ontimeout: function() { - console.log(log_prefix + '请求超时'); - updateStatus('err', '请求超时'); - setTimeout(connecthttp, 5000); - } - }); - } - - async function executeCode(data) { - let id = data.id || 'unknown'; // 获取 ID - let result; - - if (!data.code) { - console.log('收到非代码执行消息:', data); - return { error: '没有可执行的代码' }; - } - updateStatus('exec'); - const _open = window.open; - window.open = (url, target, features) => { - GM_openInTab(url, { active: true }); - return { success: true, url: url }; - }; - try { - const jsCode = data.code.trim(); - const lines = jsCode.split(/\r?\n/).filter(l => l.trim()); - const lastLine = lines.length > 0 ? lines[lines.length - 1].trim() : ''; - const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor; - - if (lastLine.startsWith('return')) { - result = await (new AsyncFunction(jsCode))(); - } else { - try { - result = eval(jsCode); - if (result instanceof Promise) result = await result; - } catch (e) { - if (isIllegalReturnError(e) || isAwaitError(e)) { - result = await (new AsyncFunction(jsCode))(); - } else throw e; - } - } - const processedResult = smartProcessResult(result); - return { result: processedResult }; - } catch (execError) { - return { error: execError }; - } finally { - setTimeout(() => window.open = _open, 100); - } - } - - function isIllegalReturnError(e) { - return e instanceof SyntaxError && ( - /Illegal return statement/i.test(e.message) || // Chrome 常见 - /return not in function/i.test(e.message) || // Firefox 常见 - /Illegal 'return' statement/i.test(e.message) // 兼容旧文案 - ); - } - - function isAwaitError(e) { - return e instanceof SyntaxError && ( - /await is only valid in async/i.test(e.message) || // Chrome - /await.*async/i.test(e.message) // Firefox等 - ); - } - - function connect() { - ws = new WebSocket(wsUrl); - - ws.onopen = function() { - window.use_ws = true; - console.log(log_prefix + '已连接'); - updateStatus('ok'); - ws.send(JSON.stringify({ - type: 'ready', - url: location.href, - sessionId: sid - })); - }; - - ws.onclose = function() { - console.log(log_prefix + '已断开,5秒后重连'); - updateStatus('disc'); - setTimeout(connect, 5000); - }; - - ws.onerror = function(err) { - console.error(log_prefix + '连接错误', err); - updateStatus('err', '连接失败'); - isWebSocketServerAlive(function (e) { if (e) connecthttp()}); - }; - - ws.onmessage = async function(e) { - try { - let data = JSON.parse(e.data); - ws.send(JSON.stringify({type: 'ack',id: data.id})); - const response = await executeCode(data); - - if (response.error) { - handleError(data.id, response.error, '执行代码'); - } else { - updateStatus('ok'); - ws.send(JSON.stringify({ - type: 'result', - id: data.id, - sessionId: sid, - result: response.result - })); - } - } catch (parseError) { - handleError('unknown', parseError, '解析消息'); - } - }; - - } - - // 初始化 - function init() { - if (document.body) { - getIndicator(); - connect(); - } else { - setTimeout(init, 50); - } - } - - // 监控DOM变化 (改为10秒定时器以优化性能) - let indicatorTimer = null; - - if (document.readyState !== 'loading') { - init(); - indicatorTimer = setInterval(() => getIndicator(), 10000); - } else { - document.addEventListener('DOMContentLoaded', () => { - init(); - indicatorTimer = setInterval(() => getIndicator(), 10000); - }); - } - - // 清理 - window.addEventListener('beforeunload', () => { - if (indicatorTimer) clearInterval(indicatorTimer); - if (ws && ws.readyState === WebSocket.OPEN) { - ws.close(); - } - }); -})(); \ No newline at end of file diff --git a/memory/ljqCtrl.py b/memory/ljqCtrl.py index ab1db6d..7f903b2 100644 --- a/memory/ljqCtrl.py +++ b/memory/ljqCtrl.py @@ -7,9 +7,10 @@ ljqCtrl Quick Reference: - Press(cmd, staytime=0): Keyboard shortcuts (e.g. 'ctrl+v') - FindBlock(fn, wrect=None, threshold=0.8) -> (obj_center_phys, is_found) - MouseDClick(staytime=0.05), MouseClick(staytime=0.05) +- GrabWindow(hwnd) -> PIL Image: DPI-safe window screenshot """ -import os, sys, time, random, math, win32api, win32con +import os, sys, time, random, math, win32api, win32con, ctypes import numpy as np dpi_scale = 1 @@ -18,24 +19,15 @@ try: import cv2 except: pass -try: - scr = ImageGrab.grab() - swidth, sheight = scr.size - print('Screen width & height:', swidth, sheight) - cwidth, cheight = map(win32api.GetSystemMetrics, [win32con.SM_CXSCREEN, win32con.SM_CYSCREEN]) - dpi_scale = cwidth / swidth - print('dpi_scale:', dpi_scale) -except: - import ctypes - user32 = ctypes.windll.user32 - user32.SetProcessDPIAware() # 确保 DPI 感知 - cwidth = user32.GetSystemMetrics(0) # SM_CXSCREEN - cheight = user32.GetSystemMetrics(1) # SM_CYSCREEN - try: - dpi = user32.GetDpiForSystem() # Windows 10 1607+ - dpi_scale = dpi / 96.0 - except: dpi_scale = 1.0 # 降级方案 - print(f'Screen (RDP disconnected): {cwidth}x{cheight}, dpi_scale: {dpi_scale}') +_hdc = ctypes.windll.user32.GetDC(0) +swidth = ctypes.windll.gdi32.GetDeviceCaps(_hdc, 118) # DESKTOPHORZRES (物理) +sheight = ctypes.windll.gdi32.GetDeviceCaps(_hdc, 117) # DESKTOPVERTRES +ctypes.windll.user32.ReleaseDC(0, _hdc) +cwidth = win32api.GetSystemMetrics(win32con.SM_CXSCREEN) # 逻辑 +cheight = win32api.GetSystemMetrics(win32con.SM_CYSCREEN) +dpi_scale = cwidth / swidth +print('Screen width & height:', swidth, sheight) +print('dpi_scale:', dpi_scale) def MouseDown(): win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0) def MouseUp(): win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,0,0) @@ -74,6 +66,11 @@ press = Press VK_CODE = {'backspace':0x08, 'tab':0x09, 'clear':0x0C, 'enter':0x0D, 'shift':0x10, 'ctrl':0x11, 'alt':0x12, 'pause':0x13, 'caps_lock':0x14, 'esc':0x1B, 'escape':0x1B, 'space':0x20, 'page_up':0x21, 'page_down':0x22, 'end':0x23, 'home':0x24, 'left_arrow':0x25, 'up_arrow':0x26, 'right_arrow':0x27, 'down_arrow':0x28, 'select':0x29, 'print':0x2A, 'execute':0x2B, 'print_screen':0x2C, 'ins':0x2D, 'del':0x2E, 'help':0x2F, '0':0x30, '1':0x31, '2':0x32, '3':0x33, '4':0x34, '5':0x35, '6':0x36, '7':0x37, '8':0x38, '9':0x39, 'a':0x41, 'b':0x42, 'c':0x43, 'd':0x44, 'e':0x45, 'f':0x46, 'g':0x47, 'h':0x48, 'i':0x49, 'j':0x4A, 'k':0x4B, 'l':0x4C, 'm':0x4D, 'n':0x4E, 'o':0x4F, 'p':0x50, 'q':0x51, 'r':0x52, 's':0x53, 't':0x54, 'u':0x55, 'v':0x56, 'w':0x57, 'x':0x58, 'y':0x59, 'z':0x5A, 'numpad_0':0x60, 'numpad_1':0x61, 'numpad_2':0x62, 'numpad_3':0x63, 'numpad_4':0x64, 'numpad_5':0x65, 'numpad_6':0x66, 'numpad_7':0x67, 'numpad_8':0x68, 'numpad_9':0x69, 'multiply_key':0x6A, 'add_key':0x6B, 'separator_key':0x6C, 'subtract_key':0x6D, 'decimal_key':0x6E, 'divide_key':0x6F, 'F1':0x70, 'F2':0x71, 'F3':0x72, 'F4':0x73, 'F5':0x74, 'F6':0x75, 'F7':0x76, 'F8':0x77, 'F9':0x78, 'F10':0x79, 'F11':0x7A, 'F12':0x7B, 'F13':0x7C, 'F14':0x7D, 'F15':0x7E, 'F16':0x7F, 'F17':0x80, 'F18':0x81, 'F19':0x82, 'F20':0x83, 'F21':0x84, 'F22':0x85, 'F23':0x86, 'F24':0x87, 'num_lock':0x90, 'scroll_lock':0x91, 'left_shift':0xA0, 'right_shift ':0xA1, 'left_control':0xA2, 'right_control':0xA3, 'left_menu':0xA4, 'right_menu':0xA5, 'browser_back':0xA6, 'browser_forward':0xA7, 'browser_refresh':0xA8, 'browser_stop':0xA9, 'browser_search':0xAA, 'browser_favorites':0xAB, 'browser_start_and_home':0xAC, 'volume_mute':0xAD, 'volume_Down':0xAE, 'volume_up':0xAF, 'next_track':0xB0, 'previous_track':0xB1, 'stop_media':0xB2, 'play/pause_media':0xB3, 'start_mail':0xB4, 'select_media':0xB5, 'start_application_1':0xB6, 'start_application_2':0xB7, 'attn_key':0xF6, 'crsel_key':0xF7, 'exsel_key':0xF8, 'play_key':0xFA, 'zoom_key':0xFB, 'clear_key':0xFE, '+':0xBB, ',':0xBC, '-':0xBD, '.':0xBE, '/':0xBF, '`':0xC0, ';':0xBA, '[':0xDB, '\\':0xDC, ']':0xDD, "'":0xDE, '`':0xC0} VK_CODE = {k.lower():v for k,v in VK_CODE.items()} +def GrabWindow(hwnd): + import win32gui; win32gui.SetForegroundWindow(hwnd); time.sleep(0.3) + bbox = tuple(int(v / dpi_scale) for v in win32gui.GetWindowRect(hwnd)) + return ImageGrab.grab(bbox) + def imshow(mt, sec=0): cv2.imshow('cc', mt) cv2.waitKey(sec)