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)