cleanup: rm user.js, untrack copilot_proxy.pyw; ljqCtrl: DPI via GDI + GrabWindow(hwnd)

This commit is contained in:
Liang Jiaqing
2026-04-10 22:17:24 +08:00
parent 5a1d3a41da
commit 5e28902cb4
4 changed files with 17 additions and 626 deletions

1
.gitignore vendored
View File

@@ -75,6 +75,7 @@ restore_commit.txt
sche_tasks/ sche_tasks/
# CDP Bridge 密钥配置(首次运行自动生成) # CDP Bridge 密钥配置(首次运行自动生成)
assets/tmwd_cdp_bridge/config.js assets/tmwd_cdp_bridge/config.js
assets/copilot_proxy.pyw
**log.* **log.*
# Reflect (ignore new files, whitelist existing) # Reflect (ignore new files, whitelist existing)

View File

@@ -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()

View File

@@ -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 (/<title>\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();
}
});
})();

View File

@@ -7,9 +7,10 @@ ljqCtrl Quick Reference:
- Press(cmd, staytime=0): Keyboard shortcuts (e.g. 'ctrl+v') - Press(cmd, staytime=0): Keyboard shortcuts (e.g. 'ctrl+v')
- FindBlock(fn, wrect=None, threshold=0.8) -> (obj_center_phys, is_found) - FindBlock(fn, wrect=None, threshold=0.8) -> (obj_center_phys, is_found)
- MouseDClick(staytime=0.05), MouseClick(staytime=0.05) - 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 import numpy as np
dpi_scale = 1 dpi_scale = 1
@@ -18,24 +19,15 @@ try:
import cv2 import cv2
except: pass except: pass
try: _hdc = ctypes.windll.user32.GetDC(0)
scr = ImageGrab.grab() swidth = ctypes.windll.gdi32.GetDeviceCaps(_hdc, 118) # DESKTOPHORZRES (物理)
swidth, sheight = scr.size sheight = ctypes.windll.gdi32.GetDeviceCaps(_hdc, 117) # DESKTOPVERTRES
print('Screen width & height:', swidth, sheight) ctypes.windll.user32.ReleaseDC(0, _hdc)
cwidth, cheight = map(win32api.GetSystemMetrics, [win32con.SM_CXSCREEN, win32con.SM_CYSCREEN]) cwidth = win32api.GetSystemMetrics(win32con.SM_CXSCREEN) # 逻辑
cheight = win32api.GetSystemMetrics(win32con.SM_CYSCREEN)
dpi_scale = cwidth / swidth dpi_scale = cwidth / swidth
print('Screen width & height:', swidth, sheight)
print('dpi_scale:', dpi_scale) 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}')
def MouseDown(): win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0) def MouseDown(): win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN,0,0)
def MouseUp(): win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP,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 = {'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()} 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): def imshow(mt, sec=0):
cv2.imshow('cc', mt) cv2.imshow('cc', mt)
cv2.waitKey(sec) cv2.waitKey(sec)