├── .github └── workflows │ └── static.yml ├── .gitignore ├── Jiejian.ahk ├── LICENSE ├── README.md ├── RELEASE ├── SNAPSHOT ├── app.csv ├── cleanAndPackage.bat ├── compiler ├── Ahk2Exe.exe ├── AutoHotkey1_U64.exe ├── AutoHotkey32.exe ├── AutoHotkey64.exe └── upx.exe ├── custom ├── ChangeBrightness.ahk ├── Controller Test Script.ahk ├── Hello.ahk └── On-Screen Keyboard.ahk ├── data.csv ├── documents ├── 历史版本.md └── 开发思路.md ├── extra ├── GenerateShortcuts.exe ├── WGestures │ ├── WG_ferder.wg28bw │ └── WGestures 1.8.5.0.wgb └── WindowSpyU32.exe ├── icons ├── favicon-paused.ico └── favicon.ico ├── imgs ├── 1.gif ├── 2.gif ├── 7 动画1.gif ├── 7 打开控制面板.apng ├── 7 打开百度.apng ├── 动画2-2.gif ├── 动画2-3.gif ├── 动画2-4.gif ├── 动画3.gif ├── 动画4.gif ├── 动画5.gif └── 动画6.gif ├── jiejianPostExec.exe ├── lang ├── ar.ini ├── de.ini ├── en.ini ├── es.ini ├── fr.ini ├── it.ini ├── ja.ini ├── ko.ini ├── pt.ini ├── ru.ini ├── tr.ini ├── zh-Hans.ini └── zh-Hant.ini ├── lib ├── Actions.ahk ├── Functions.ahk ├── KeymapManager.ahk ├── Monitor.ahk ├── MoveWindow.ahk ├── Utils.ahk ├── WinHole.ahk ├── WindowSpy.ahk └── WindowsTheme.ahk ├── modules ├── Anyrun.ahk ├── CheckUpdate.ahk ├── ConfigMouse.ahk ├── Crypt.ahk ├── GenerateShortcuts.ahk ├── MyTrayMenu.ahk ├── ReadApp.ahk ├── ReadData.ahk ├── Sort.ahk ├── Utils.ahk ├── WindowShading.ahk ├── Windows.ahk └── debug │ ├── CaretGetPos.ahk │ ├── GetAll.ahk │ ├── MouseGetPos.ahk │ └── WinGet.ahk ├── package ├── 7zr.exe ├── JiejianPostExec.ahk └── Package.ahk ├── packagePrep.bat ├── setting.ini ├── template ├── data.csv └── dataTemplate.csv └── tools ├── MouseSC_Query.bat ├── MouseSC_x64.exe ├── ReIconCache_x64.exe ├── Rexplorer_x64.exe ├── SkipUAC.ini ├── SkipUAC_x64.exe ├── SoundControl.exe ├── W11ClassicMenu.exe ├── W11ClassicMenu.ini ├── Wub.ini └── Wub_x64.exe /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["main"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 20 | concurrency: 21 | group: "pages" 22 | cancel-in-progress: false 23 | 24 | jobs: 25 | # Single deploy job since we're just deploying 26 | deploy: 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | runs-on: ubuntu-latest 31 | steps: 32 | - name: Checkout 33 | uses: actions/checkout@v4 34 | - name: Setup Pages 35 | uses: actions/configure-pages@v4 36 | - name: Upload artifact 37 | uses: actions/upload-pages-artifact@v2 38 | with: 39 | # Upload entire repository 40 | path: '.' 41 | - name: Deploy to GitHub Pages 42 | id: deployment 43 | uses: actions/deploy-pages@v3 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode/ 3 | 4 | /out/ 5 | /shortcuts/ 6 | 7 | jiejian*.exe 8 | !jiejianPostExec.exe 9 | 10 | log.txt 11 | 12 | *.db 13 | 14 | .~lock.* 15 | -------------------------------------------------------------------------------- /Jiejian.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | AHK2 jiejian 3 | A key mapping/shortcut enhancement tool developed based on AutoHotkey v2.0+ (http://www.autohotkey.com) 4 | 5 | Copyright 2023-2024 acc8226 6 | -------------------------------- 7 | */ 8 | 9 | /* 10 | 快速参考 | AutoHotkey v2 https://wyagd001.github.io/v2/docs/ 11 | vscode 插件安装 https://marketplace.visualstudio.com/items?itemName=thqby.vscode-autohotkey2-lsp 12 | */ 13 | #Requires AutoHotkey >=v2.0.19 14 | 15 | ; --------------------- COMPILER DIRECTIVES -------------------------- 16 | 17 | ;@Ahk2Exe-SetCopyright 全民反诈联盟 18 | ;@Ahk2Exe-SetDescription 捷键-为简化键鼠操作而生 19 | ;@Ahk2Exe-SetMainIcon icons\favicon.ico 20 | ;@Ahk2Exe-AddResource icons\favicon-paused.ico, 206 ; 编译后 替换成 标准图标 21 | 22 | ; --------------------- GLOBAL -------------------------- 23 | 24 | #SingleInstance force ; 跳过对话框并自动替换旧实例 25 | #Hotstring * C ; 热字符串开启 区分大小写 和 禁用终止符。当前自动终止符有 bug 我才禁用的 26 | CoordMode 'Mouse' ; 第二个参数如果省略, 默认为 Screen 27 | FileEncoding 54936 ; Windows XP 及更高版本:GB18030 简体中文 (4 字节) 28 | SetTitleMatchMode 'RegEx' ; 设置 WinTitle parameter 在内置函数中的匹配行为 29 | 30 | ; This is the setting that runs smoothest on my 31 | ; system. Depending on your video card and cpu 32 | ; power, you may want to raise or lower this value. 33 | SetWinDelay 39 ; 如果没有使用 SetWinDelay, 默认延时为 100. 34 | ; 如果没有使用 SetMouseDelay, 则对于传统的 SendEvent 模式默认延时为 10 35 | SetMouseDelay 10 36 | 37 | SetDefaults 38 | SetDefaults() { 39 | GLOBAL 40 | REG_KEY_NAME := 'HKEY_CURRENT_USER\SOFTWARE\jiejian' 41 | REG_RECORD_MINS := 'record_mins' 42 | REG_LAUNCH_COUNT := 'launch_count' 43 | REG_LANG := 'LANG' 44 | REG_RELAX_REMINDER := 'relaxReminder' 45 | REG_DARK_MODE := 'darkMode' 46 | 47 | CURRENT_LANG := RegRead(REG_KEY_NAME, REG_LANG, '') 48 | if (CURRENT_LANG == '') { 49 | ; https://www.autohotkey.com/docs/v2/misc/Languages.htm 50 | switch A_Language { 51 | case '7804', '0004', '0804', '1004' : CURRENT_LANG := 'zh-Hans' 52 | case '7C04', '0C04', '1404', '0404' : CURRENT_LANG := 'zh-Hant' 53 | default: CURRENT_LANG := 'en' 54 | } 55 | } 56 | 57 | APP_NAME := '捷键' ; 用于 msgbox 标题展示 58 | START_TIME := A_NowUTC 59 | CTRL_TIMESTAMP := A_NowUTC ; 记录 ctrl + c/x 最新时间戳 60 | 61 | ; CapsLock 模式:对任务管理器 和 系统高级设置没用 62 | ; 短按依旧有用 确保了 CapsLock 灯不会闪 63 | IS_CAPS_PRESSED := false 64 | } 65 | 66 | ; 如果 非管理器启动 且 不含 /restart 参数(表示首次启动)则以管理员方式启动 67 | if NOT (A_IsAdmin or RegExMatch(DllCall('GetCommandLine', 'str'), ' /restart(?!\S)')) { 68 | try { 69 | if A_IsCompiled 70 | Run '*RunAs "' A_ScriptFullPath '" /restart' 71 | else 72 | Run '*RunAs "' A_AhkPath '" /restart "' A_ScriptFullPath '"' ; 双击 .ahk 会进入此,形如 *runs as "autohotkey64.exe" /restart "jiejian.ahk" 73 | ExitApp 74 | } catch Error as e 75 | Tip("`n 捷键正在以普通权限运行。`n 捷键无法在具有管理员权限的窗口中工作(例如,Taskmgr.exe)。 `n ", -2600) 76 | } 77 | 78 | ; 定义版本信息并写入 79 | GLOBAL CODE_VERSION := '25.2-beta3' 80 | ;@Ahk2Exe-Let U_version = %A_PriorLine~U).+['"](.+)['"]~$1% 81 | ; FileVersion 将写入 exe 82 | ;@Ahk2Exe-Set FileVersion, %U_version% 83 | ; 往对应文件写入对应版本号,只在生成 32 位 exe 的时候执行 84 | ;@Ahk2Exe-Obey U_V, = %A_PtrSize% == 4 ? 'PostExec' : 'Nop' 85 | ; 提取出 文件名 再拼接 PostExec.exe; 版本号; When: 2 仅在指定 UPX 压缩时运行 ; WorkingDir: 脚本所在路径 86 | ;@Ahk2Exe-%U_V% %A_ScriptName~\.[^\.]+$~PostExec.exe% %U_version%, 2, %A_ScriptDir% 87 | 88 | ; 生成快捷方式:每次运行检测如果 shortcuts 里的文件为空则重新生成一次快捷方式,要想重新生成可以双击 GenerateShortcuts.ahk 脚本或者清空或删除该文件夹 89 | if not FileExist(A_WorkingDir . '\shortcuts\*') { 90 | Run 'extra/GenerateShortcuts.exe' 91 | Sleep 100 ; 加入必要的等待时间 92 | } 93 | 94 | ; ----- 1. 热键 之 鼠标操作 ----- 95 | #Include 'lib\Functions.ahk' 96 | #Include 'lib\Actions.ahk' 97 | #Include 'lib\Utils.ahk' 98 | #Include 'lib\WinHole.ahk' 99 | #Include 'lib\KeymapManager.ahk' 100 | 101 | #Include 102 | #Include 103 | 104 | #Include 'modules\ConfigMouse.ahk' 105 | #Include 'modules\Utils.ahk' 106 | #Include 'modules\ReadApp.ahk' 107 | #Include 'modules\ReadData.ahk' 108 | #Include 'modules\Anyrun.ahk' 109 | #Include 'modules\CheckUpdate.ahk' 110 | #Include 'modules\MyTrayMenu.ahk' 111 | #Include 'modules\WindowShading.ahk' 112 | #Include 'modules\Crypt.ahk' 113 | 114 | initLanguage 115 | 116 | SettingTray 117 | ; 设置托盘图标和菜单 118 | SettingTray() { 119 | ; 构建菜单项 120 | GLOBAL JJ_TRAY_MENU := MyTrayMenu() 121 | 122 | localIsAlphaOrBeta := InStr(CODE_VERSION, 'alpha') || InStr(CODE_VERSION, 'beta') 123 | A_IconTip := "捷键 " . CODE_VERSION . (A_IsCompiled ? '' : ' 未编译') . (localIsAlphaOrBeta ? " 测试版" : " ") . (A_PtrSize == 4 ? '32位' : '64位') 124 | 125 | if !A_IsCompiled 126 | TraySetIcon 'icons\favicon.ico' ; 建议使用 16*16 或 32*32 像素的图标,使用 Ahk2Exe-Let 提取出 favicon.ico 127 | } 128 | 129 | ; 检查更新 130 | CheckUpdate 131 | 132 | ; ----- 热键 之 快捷键重写和增强 ----- 133 | ; ----- 热键 之 打开网址 ----- 134 | ; ----- 热键 之 运行程序 ;问题是怎么添加应用商店的路径----- 135 | ; ----- 热键 之 启动文件夹 ----- 136 | ; ----- 热键 之 其他 ----- 137 | 138 | ; 文本类 为了 md 增强 记事本 & vscode 139 | ; ctrl + 数字 1-5 为光标所在行添加 markdown 格式标题 140 | #HotIf WinActive('ahk_exe i)notepad.exe') || WinActive('ahk_class i)Chrome_WidgetWin_1 ahk_exe i)Code.exe') 141 | ^1:: 142 | ^2:: 143 | ^3:: 144 | ^4:: 145 | ^5::{ 146 | oldText := A_Clipboard 147 | A_Clipboard := '' 148 | Send('{Home}{Shift Down}{End}{Shift Up}') ; 切到首部然后选中到尾部 149 | Sleep 100 150 | Send '^x' 151 | ClipWait ; 等待剪贴板中出现文本 152 | newText := RegExReplace(A_Clipboard, "\s*$", "") ; 去掉尾部空格 153 | newText := RegExReplace(newText, "^#{1,6}\s+(.*)", "$1") 154 | nums := SubStr(A_ThisHotkey, 2) 155 | Send('{Home}{# ' . nums . '}' . ' ') 156 | ; 之所以拆开是为防止被中文输入法影响 157 | SendText newText 158 | Send '{End}' 159 | A_Clipboard := oldText 160 | } 161 | #HotIf 162 | 163 | ; Ctrl + Alt + R 重启脚本 164 | ^!r::{ 165 | Reload 166 | Sleep 60 ; 不创建多个实例的情况下重新加载脚本的简单实现,给个暂停时长 167 | } 168 | ^!s::JJ_TRAY_MENU.MySuspend ; Ctrl + Alt + S 暂停脚本 169 | ^!v::Send A_Clipboard ; Ctrl + Alt + V 将剪贴板的内容输入到当前活动应用程序中,防止了一些网站禁止在 HTML 密码框中进行粘贴操作 170 | ^+"::Send('""{Left}') ; Ctrl + Shift + " 快捷操作-插入双引号 171 | 172 | RAlt::LControl ; 右 alt 不常用,映射为左 ctrl 173 | 174 | ; 启动 Anyrun 组件 175 | #HotIf IniRead('setting.ini', 'Common', 'AnyRunUse', '0') = 'alt' 176 | !Space::Anyrun 177 | #HotIf IniRead('setting.ini', 'Common', 'AnyRunUse', '0') = 'ctrl' 178 | ^Space::Anyrun 179 | 180 | #HotIf IniRead('setting.ini', 'Common', 'AppsKey2', '0') = 'ShowDesktop' 181 | ; 可以当作老板键 或者下一曲 182 | AppsKey::Send '#d' 183 | #HotIf IniRead('setting.ini', 'Common', 'AppsKey2', '0') = 'MediaNext' 184 | ; 可以当作老板键 或者下一曲 185 | AppsKey::Send '{Media_Next}' 186 | 187 | #HotIf IniRead('setting.ini', 'Common', 'PrintScrlkPause2Media', '0') 188 | PrintScreen::Volume_Down 189 | ScrollLock::Media_Play_Pause 190 | Pause::Volume_Up 191 | 192 | ; 数字面板 且 未开启数字键模式 193 | #HotIf IniRead('setting.ini', 'Common', 'NumpadControl2Media', '0') 194 | NumpadDown::Media_Next 195 | NumpadLeft::Volume_Down 196 | NumpadClear::Media_Play_Pause 197 | NumpadRight::Volume_Up 198 | NumpadUp::Media_Prev 199 | 200 | #HotIf IniRead('setting.ini', 'Common', 'F6toF102Media', '0') 201 | F6::Media_Prev 202 | F7::Media_Next 203 | F8::Volume_Down 204 | F9::Media_Play_Pause 205 | F10::Volume_Up 206 | 207 | #HotIf 208 | 209 | ; ----- 热串 之 打开网址。选择 z 而非 q,因为 q 的距离在第一行太远了,我称之为 Z 模式,用于全局跳转网址 ----- 210 | ; ----- 热串 之 缩写扩展:将短缩词自动扩展为长词或长句(英文单词中哪个字母开头的单词数最少,我称之为 X 模式)----- 211 | 212 | ; 意为 'now' 213 | ::nnz::{ 214 | SendText(FormatTime(, 'yyyy-MM-dd HH:mm:ss')) 215 | } 216 | ; 意为 '分割线的首字母 f' 217 | ::ffz::{ 218 | SendText '——————— ฅ՞• •՞ฅ ———————' 219 | } 220 | ; 意为 idcard 221 | #HotIf NOT A_IsCompiled 222 | ::iiz::{ 223 | SendText(XOR_Crypt(']_X][]XUP^X\Y]Y\X^')) 224 | } 225 | #HotIf 226 | 227 | ; 注册一个当脚本退出时, 会自动调用的函数 228 | OnExit ExitFunc 229 | ExitFunc(exitReason, exitCode) { 230 | ; 统计软件使用总分钟数 231 | minutesDiff := DateDiff(A_NowUTC, START_TIME, 'Minutes') 232 | if (minutesDiff > 0) { 233 | recordMins := RegRead(REG_KEY_NAME, REG_RECORD_MINS, 0) + minutesDiff 234 | RegWrite(recordMins, 'REG_DWORD', REG_KEY_NAME, REG_RECORD_MINS) 235 | } 236 | ; 统计软件使用次数 237 | if (NOT exitReason ~= 'i)^(?:Error|Reload|Single)$') { 238 | launchCount := RegRead(REG_KEY_NAME, REG_LAUNCH_COUNT, 1) + 1 239 | RegWrite(launchCount, 'REG_DWORD', REG_KEY_NAME, REG_LAUNCH_COUNT) 240 | } 241 | ; 当前选择的语言 242 | RegWrite(CURRENT_LANG, 'REG_SZ', REG_KEY_NAME, REG_LANG) 243 | 244 | ; 还原窗口 for shading windows 245 | restoreWindows 246 | } 247 | 248 | ; 触发热键时, 热键中按键原有的功能不会被屏蔽(对操作系统隐藏) 249 | F17::OpenSelectedText 250 | 251 | ; 双击模式我只推荐 双击 esc、home 和 end 252 | #HotIf IsSet(MY_DOUBLE_HOME) 253 | ~Home:: 254 | ~NumpadHome::DoubleClick(ThisHotkey, MY_DOUBLE_HOME) 255 | #HotIf IsSet(MY_DOUBLE_END) 256 | ~End:: 257 | ~NumpadEnd::DoubleClick(ThisHotkey, MY_DOUBLE_END) 258 | #HotIf IsSet(MY_DOUBLE_ESC) ; 双击 esc 表示关闭,esc 不适用于 vscode 防止误操作 259 | ~Esc::DoubleClick(ThisHotkey, MY_DOUBLE_ESC) 260 | #HotIf 261 | 262 | ; command 约定是 type-title 的组合 263 | DoubleClick(hk, command) { 264 | if (hk == A_PriorHotkey && A_TimeSincePriorHotkey > 100 && A_TimeSincePriorHotkey < 210) { 265 | ; esc 不适用于 vscode 防止误操作 266 | if (hk = 'Esc') { 267 | try { 268 | processName := WinGetProcessName('A') 269 | ; 如果是 code 这种频繁使用 esc 按键的软件则禁用双击 esc 270 | if processName = 'Code.exe' 271 | return 272 | } catch as e { 273 | MsgBox('An error was thrown!`nSpecifically: ' . e.Message) 274 | return 275 | } 276 | } else { 277 | ; 分离按下的键 278 | KeyWait(SubStr(hk, 2)) ; This prevents the keyboard's auto-repeat feature from interfering. 279 | } 280 | item := unset 281 | if (StrLen(command) > 0) { 282 | split := StrSplit(command, '-') 283 | ; 分割并截取类型 type 和 title 名称 284 | if (split.Length == 2) { 285 | type := split[1] 286 | title := split[2] 287 | item := FindItemByTypeAndTitle(type, title) 288 | } 289 | } 290 | if !(IsSet(item) && item != '') 291 | MsgBox(hk . ' 对应指令找不到!', APP_NAME) 292 | else 293 | OpenPathByType item 294 | } 295 | } 296 | 297 | ; 监控 ctrl + c 按键放下 298 | ~^c Up:: 299 | UpdateCtrlTimestamp(hk?) { 300 | GLOBAL CTRL_TIMESTAMP := A_NowUTC 301 | } 302 | 303 | Capslock::{ 304 | GLOBAL IS_CAPS_PRESSED := true 305 | ; 使用过会清除该变量 306 | GLOBAL ENABLE_CHANGE_CAPS_STATE := true 307 | 308 | DisableCapsChange() { 309 | GLOBAL ENABLE_CHANGE_CAPS_STATE := false 310 | } 311 | 312 | SetTimer(DisableCapsChange, -300) ; 300 ms 犹豫操作时间 313 | KeyWait 'CapsLock' ; 等待用户物理释放按键 314 | 315 | ; Capslock 先置空,来关闭 Capslock+ 功能的触发 316 | IS_CAPS_PRESSED := false 317 | ; 松开的时候才切换 CapsLock 大小写 318 | if ENABLE_CHANGE_CAPS_STATE 319 | SetCapsLockState(!GetKeyState('CapsLock', 'T')) 320 | } 321 | 322 | ; 需要按一次按键才会生效,时好时坏 323 | #HotIf IS_CAPS_PRESSED 324 | ; 关闭窗口 325 | q::SmartCloseWindow 326 | 327 | ; 宽度拉升至最大 328 | w::SetWindowWeightToFullScreen 329 | ; 高度拉升至最大 330 | h::SetWindowHeightToFullScreen 331 | 332 | ; 切换到上个窗口 333 | e::Send '!{tab}' 334 | 335 | ; 程序内切换窗口 caps + ` 或者 r 来切换 336 | vkC0:: 337 | r::LoopRelatedWindows 338 | 339 | ; 切换到上一个虚拟桌面 340 | y::Send '^#{Left}' 341 | ; 切换到下一个虚拟桌面 342 | n::Send '^#{Right}' 343 | 344 | ; 适合 b 站 345 | a::CenterAndResizeWindow 818, 460 346 | ; 适合 b 站 347 | s::CenterAndResizeWindow 1280, 770 348 | d::CenterAndResizeWindow 1920, 1080 ; 16:9 349 | ; 最大化或还原 350 | f::MaximizeWindow 351 | 352 | j::CenterAndResizeWindow_window_percent 200 353 | k::CenterAndResizeWindow_window_percent -200 354 | 355 | u::Click 356 | i::Click 'Up Right' 357 | 358 | o::MouseMove 0, -3,, 'R' ; 上移 359 | l::MouseMove -3, 0,, 'R' ; 左移 360 | VKba::MouseMove 3, 0,, 'R' ; 右移 caps + ; vk=BA sc=027 361 | VKbe::MouseMove 0, 3,, 'R' ; 下移 caps + . vk=BE sc=034 362 | 363 | ; 复制路径 364 | z::CopySelectedAsPlainText 365 | ; 音量调节 366 | x::SoundControl 367 | ; 搜索或打开网址 368 | c::OpenSelectedText 369 | ; 窗口移到下一个显示器 370 | v::Send '#+{right}' 371 | 372 | ; 窗口最小化 373 | m::MinimizeWindow 374 | ; Window Shading(窗口遮帘) caps + , 375 | vkBC::ShadingWindows 376 | 377 | ; 复制选中文件路径并打开 Anyrun 组件 378 | Space::{ 379 | CopySelectedAsPlainTextQuiet 380 | ; 由于命令发送 ctrl + c 不会触发监听则手动更新时间戳 381 | UpdateCtrlTimestamp 382 | Anyrun 383 | } 384 | 385 | ; 按住 CapsLock 的同时 按下鼠标左键拖动窗口 386 | LButton::MoveWindow2 387 | RButton::resizeWindow 388 | 389 | Left::MoveRelative -28 390 | Right::MoveRelative 28 391 | Up::MoveRelative 0, -28 392 | Down::MoveRelative 0, 28 393 | 394 | ; 为程序员们添加 ctrl + alt + t 为在当前页面打开终端 395 | #HotIf WinActive('ahk_exe explorer.exe') 396 | ^!t::openCMDhere() 397 | openCMDhere() { 398 | WinClass := WinGetClass('A') 399 | If (WinClass = 'Progman' or WinClass = 'WorkerW') { 400 | if IsSet(MY_NEW_TERMINAL) 401 | Run(MY_NEW_TERMINAL ' /d .', A_Desktop) 402 | else 403 | Run(A_ComSpec, A_Desktop) 404 | return 405 | } 406 | WinHWND := WinGetID('A') 407 | ; 从所有的文件窗口中进行筛选 408 | for window in ComObject("Shell.Application").Windows { 409 | If (window.HWND = WinHWND) { 410 | if window.LocationURL = "" 411 | return 412 | currdir := SubStr(window.LocationURL, 9) 413 | currdir := RegExReplace(currdir, '%20', ' ') 414 | if IsSet(MY_NEW_TERMINAL) 415 | Run(MY_NEW_TERMINAL ' /d .', currdir) 416 | else 417 | Run(A_ComSpec, currdir) 418 | break 419 | } 420 | } 421 | } 422 | #HotIf 423 | 424 | ; 避免 未编译程序 和 已编译的程序打架 425 | if (A_IsCompiled) { 426 | ; 32 位 和 64 位独活一个 427 | if (A_PtrSize == 8) { 428 | ; 杀掉 32 位程式 429 | if PID := ProcessExist('jiejian32.exe') 430 | ProcessClose PID 431 | } else { 432 | ; 杀掉 64 位程式 433 | if PID := ProcessExist('jiejian64.exe') 434 | ProcessClose PID 435 | } 436 | } else { 437 | ; 关闭所有测试程序 438 | if PID := ProcessExist('jiejian32.exe') 439 | ProcessClose PID 440 | if PID := ProcessExist('jiejian64.exe') 441 | ProcessClose PID 442 | } 443 | 444 | ; 引入 mykeymap 的 keymapmanager.ahk 445 | ; 窗口组 446 | GroupAdd("MY_WINDOW_GROUP__1", "Stardew Valley ahk_class SDL_app") 447 | GroupAdd("MY_WINDOW_GROUP__1", "ahk_exe Rune Factory 3 Special.exe") 448 | KeymapManager.GlobalKeymap.DisabledAt := "ahk_group MY_WINDOW_GROUP__1" 449 | 450 | ; 分号模式( ; ) 451 | km13 := KeymapManager.NewKeymap("*;", "分号模式( `; )", '') 452 | km := km13 453 | km.Map("*a", _ => (Send("{blind}*"))) 454 | km.Map("*b", _ => (Send("{blind}%"))) 455 | km.Map("*c", _ => (Send("{blind}."))) 456 | km.Map("*d", _ => (Send("{blind}="))) 457 | km.Map("*e", _ => (Send("{blind}{^}"))) 458 | km.Map("*f", _ => (Send("{blind}>"))) 459 | km.Map("*g", _ => (Send("{blind}{!}"))) 460 | km.Map("*h", _ => (Send("{blind}{+}"))) 461 | km.Map("*i", _ => (Send("{blind}:"))) 462 | km.Map("*j", _ => (Send("{blind};"))) 463 | km.Map("*k", _ => (Send("{blind}``"))) 464 | km.Map("*m", _ => (Send("{blind}-"))) 465 | km.Map("*n", _ => (Send("{blind}/"))) 466 | km.Map("*r", _ => (Send("{blind}&"))) 467 | km.Map("*s", _ => (Send("{blind}<"))) 468 | km.Map("*t", _ => (Send("{blind}~"))) 469 | km.Map("*u", _ => (Send("{blind}$"))) 470 | km.Map("*v", _ => (Send("{blind}|"))) 471 | km.Map("*w", _ => (Send("{blind}{#}"))) 472 | km.Map("*x", _ => (Send("{blind}_"))) 473 | km.Map("*y", _ => (Send("{blind}@"))) 474 | km.Map("*z", _ => (Send("{blind}\"))) 475 | km.Map("singlePress", _ => Send("{blind};")) 476 | 477 | KeymapManager.GlobalKeymap.Enable() 478 | 479 | ; --- regular start --- 480 | 481 | ; --- regular end --- 482 | 483 | ; 是否启用手柄 484 | if NOT IniRead('setting.ini', "JoyControl", "enable", '0') 485 | return 486 | 487 | ; 如果有手柄则开启手柄按键映射 488 | ; If you want to unconditionally use a specific controller number, change 489 | ; the following value from 0 to the number of the controller (1-16). 490 | ; A value of 0 causes the controller number to be auto-detected: 491 | ControllerNumber := 0 492 | ; Auto-detect the controller number if called for: 493 | if (ControllerNumber <= 0) { 494 | Loop 16 ; Query each controller number to find out which ones exist. 495 | { 496 | if GetKeyState(A_Index "JoyName") { 497 | ControllerNumber := A_Index 498 | break 499 | } 500 | } 501 | if ControllerNumber <= 0 502 | return 503 | } 504 | 505 | ; This script converts a controller (gamepad, joystick, etc.) into a three-button 506 | ; mouse. It allows each button to drag just like a mouse button and it uses 507 | ; virtually no CPU time. Also, it will move the cursor faster depending on how far 508 | ; you push the stick from center. You can personalize various settings at the 509 | ; top of the script. 510 | ; 511 | ; Note: For Xbox controller 2013 and newer (anything newer than the Xbox 360 512 | ; controller), this script will only work if a window it owns is active, 513 | ; such as a message box, GUI, or the script's main window. 514 | 515 | ; Increase the following value to make the mouse cursor move faster: 516 | ContMultiplier := 0.5 517 | 518 | ; Decrease the following value to require less stick displacement-from-center 519 | ; to start moving the mouse. However, you may need to calibrate your stick 520 | ; -- ensuring it's properly centered -- to avoid cursor drift. A perfectly tight 521 | ; and centered stick could use a value of 1: 522 | ContThreshold := 3 523 | 524 | ; Change the following to true to invert the Y-axis, which causes the mouse to 525 | ; move vertically in the direction opposite the stick: 526 | InvertYAxis := false 527 | 528 | ; If your system has more than one controller, increase this value to use a 529 | ; controller other than the first: 530 | ControllerNumber := 1 531 | 532 | ; END OF CONFIG SECTION -- Don't change anything below this point unless you want 533 | ; to alter the basic nature of the script. 534 | 535 | ControllerPrefix := ControllerNumber . "Joy" 536 | Hotkey ControllerPrefix . 1, ClickButtonLeft 537 | Hotkey ControllerPrefix . 2, ClickButtonRight 538 | ; 播放 539 | Hotkey ControllerPrefix . 3, (*) => Send("{Media_Play_Pause}") 540 | ; 下一首 541 | Hotkey ControllerPrefix . 4, (*) => Send("{Media_Next}") 542 | 543 | ; 菜单键 544 | Hotkey ControllerPrefix . 7, (*) => Send("{Volume_Down}") 545 | ; 开始键 546 | Hotkey ControllerPrefix . 8, (*) => Send("{Volume_Up}") 547 | 548 | ; Calculate the axis displacements that are needed to start moving the cursor: 549 | ContThresholdUpper := 50 + ContThreshold 550 | ContThresholdLower := 50 - ContThreshold 551 | YAxisMultiplier := InvertYAxis ? -1 : 1 552 | ; Monitor the movement of the stick. 553 | SetTimer(WatchController, 30) 554 | 555 | ; The functions below do not use KeyWait because that would sometimes trap the 556 | ; WatchController quasi-thread beneath the wait-for-button-up thread, which would 557 | ; effectively prevent mouse-dragging with the controller. 558 | ClickButtonLeft(*) { 559 | MouseClick("Left",,, 1, 0, 'D') ; Hold down the left mouse button. 560 | SetTimer(WaitForLeftButtonUp, 10) 561 | 562 | WaitForLeftButtonUp() { 563 | if GetKeyState(A_ThisHotkey) 564 | return ; The button is still, down, so keep waiting. 565 | ; Otherwise, the button has been released. 566 | SetTimer , 0 567 | MouseClick("Left",,, 1, 0, "U") ; Release the mouse button. 568 | } 569 | } 570 | 571 | ClickButtonRight(*) { 572 | MouseClick("Right",,, 1, 0, 'D') ; Hold down the right mouse button. 573 | SetTimer(WaitForRightButtonUp, 10) 574 | 575 | WaitForRightButtonUp() { 576 | if GetKeyState(A_ThisHotkey) 577 | return ; The button is still, down, so keep waiting. 578 | ; Otherwise, the button has been released. 579 | SetTimer , 0 580 | MouseClick("Right",,, 1, 0, "U") ; Release the mouse button. 581 | } 582 | } 583 | 584 | WatchController() { 585 | global 586 | ; Don't do anything if the script is suspended. 587 | if A_IsSuspended 588 | return 589 | MouseNeedsToBeMoved := false ; Set default. 590 | JoyX := GetKeyState(ControllerNumber 'JoyX') 591 | JoyY := GetKeyState(ControllerNumber 'JoyY') 592 | 593 | if (JoyX == '' or JoyY == '' or (JoyX < 5 and JoyY < 5) or (JoyX > 95 and JoyY > 95)) { 594 | SetTimer , 0 595 | return 596 | } 597 | 598 | if JoyX > ContThresholdUpper { 599 | MouseNeedsToBeMoved := true 600 | DeltaX := Round(JoyX - ContThresholdUpper) 601 | } else if JoyX < ContThresholdLower { 602 | MouseNeedsToBeMoved := true 603 | DeltaX := Round(JoyX - ContThresholdLower) 604 | } else { 605 | DeltaX := 0 606 | } 607 | 608 | if JoyY > ContThresholdUpper { 609 | MouseNeedsToBeMoved := true 610 | DeltaY := Round(JoyY - ContThresholdUpper) 611 | } else if JoyY < ContThresholdLower { 612 | MouseNeedsToBeMoved := true 613 | DeltaY := Round(JoyY - ContThresholdLower) 614 | } else { 615 | DeltaY := 0 616 | } 617 | 618 | if (MouseNeedsToBeMoved) { 619 | SetMouseDelay -1 ; Makes movement smoother. 620 | MouseMove DeltaX * ContMultiplier, DeltaY * ContMultiplier * YAxisMultiplier, 0, 'R' 621 | } 622 | } 623 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 acc8226 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 捷键使用说明 2 | 3 | 文档最新版发布在[《捷键使用说明》](https://feipig.fun/jiejian) 4 | 5 |
6 | Static Badge 7 | GitHub top language 8 | GitHub commit activity 9 | GitHub last commit 10 | 11 | Static Badge 12 | 13 | 14 | Static Badge 15 | 16 |
17 | 18 | [![Download jiejian(捷键)for windows](https://a.fsdn.com/con/app/sf-download-button)](https://sourceforge.net/projects/jiejian/files/latest/download) 19 | 20 | A key mapping/shortcut enhancement tool developed based on [Autohotkey2](https://www.autohotkey.com), designed to simplify keyboard and mouse operations on Windows. It can serve as a traditional launcher and also supports mouse side buttons and hotkeys effectively. 21 | 22 | It is highly recommended to use it in conjunction with global mouse gesture software and a mouse with side buttons. 23 | 24 | System Requirements: Compatible with Windows XP and above, but it is recommended to run smoothly on at least Windows 7. 25 | 26 | "捷键"是一款基于 [Autohotkey2](https://www.autohotkey.com) 开发的 Windows 键鼠增强工具。旨在简化 Windows 操作系统中的键鼠操作,提供了以下功能: 27 | 28 | 1. **快捷键改写和增强**:允许用户自定义快捷键,以快速执行常用操作或宏。 29 | 2. **传统启动器功能**:可以快速启动应用程序或执行命令。 30 | 3. **增强鼠标侧边按键功能**:为鼠标的额外按键提供自定义功能,增加操作的便捷性。 31 | 4. **支持热字符**:允许用户通过快捷键快速输入预设的字符或短语。 32 | 33 | 为了获得最佳体验,建议用户: 34 | 35 | * 使用带有侧键的鼠标,以便利用“捷键”增强的鼠标功能 36 | * 搭配全局鼠标手势软件,以实现更丰富的操作 37 | * 将“捷键”设置为开机自启 38 | 39 | 软件大小 3.77 MB | 多国语言支持 | 最低 32 位和 64 位 Windows XP 系统支持,Win 7 以上系统使用更佳 40 | 41 | ![已适配 windows 深色模式](https://cloudflare.cdnjson.com/images/2024/10/10/22222b75ac9b61531a0e3.png '已支持 windows 深色模式') 42 | 43 | [download 下载][捷键] | [demonstration 演示](https://www.bilibili.com/video/BV19H4y1e7hJ?vd_source=54168537affc2c02555097cb26797d99) 44 | -------------------------------------------------------------------------------- /RELEASE: -------------------------------------------------------------------------------- 1 | 24.8 -------------------------------------------------------------------------------- /SNAPSHOT: -------------------------------------------------------------------------------- 1 | 24.8-beta2 -------------------------------------------------------------------------------- /app.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/app.csv -------------------------------------------------------------------------------- /cleanAndPackage.bat: -------------------------------------------------------------------------------- 1 | @REM author acc8226 2 | @REM updateDate: 2025 3 | @REM 双击即可打包,可以直接调用 4 | 5 | RMDIR /S /Q out\ 6 | compiler\AutoHotkey32.exe package\Package.ahk 7 | 8 | @REM 清理现场 9 | DEL /Q jiejian32.exe jiejian64.exe 10 | -------------------------------------------------------------------------------- /compiler/Ahk2Exe.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/compiler/Ahk2Exe.exe -------------------------------------------------------------------------------- /compiler/AutoHotkey1_U64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/compiler/AutoHotkey1_U64.exe -------------------------------------------------------------------------------- /compiler/AutoHotkey32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/compiler/AutoHotkey32.exe -------------------------------------------------------------------------------- /compiler/AutoHotkey64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/compiler/AutoHotkey64.exe -------------------------------------------------------------------------------- /compiler/upx.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/compiler/upx.exe -------------------------------------------------------------------------------- /custom/Controller Test Script.ahk: -------------------------------------------------------------------------------- 1 | ; Controller Test Script 2 | ; https://www.autohotkey.com 3 | ; This script helps determine the button numbers and other attributes 4 | ; of your controller (gamepad, joystick, etc.). It might also reveal 5 | ; if your controller is in need of calibration; that is, whether the 6 | ; range of motion of each of its axes is from 0 to 100 percent as it 7 | ; should be. If calibration is needed, use the operating system's 8 | ; control panel or the software that came with your controller. 9 | 10 | ; May 24, 2023: Replaced ToolTip and MsgBox with GUI. 11 | ; April 14, 2023: Renamed 'joystick' to 'controller'. 12 | ; July 16, 2016: Revised code for AHK v2 compatibility 13 | ; July 6, 2005 : Added auto-detection of joystick number. 14 | ; May 8, 2005 : Fixed: JoyAxes is no longer queried as a means of 15 | ; detecting whether the joystick is connected. Some joysticks are 16 | ; gamepads and don't have even a single axis. 17 | 18 | ; If you want to unconditionally use a specific controller number, change 19 | ; the following value from 0 to the number of the controller (1-16). 20 | ; A value of 0 causes the controller number to be auto-detected: 21 | ControllerNumber := 0 22 | 23 | ; END OF CONFIG SECTION. Do not make changes below this point unless 24 | ; you wish to alter the basic functionality of the script. 25 | 26 | G := Gui("+AlwaysOnTop", "Controller Test Script") 27 | G.Add("Text", "w300", "Note: For Xbox controller 2013 and newer (anything newer than the Xbox 360 controller), this script can only detect controller events if a window it owns is active (like this one).") 28 | E := G.Add("Edit", "w300 h300 +ReadOnly") 29 | G.Show() 30 | 31 | ; Auto-detect the controller number if called for: 32 | if ControllerNumber <= 0 33 | { 34 | Loop 16 ; Query each controller number to find out which ones exist. 35 | { 36 | if GetKeyState(A_Index "JoyName") 37 | { 38 | ControllerNumber := A_Index 39 | break 40 | } 41 | } 42 | if ControllerNumber <= 0 43 | { 44 | E.Value := "The system does not appear to have any controllers." 45 | return 46 | } 47 | } 48 | 49 | #SingleInstance 50 | cont_buttons := GetKeyState(ControllerNumber "JoyButtons") 51 | cont_name := GetKeyState(ControllerNumber "JoyName") 52 | cont_info := GetKeyState(ControllerNumber "JoyInfo") 53 | Loop 54 | { 55 | buttons_down := "" 56 | Loop cont_buttons 57 | { 58 | if GetKeyState(ControllerNumber "Joy" A_Index) 59 | buttons_down .= " " A_Index 60 | } 61 | axis_info := "X" Round(GetKeyState(ControllerNumber "JoyX")) 62 | axis_info .= " Y" Round(GetKeyState(ControllerNumber "JoyY")) 63 | if InStr(cont_info, "Z") 64 | axis_info .= " Z" Round(GetKeyState(ControllerNumber "JoyZ")) 65 | if InStr(cont_info, "R") 66 | axis_info .= " R" Round(GetKeyState(ControllerNumber "JoyR")) 67 | if InStr(cont_info, "U") 68 | axis_info .= " U" Round(GetKeyState(ControllerNumber "JoyU")) 69 | if InStr(cont_info, "V") 70 | axis_info .= " V" Round(GetKeyState(ControllerNumber "JoyV")) 71 | if InStr(cont_info, "P") 72 | axis_info .= " POV" Round(GetKeyState(ControllerNumber "JoyPOV")) 73 | E.Value := cont_name " (#" ControllerNumber "):`n" axis_info "`nButtons Down: " buttons_down 74 | Sleep 100 75 | } 76 | -------------------------------------------------------------------------------- /custom/Hello.ahk: -------------------------------------------------------------------------------- 1 | ; 外部 ahk 文件 2 | 3 | sayHello() { 4 | Msgbox 'hello AHk 2!' 5 | } 6 | 7 | sayHello 8 | -------------------------------------------------------------------------------- /custom/On-Screen Keyboard.ahk: -------------------------------------------------------------------------------- 1 | ; On-Screen Keyboard (based on the v1 script by Jon) 2 | ; https://www.autohotkey.com 3 | ; This script creates a mock keyboard at the bottom of your screen that shows 4 | ; the keys you are pressing in real time. I made it to help me to learn to 5 | ; touch-type (to get used to not looking at the keyboard). The size of the 6 | ; on-screen keyboard can be customized at the top of the script. Also, you 7 | ; can double-click the tray icon to show or hide the keyboard. 8 | 9 | ;---- Configuration Section: Customize the size of the on-screen keyboard and 10 | ; other options here. 11 | 12 | ; Changing this font size will make the entire on-screen keyboard get 13 | ; larger or smaller: 14 | k_FontSize := 10 15 | k_FontName := "Verdana" ; This can be blank to use the system's default font. 16 | k_FontStyle := "Bold" ; Example of an alternative: Italic Underline 17 | 18 | ; Names for the tray menu items: 19 | k_MenuItemHide := "Hide on-screen &keyboard" 20 | k_MenuItemShow := "Show on-screen &keyboard" 21 | 22 | ; To have the keyboard appear on a monitor other than the primary, specify 23 | ; a number such as 2 for the following variable. Leave it unset to use 24 | ; the primary: 25 | k_Monitor := unset 26 | 27 | ;---- End of configuration section. Don't change anything below this point 28 | ; unless you want to alter the basic nature of the script. 29 | 30 | ;---- Create a Gui window for the on-screen keyboard: 31 | MyGui := Gui("-Caption +ToolWindow +AlwaysOnTop +Disabled") 32 | MyGui.SetFont("s" k_FontSize " " k_FontStyle, k_FontName) 33 | MyGui.MarginY := 0, MyGui.MarginX := 0 34 | 35 | ;---- Alter the tray icon menu: 36 | A_TrayMenu.Delete 37 | A_TrayMenu.Add k_MenuItemHide, k_ShowHide 38 | A_TrayMenu.Add "&Exit", (*) => ExitApp() 39 | A_TrayMenu.Default := k_MenuItemHide 40 | 41 | ;---- Add a button for each key: 42 | 43 | ; The keyboard layout: 44 | k_Layout := [ 45 | ["``", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", "Backspace:3"], 46 | ["Tab:3", "Q", "W", "E", "R", "T", "Y", "U", "I", "O", "P", "[", "]", "\"], 47 | ["CapsLock:3", "A", "S", "D", "F", "G", "H", "J", "K", "L", ";", "'", "Enter:2"], 48 | ["LShift:3", "Z", "X", "C", "V", "B", "N", "M", ",", ".", "/", "Shift:3"], 49 | ["LCtrl:2", "LWin:2", "LAlt:2", "Space:2", "RAlt:2", "RWin:2", "AppsKey:2", "RCtrl:2"] 50 | ] 51 | 52 | ; Traverse the keys of the keyboard layout: 53 | for n, k_Row in k_Layout 54 | for i, k_Key in k_Row 55 | { 56 | k_KeyWidthMultiplier := 1 57 | ; Get custom key width multiplier: 58 | if RegExMatch(k_Key, "(.+):(\d)", &m) 59 | { 60 | k_Key := m[1] 61 | k_KeyWidthMultiplier := m[2] 62 | } 63 | ; Get localized key name: 64 | k_KeyNameText := GetKeyNameText(k_Key, 0, 1) 65 | ; Windows key names start with left or right so replace it: 66 | if (k_Key = "LWin" || k_Key = "RWin") 67 | k_KeyNameText := "Win" 68 | ; Truncate the key name: 69 | if (StrLen(k_Key) > 1) 70 | k_KeyNameText := Trim(SubStr(k_KeyNameText, 1, 5)) 71 | else 72 | k_KeyNameText := k_Key 73 | ; Convert to uppercase: 74 | k_KeyNameText := StrUpper(k_KeyNameText) 75 | ; Calculate object dimensions based on chosen font size: 76 | k_KeyHeight := k_FontSize * 3 77 | opt := "h" k_KeyHeight " w" k_KeyHeight * k_KeyWidthMultiplier " -Wrap x+m" 78 | if (i = 1) 79 | opt .= " y+m xm" 80 | ; Add the button: 81 | Btn := MyGui.Add("Button", opt, k_KeyNameText) 82 | ; When a key is pressed by the user, click the corresponding button on-screen: 83 | Hotkey("~*" k_Key, k_KeyPress.bind(Btn)) 84 | } 85 | 86 | ;---- Position the keyboard at the bottom of the screen (taking into account 87 | ; the position of the taskbar): 88 | MyGui.Show("Hide") ; Required to get the window's calculated width and height. 89 | ; Calculate window's X-position: 90 | MonitorGetWorkArea(k_Monitor?, &WL,, &WR, &WB) 91 | MyGui.GetPos(,, &k_width, &k_height) 92 | k_xPos := (WR - WL - k_width) / 2 ; Calculate position to center it horizontally. 93 | ; The following is done in case the window will be on a non-primary monitor 94 | ; or if the taskbar is anchored on the left side of the screen: 95 | k_xPos += WL 96 | ; Calculate window's Y-position: 97 | k_yPos := WB - k_height 98 | 99 | ;---- Show the window: 100 | MyGui.Show("x" k_xPos " y" k_yPos " NA") 101 | 102 | ;---- Function definitions: 103 | k_KeyPress(BtnCtrl, *) 104 | { 105 | BtnCtrl.Opt("Default") ; Highlight the last pressed key. 106 | ControlClick(, BtnCtrl,,,, "D") 107 | KeyWait(SubStr(A_ThisHotkey, 3)) 108 | ControlClick(, BtnCtrl,,,, "U") 109 | } 110 | 111 | k_ShowHide(*) 112 | { 113 | static isVisible := true 114 | if isVisible 115 | { 116 | MyGui.Hide 117 | A_TrayMenu.Rename k_MenuItemHide, k_MenuItemShow 118 | isVisible := false 119 | } 120 | else 121 | { 122 | MyGui.Show 123 | A_TrayMenu.Rename k_MenuItemShow, k_MenuItemHide 124 | isVisible := true 125 | } 126 | } 127 | 128 | GetKeyNameText(Key, Extended := false, DoNotCare := false) 129 | { 130 | Params := (GetKeySC(Key) << 16) | (Extended << 24) | (DoNotCare << 25) 131 | KeyNameText := Buffer(64, 0) 132 | DllCall("User32.dll\GetKeyNameText", "Int", Params, "Ptr", KeyNameText, "Int", 32) 133 | return StrGet(KeyNameText) 134 | } -------------------------------------------------------------------------------- /data.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/data.csv -------------------------------------------------------------------------------- /documents/历史版本.md: -------------------------------------------------------------------------------- 1 | ### 捷键 2024.08 2 | 3 | * 菜单话术优化 4 | * 新建窗口由 ctrl + shift + n 改为 Ctrl + F9 5 | * 支持访问路径中带中文的网址,但是域名系统是不能带的,否则提示可能访问的是诈骗网站 6 | * caps 模式新增 a、s、d、w、h 按键 7 | * win 7 调节音量优化提示语,win 10以下通过鼠标在桌面左边缘滑动调节音量会有即时提示,win 11以下通过鼠标在任务栏下滑动调节音量会有即时提示 8 | * data.csv 如果类型是程序则第二列 path 支持写多个 9 | * anyrun 组件文字大小调整 10 | * 新加的 窗口按照 指定像素进行缩放函数 和 caps + j 和 caps + k 11 | * win 11 22533 新的音量合成器则取消任务栏提示 12 | * 内部命令添加设置音量 13 | * 添加定时关机任务 14 | * js 关键字支持 15 | 16 | ### 捷键 2024.05 17 | 18 | * 支持 win 7 记事本和 IE 11 程式 19 | * 首次运行会生成快捷方式文件夹 shortcuts/ 供启动程序用 20 | * 快捷启动器 anyrun 增强,增强操作文件和网址的能力,搜索命令可去 data.csv 自定义、新增内部命令(关机、注销、锁屏、重启、上下一曲、暂停...)、外部命令(支持运行外部 ahk 脚本) 21 | * 增加打包 和 clean 脚本,双击 package.ahk 可方便打成 7z 压缩包 22 | * 新增 FastGestures 鼠标手势支持 23 | * 新增 CapsLock 模式 24 | * 新增 双击模式 25 | 26 | ### 捷键 2024.02 27 | 28 | 2024 年 2 月 于祁阳 29 | 30 | 日益完善,可能真是今年的最后一个版本 31 | 32 | * app.csv 新增 Ctrl + Shift + n 作为新建窗口的默认标识 33 | * csv 文件增加是否启用列 34 | * 重新设计了一版菜单,并增加开机自启选项 35 | * 新增一些看图、压缩、视频播放软件的支持 36 | * 增强搜一搜组件,现在支持录入文件/网址/程序后支持跳转了 37 | * 热键、热串若匹配则会有短暂提示 38 | * 音乐播放软件优先使用软件的快捷键,而非媒体快捷键 39 | * 鼠标在左或者上边缘的按键操作添加短暂提示 40 | * 试验性的增加左键辅助功能 41 | * 修复一些已知 bug 42 | 43 | ### 捷键 2024.01 44 | 45 | 2024 年 1 月 31 下午 于北京 46 | 47 | 软件名由英文 shortcut 改为 jiejian 48 | 49 | 这是 24 年的第一个版本,可能也是今年的最后一个版本。 50 | 51 | 新增: 52 | 53 | * 完善对 JetBrains 系列软件的支持,包含衍生的 Google Android Studio 和 华为 DevEco Studio。完善 JetBrains 系列的关闭键的功能,可以智能关闭标签和窗口了 54 | * 完善一些对微软 office、文本编辑器、pdf 类、音视频类软件的支持 55 | * 新增鼠标中间和右键的按键支持 56 | * 新增 按住 CapsLock 后可以用鼠标左键拖动窗口 和 兜底的关闭功能更加完善了 57 | * 添加 MyKeymap 和 [WGestures 2][WGestures 2付费链接] 的鼠标手势的玩法 58 | * 重新定义了一套菜单选项,增加软件使用统计 和 bug 修复。新增检查版本更新的功能 59 | * 新增 一键打包 package.ahk 脚本 60 | * 版本划分为正式版和测试版 61 | * csv 文件的字符集改为更适合本土体质的 GB18030 62 | * 添加了 Ctrl + F7 置顶功能 和 f11 全屏/取消全屏 63 | 64 | ### 捷键 2023 年度纪念版 65 | 66 | 这是 23 年最后的一个版本,在这里提前祝大家元旦快乐 67 | 68 | * 支持青鸟浏览器、小白浏览器、阿里云客户端、Spotify、CudaText、PotPlayer、Devecostudio 64 位。 69 | * 提供了 [WGestures 2][WGestures 2付费链接] 预设手势方案 70 | * bug 修复 71 | 72 | ### 捷键 2023.10 73 | 74 | 2023 年 10 月 30 晚 于北京 75 | 76 | 新增: 77 | 78 | * 当鼠标在任务栏上定义左滑为上一首,右滑为下一首 79 | * 支持 稻壳阅读器、汽水音乐、navicat 80 | * 快速启动 新增 bing 搜索 81 | 82 | 优化: 83 | 84 | * 当程序启动失败,添加了友好提示 85 | 86 | ### 捷键 2023.09 87 | 88 | 2023 年 9 月 29 晚 于北京 89 | 90 | 新增 91 | 92 | * 支持 netbean、editplus、JB fleet、eclipse、myeclipse、Notepad--、Notepad++、SpringToolSuite4、sublime、ultraedit 64 位、HBuilderX 93 | 94 | 优化 95 | 96 | * 导入 csv 的 path 支持首尾都包含双引号 97 | * 调整 x 模式的激活条件,使更合理 98 | 99 | ### 捷键 v1.0.3 2023-8-27 100 | 101 | 新增: 102 | 103 | * 热串 xnow 用于输出当前日期时间时间 104 | * 热串 xdate 用于 markdown 版输出当前日期时间时间 105 | 106 | 优化: 107 | 108 | * 热串 x 模式主要用于拓展文本,限定范围为只在文本编辑框可编辑 109 | * 热串 z 模式主要用于打开网页,限定范围为排除在 vscode 和 浏览器中鼠标处于非光标形状下使用 110 | * 启用自定义系统栏图标,更有辨识度 111 | * 优化找不到目标应用的错误提示 112 | * 新增和关闭标签 适配 win11 新版 记事本 113 | * 新建标签/窗口 改为使用 Ctrl + F3 114 | * 删除用的不多的禁用脚本快捷键 Ctrl + Alt + s 115 | 116 | ### 捷键 v1.0.2 2023-08-17 117 | 118 | 优化: 119 | 120 | * 优先使用 WinClose "A" 去关闭窗口 121 | * Anyrun 组件优先使用微软雅黑字体,匹配按照优先级排序:匹配位置 + 字符串长度 122 | * 关闭标签从 Ctrl + w 改为了 Ctrl + F4 123 | 124 | ### 捷键 v1.0.1 2023-08-07 125 | 126 | 新增: 127 | 128 | * 添加 ip 查询 129 | 130 | 优化: 131 | 132 | * 捷键输入框不再区分大小写,具体规则是搜索按照匹配位置进行排序,首字母越先匹配到的越靠前 133 | 134 | ### 捷键 v1.0.0 for windows 2023-07-22 135 | 136 | 第一个正式版本 137 | 138 | * 代码注释调整 和 丰富数据源 139 | * 修复 bug 140 | * Anyrun 组件完善当鼠标在窗口外点击后关闭窗口 141 | 142 | ### 捷键 v0.0.2 for windows 2023-07-20 143 | 144 | * 运行框支持英文大小写混写 145 | * 创建 Anyrun 组件以及其他优化 146 | 147 | ### 捷键 v0.0.1 for windows 2023-07-13 148 | 149 | 初版 150 | -------------------------------------------------------------------------------- /documents/开发思路.md: -------------------------------------------------------------------------------- 1 | 遵从已有 windows 使用习惯,若非大改动,尽量增强已有快捷键。其他情况下则会考虑新造快捷键。 2 | 3 | 首先熟悉 windows 的用法 4 | 5 | * **F1 显示帮助** 6 | * **F2 重命名选定项** 7 | * F3 搜索文件或文件夹 8 | * F4 在 Windows 资源管理器中显示地址栏列表 9 | * **F5(或 Ctrl + R)刷新活动窗口** 10 | * F6 循环浏览窗口中或桌面上的屏幕元素 上一曲 11 | * F7:调用任务管理器 播放/暂停 12 | * F8:启动高级启动选项 下一曲 13 | * F9:在Windows Media Center中暂停或播放媒体 音量降低 14 | * F10 激活活动程序中的菜单栏/显示“选项”菜单,在Windows Media Center中向前或向后跳过媒体 音量提高 15 | * **F11:在全屏模式下显示浏览器**。 选用 作为万能全屏键 16 | * **F12:在浏览器中打开开发人员工具** 17 | 18 | 综合考量下我使用了 19 | 20 | * Ctrl + F4 作为万能关闭键,用于关闭标签,避免和常见的 Ctrl + w 冲突 21 | * Ctrl + F8 作为万能新建键 22 | * Ctrl + F7 作为窗口置顶键 23 | * Ctrl + F9 新建窗口 24 | * F11 依旧沿用用于全屏和取消全屏 25 | 26 | 由于 ahk 中的 hotIf 的优先匹配原则。一般 esc 会在前。由于考虑到 Esc 逃逸键 用于关闭窗口,目前支持记事本中使用。 27 | 28 | * Ctrl + F8 为新建标签/窗口 避免和已有 Ctrl + t 冲突 29 | * Ctrl + Shift + Tab / Ctrl + Tab 切换到上/下个标签 默认不需要重写 30 | * Ctrl + Shift + t 撤销关闭标签 默认不需要重写 31 | * Alt + 左方向键 比 backspace 更加通用 32 | * 后退 默认 Alt + 左 33 | * app.csv 中先后顺序关系通过优先级进行定义 34 | 35 | * 压缩软件:分别找出打开、前进和后退的快捷键。 36 | * 视频类软件:分别找出打开、快进、快退、切换上一个视频、切换下一个视频的快捷键。 37 | 38 | 在考虑窗口匹配的规则中,ahk_exe 和 ahk_class 两者中,我一般会优先考虑 exe,但是如果 class 特别能表示出该软件则以它为准,必要时会考虑组合 exe 和 class。 39 | 40 | ## 关于打包 41 | 42 | 每次打包都会输出到 out 目录。 43 | 44 | ## 软件打包构建 45 | 46 | 由于 Ahk2Exe 可以选择使用 MPRESS 或 UPX 这两款免费软件来压缩编译后的脚本。如果 MPRESS.exe 或 UPX.exe 已被复制到安装 AutoHotkey 的 "Compiler" 子文件夹中,则可以通过 /compress 参数(1 = 使用 MPRESS,2 = 使用 UPX)或 GUI 设置来压缩 .exe 文件。 47 | 48 | 32 位平台我没试过,建议在 win 7 及其以上 64 位系统 + ahk2 + [UPX 4.2.2](https://github.com/upx/upx/releases/download/v4.2.2/upx-4.2.2-win64.zip) 及其以上的组合双击 package.bat 即可打包。 49 | 50 | package.bat 的设计思路:Ahk2Exe.exe 将 ahk 转化为 exe,期间使用 `/compress 2` 指定了压缩方式。ahk 编译会触发 jiejianPostExec.ahk。jiejianPostExec.ahk 则完成了写入版本信息、文件夹命名为 jiejian-版本,并使用 7z 压缩打包文件。 51 | 52 | 但目前有个问题,jiejian32.exe 如果只打包一次无法拷贝到 out 目录下,目前我是修改脚本连续打包 2 次去解决的。 53 | 54 | ## 升级思路 55 | 56 | jiejian.exe 的文件版本为当前四位版本号,产品版本为当前编译的 ahk 版本。 57 | 58 | 升级信息会写入了注册表,而非传统 ini 文件。 59 | 60 | 升级有两个渠道,release 为正式版 和 snapshot 为测试版。release 版本中会自动检查更新间隔为 1 天。snapshot 版本中为每次启动的时候。 61 | 62 | 下载新的发布包,提取 jiejian.exe / jiejian64.exe 覆盖即可。 63 | 64 | 另外 app.csv 和 data.csv 可按需覆盖。一般情况下建议 app.csv 和 data.csv 自定义内容追加在尾部,方便迁移数据。 65 | -------------------------------------------------------------------------------- /extra/GenerateShortcuts.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/extra/GenerateShortcuts.exe -------------------------------------------------------------------------------- /extra/WGestures/WG_ferder.wg28bw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/extra/WGestures/WG_ferder.wg28bw -------------------------------------------------------------------------------- /extra/WGestures/WGestures 1.8.5.0.wgb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/extra/WGestures/WGestures 1.8.5.0.wgb -------------------------------------------------------------------------------- /extra/WindowSpyU32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/extra/WindowSpyU32.exe -------------------------------------------------------------------------------- /icons/favicon-paused.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/icons/favicon-paused.ico -------------------------------------------------------------------------------- /icons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/icons/favicon.ico -------------------------------------------------------------------------------- /imgs/1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/1.gif -------------------------------------------------------------------------------- /imgs/2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/2.gif -------------------------------------------------------------------------------- /imgs/7 动画1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/7 动画1.gif -------------------------------------------------------------------------------- /imgs/7 打开控制面板.apng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/7 打开控制面板.apng -------------------------------------------------------------------------------- /imgs/7 打开百度.apng: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/7 打开百度.apng -------------------------------------------------------------------------------- /imgs/动画2-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/动画2-2.gif -------------------------------------------------------------------------------- /imgs/动画2-3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/动画2-3.gif -------------------------------------------------------------------------------- /imgs/动画2-4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/动画2-4.gif -------------------------------------------------------------------------------- /imgs/动画3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/动画3.gif -------------------------------------------------------------------------------- /imgs/动画4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/动画4.gif -------------------------------------------------------------------------------- /imgs/动画5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/动画5.gif -------------------------------------------------------------------------------- /imgs/动画6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/imgs/动画6.gif -------------------------------------------------------------------------------- /jiejianPostExec.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/jiejianPostExec.exe -------------------------------------------------------------------------------- /lang/ar.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/ar.ini -------------------------------------------------------------------------------- /lang/de.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/de.ini -------------------------------------------------------------------------------- /lang/en.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/en.ini -------------------------------------------------------------------------------- /lang/es.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/es.ini -------------------------------------------------------------------------------- /lang/fr.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/fr.ini -------------------------------------------------------------------------------- /lang/it.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/it.ini -------------------------------------------------------------------------------- /lang/ja.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/ja.ini -------------------------------------------------------------------------------- /lang/ko.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/ko.ini -------------------------------------------------------------------------------- /lang/pt.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/pt.ini -------------------------------------------------------------------------------- /lang/ru.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/ru.ini -------------------------------------------------------------------------------- /lang/tr.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/tr.ini -------------------------------------------------------------------------------- /lang/zh-Hans.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/zh-Hans.ini -------------------------------------------------------------------------------- /lang/zh-Hant.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/lang/zh-Hant.ini -------------------------------------------------------------------------------- /lib/Actions.ahk: -------------------------------------------------------------------------------- 1 | /** 2 | * 智能的关闭窗口,如果是桌面就 alt +f4 3 | */ 4 | SmartCloseWindow() { 5 | if (WinExist('A')) { 6 | if (IsDesktop() or (WinGetClass('A') == 'ApplicationFrameWindow' || GetProcessName() == 'explorer.exe')) 7 | Send '!{F4}' 8 | else { 9 | try { 10 | PostMessage(0x112, 0xF060, , , 'A') 11 | } catch Error as e { 12 | MsgBox '该程序暂时无法关闭,2 秒后弹窗自动关闭后请重试', , 'T2' 13 | } 14 | } 15 | } else { 16 | MsgBox '无活动窗口,2 秒后弹窗自动关闭后请重试', , 'T2' 17 | } 18 | } 19 | 20 | /** 21 | * 启动程序或切换到程序 22 | * @param {string} winTitle AHK 中的 WinTitle 23 | * 24 | * @param {string} target 程序的路径 25 | * @param {string} args 参数 26 | * @param {string} workingDir 工作文件夹 27 | * @param {bool} admin 是否为管理员启动 28 | * @param {bool} isHide 窗口是否为隐藏窗口 29 | * @returns {void} 30 | */ 31 | ActivateOrRun(winTitle := '', target := '', args := '', workingDir := '', admin := false, isHide := false, runInBackground := false) { 32 | ; 如果是程序或参数中带有“选中的文件” 则通过该程序打开该连接 33 | if (InStr(target, '{selected}') || InStr(args, '{selected}')) { 34 | ; 没有获取到文字直接返回 35 | if !ReplaceSelectedText(&target, &args) 36 | return 37 | } 38 | 39 | ; 切换程序 40 | winTitle := Trim(winTitle) 41 | if (winTitle && activateWindow(winTitle, isHide)) 42 | return 43 | 44 | ; 程序没有运行,运行程序 45 | if not target { 46 | return 47 | } 48 | workingDir := workingDir ? workingDir : A_WorkingDir 49 | RunPrograms(target, args, workingDir, admin, runInBackground) 50 | } 51 | 52 | /** 53 | * 窗口置顶 54 | */ 55 | ToggleWindowTopMost() { 56 | value := !(WinGetExStyle('A') & 0x8) 57 | WinSetAlwaysOnTop(value, 'A') 58 | if value 59 | Tip '已置顶当前窗口' 60 | else 61 | Tip '取消置顶' 62 | } 63 | 64 | ; 关闭显示器: 65 | SystemSleepScreen() { 66 | Sleep 1000 ; 让用户有机会释放按键(以防释放它们时再次唤醒显视器) 67 | ; 关闭显示器: 68 | SendMessage 0x0112, 0xF170, 2,, 'Program Manager' ; 0x0112 is WM_SYSCOMMAND, 0xF170 is SC_MONITORPOWER. 69 | } 70 | 71 | /** 72 | * 锁屏 73 | */ 74 | SystemLockScreen() { 75 | Sleep 300 76 | DllCall 'LockWorkStation' 77 | } 78 | 79 | /** 80 | * 注销 81 | */ 82 | SystemLogoff() { 83 | Shutdown 0 84 | } 85 | 86 | /** 87 | * 关机 88 | */ 89 | SystemShutdown() { 90 | ; 如果存在 SlideToShutDown.exe 则使用滑动关机,否则使用普通关机 91 | if (FileExist(A_WinDir "\System32\SlideToShutDown.exe")) { 92 | Run 'SlideToShutDown.exe' 93 | sleep 1300 94 | CoordMode 'Mouse' 95 | MouseClick('Left', 100, 100) 96 | } else { 97 | Shutdown 1 98 | } 99 | } 100 | 101 | /** 102 | * 重启 103 | */ 104 | SystemReboot() { 105 | Shutdown 2 106 | } 107 | 108 | /** 109 | * 睡眠 110 | */ 111 | SystemSleep() { 112 | ; 调用 Windows API 函数 "SetSuspendState" 来让系统挂起或休眠. 注意, 第二个参数对较新的系统可能没有任何影响 113 | ; 参数 #1: 使用 1 代替 0 来进行休眠而不是挂起 114 | ; 参数 #2: 使用 1 代替 0 来立即挂起而不询问每个应用程序以获得许可 115 | ; 参数 #3: 使用 1 而不是 0 来禁止所有的唤醒事件 116 | DllCall("PowrProf\SetSuspendState", "Int", 0, "Int", 0, "Int", 0) 117 | } 118 | 119 | CopySelectedAsPlainText() { 120 | A_Clipboard := "" 121 | Send("^c") 122 | if (!ClipWait(1)) { 123 | Tip('复制失败') 124 | return 125 | } 126 | A_Clipboard := A_Clipboard 127 | Tip '路径已复制' 128 | } 129 | 130 | CopySelectedAsPlainTextQuiet() { 131 | A_Clipboard := "" 132 | Send "^c" 133 | if (!ClipWait(1)) { 134 | Tip "复制失败" 135 | return 136 | } 137 | A_Clipboard := A_Clipboard 138 | } 139 | 140 | /** 141 | * 窗口最大化 142 | */ 143 | MaximizeWindow() { 144 | if NotActiveWin() 145 | return 146 | 147 | if WindowMaxOrMin() 148 | WinRestore "A" 149 | else 150 | WinMaximize "A" 151 | } 152 | 153 | /** 154 | * 窗口最小化 155 | */ 156 | MinimizeWindow() { 157 | if NotActiveWin() || WinGetProcessName("A") = "Rainmeter.exe" 158 | return 159 | WinMinimize 'A' 160 | } 161 | 162 | /** 163 | * 窗口居中并修改为指定大小 164 | * @param width 窗口宽度 165 | * @param height 窗口高度 166 | * @returns {void} 167 | */ 168 | CenterAndResizeWindow(width, height) { 169 | if NotActiveWin() 170 | return 171 | 172 | ; 在 mousemove 时需要 PER_MONITOR_AWARE (-3), 否则当两个显示器有不同的缩放比例时, mousemove 会有诡异的漂移 173 | ; 在 winmove 时需要 UNAWARE (-1), 这样即使写死了窗口大小为 1200x800, 系统会帮你缩放到合适的大小 174 | ; 不适用于 win 7 以下系统 175 | if VerCompare(A_OSVersion, "6.2") >= 0 176 | DllCall("SetThreadDpiAwarenessContext", "ptr", -1, "ptr") 177 | 178 | WinExist("A") 179 | if WindowMaxOrMin() 180 | WinRestore 181 | ; 返回监视器的数量 182 | monitorCount := MonitorGetCount() 183 | if (monitorCount > 1) { 184 | ; 获取指定窗口的位置和大小 185 | WinGetPos(&x, &y, &w, &h) 186 | ; ms 为 监视器编号, 介于 1 和 MonitorGetCount 返回的数字之间 187 | monitorCount := GetMonitorAt(x + w / 2, y + h / 2) 188 | } 189 | ; 检查指定的监视器是否存在, 并可选地检索其工作区域的边界坐标。分别为左上右下 190 | MonitorGetWorkArea(monitorCount, &l, &t, &r, &b) 191 | ; 获得当前屏幕的宽度和高度 192 | w := r - l 193 | h := b - t 194 | 195 | ; 预设宽度 和 屏幕宽度的最小值 196 | finalWidth := Min(width, w) 197 | ; 预设高度 和 屏幕宽度的最小值 198 | finalHeight := Min(height, h) 199 | 200 | ; 设置最小宽度 和 高度 201 | if finalWidth < 160 202 | finalWidth := 160 203 | if finalHeight < 100 204 | finalHeight := 100 205 | 206 | ; 最终窗口的 x 值 207 | finalX := l + (w - finalWidth) / 2 208 | ; 最终窗口的 y 值 209 | finalY := t + (h - finalHeight) / 2 210 | 211 | WinMove(finalX, finalY, finalWidth, finalHeight) 212 | if VerCompare(A_OSVersion, "6.2") >= 0 213 | DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr") 214 | } 215 | 216 | ; 我新加的 窗口按照屏幕的百分比 和 宽高比 16:9 进行显示 217 | CenterAndResizeWindow_X_Percent(percent) { 218 | if NotActiveWin() 219 | return 220 | 221 | ; 在 mousemove 时需要 PER_MONITOR_AWARE (-3), 否则当两个显示器有不同的缩放比例时, mousemove 会有诡异的漂移 222 | ; 在 winmove 时需要 UNAWARE (-1), 这样即使写死了窗口大小为 1200x800, 系统会帮你缩放到合适的大小 223 | ; 不适用于 win 7 以下系统 224 | if VerCompare(A_OSVersion, '6.2') >= 0 225 | DllCall("SetThreadDpiAwarenessContext", "ptr", -1, "ptr") 226 | 227 | WinExist("A") 228 | if WindowMaxOrMin() 229 | WinRestore 230 | ; 返回监视器的数量 231 | monitorCount := MonitorGetCount() 232 | if (monitorCount > 1) { 233 | ; 获取指定窗口的位置和大小 234 | WinGetPos(&x, &y, &w, &h) 235 | ; ms 为 监视器编号, 介于 1 和 MonitorGetCount 返回的数字之间 236 | monitorCount := GetMonitorAt(x + w / 2, y + h / 2) 237 | } 238 | ; 检查指定的监视器是否存在, 并可选地检索其工作区域的边界坐标。分别为左上右下 239 | MonitorGetWorkArea(monitorCount, &l, &t, &r, &b) 240 | 241 | ; 获得宽度和高度 242 | w := r - l 243 | h := b - t 244 | 245 | ; 高度作为基准 246 | finalHeight := h * percent 247 | ; 宽度按照 16:9 这样在超宽屏幕上显示正常 248 | finalWidth := finalHeight * 16 / 9 249 | ; 最终窗口的 x 值 250 | finalX := l + (w - finalWidth) / 2 251 | ; 最终窗口的 y 值 252 | finalY := t + (h - finalHeight) / 2 253 | 254 | WinMove(finalX, finalY, finalWidth, finalHeight) 255 | if VerCompare(A_OSVersion, '6.2') >= 0 256 | DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr") 257 | } 258 | 259 | ; 我新加的 高度拓展至全屏 260 | SetWindowHeightToFullScreen() { 261 | if NotActiveWin() 262 | return 263 | 264 | ; 在 mousemove 时需要 PER_MONITOR_AWARE (-3), 否则当两个显示器有不同的缩放比例时, mousemove 会有诡异的漂移 265 | ; 在 winmove 时需要 UNAWARE (-1), 这样即使写死了窗口大小为 1200x800, 系统会帮你缩放到合适的大小 266 | if VerCompare(A_OSVersion, '6.2') >= 0 267 | DllCall("SetThreadDpiAwarenessContext", "ptr", -1, "ptr") 268 | 269 | WinExist("A") 270 | WinGetPos(&originX,,&originW) 271 | 272 | if (WindowMaxOrMin()) 273 | WinRestore 274 | 275 | ; 获取指定窗口的位置和大小 276 | WinGetPos(&x, &y, &w, &h) 277 | ; ms 为 监视器编号, 介于 1 和 MonitorGetCount 返回的数字之间. 278 | ms := GetMonitorAt(x + w / 2, y + h / 2) 279 | 280 | ; 分别为左上右下 281 | MonitorGetWorkArea(ms,, &t,, &b) 282 | WinMove(originX, t, originW, b - t) 283 | 284 | if VerCompare(A_OSVersion, '6.2') >= 0 285 | DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr") 286 | } 287 | 288 | ; 我新加的 宽度拓展至全屏 289 | SetWindowWeightToFullScreen() { 290 | if NotActiveWin() 291 | return 292 | 293 | ; 在 mousemove 时需要 PER_MONITOR_AWARE (-3), 否则当两个显示器有不同的缩放比例时, mousemove 会有诡异的漂移 294 | ; 在 winmove 时需要 UNAWARE (-1), 这样即使写死了窗口大小为 1200x800, 系统会帮你缩放到合适的大小 295 | 296 | if VerCompare(A_OSVersion, "6.2") >= 0 297 | DllCall("SetThreadDpiAwarenessContext", "ptr", -1, "ptr") 298 | 299 | WinExist("A") 300 | WinGetPos(, &originY, , &originH) 301 | 302 | if WindowMaxOrMin() 303 | WinRestore 304 | 305 | ; 获取指定窗口的位置和大小 306 | WinGetPos(&x, &y, &w, &h) 307 | 308 | ; 如果是 360 极速浏览器则特殊处理 309 | processName := WinGetProcessName('A') 310 | offset := 0 311 | if processName = '360ChromeX.exe' 312 | offset := 3 313 | 314 | ; ms 为 监视器编号, 介于 1 和 MonitorGetCount 返回的数字之间. 315 | ms := GetMonitorAt(x + w / 2, y + h / 2) 316 | ; 分别为左上右下 317 | MonitorGetWorkArea(ms, &l,, &r) 318 | WinMove(l + offset, originY, r - (l + offset) - offset, originH) 319 | 320 | if VerCompare(A_OSVersion, '6.2') >= 0 321 | DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr") 322 | } 323 | 324 | ; 我新加的 窗口按照 指定像素进行缩放,例如 10 或者 -10 325 | CenterAndResizeWindow_window_percent(step) { 326 | if NotActiveWin() 327 | return 328 | 329 | WinExist("A") 330 | ; 不适用于最大化和最小化的状态下 331 | if (WindowMaxOrMin()) { 332 | Tip("请在窗口模式下缩放") 333 | return 334 | } 335 | 336 | ; 在 mousemove 时需要 PER_MONITOR_AWARE (-3), 否则当两个显示器有不同的缩放比例时, mousemove 会有诡异的漂移 337 | ; 在 winmove 时需要 UNAWARE (-1), 这样即使写死了窗口大小为 1200x800, 系统会帮你缩放到合适的大小 338 | ; 不适用于 win 7 以下系统 339 | if VerCompare(A_OSVersion, '6.2') >= 0 340 | DllCall("SetThreadDpiAwarenessContext", "ptr", -1, "ptr") 341 | 342 | ; 获取指定窗口的位置和大小 343 | WinGetPos(&x, &y, &w, &h) 344 | 345 | ; 返回监视器的数量 346 | monitorCount := MonitorGetCount() 347 | if (monitorCount > 1) { 348 | ; ms 为 监视器编号, 介于 1 和 MonitorGetCount 返回的数字之间 349 | monitorCount := GetMonitorAt(x + w / 2, y + h / 2) 350 | } 351 | ; 检查指定的监视器是否存在, 并可选地检索其工作区域的边界坐标。分别为 左上右下 352 | MonitorGetWorkArea(monitorCount, &l, &t, &r, &b) 353 | ; 获得监视器的宽度和高度 354 | monitorWidth := r - l 355 | monitorHeight := b - t 356 | 357 | ; 如果是扩大 358 | if (step > 0) { 359 | finalX := unset 360 | finalY := unset 361 | if (x < 0) { 362 | finalX := 0 363 | } else if (x + w / 2.0 > monitorWidth) { ; 如果窗口超过一半被挡住 364 | finalX := monitorWidth - w 365 | } 366 | if y < 0 { 367 | finalY := 0 368 | } else if (y + h / 2.0 > monitorHeight) { ; 如果窗口超过一半被挡住 369 | finalY := monitorHeight - h 370 | } 371 | 372 | if (IsSet(finalX) OR IsSet(finalY)) { 373 | if NOT IsSet(finalX) 374 | finalX := x 375 | if NOT IsSet(finalY) 376 | finalY := y 377 | finalWidth := w 378 | finalHeight := h 379 | } else { 380 | ; 只需要再当前窗口的基础上 x 和 y 轴拓展 二分之 step 长度即可 381 | finalWidth := w + step 382 | finalHeight := h + step 383 | if finalWidth > monitorWidth 384 | finalWidth := monitorWidth 385 | if finalHeight > monitorHeight 386 | finalHeight := monitorHeight 387 | 388 | finalX := x - step / 2.0 389 | if finalX < 0 390 | finalX := 0 391 | 392 | finalY := y - step / 2.0 393 | if finalY < 0 394 | finalY := 0 395 | } 396 | } else { 397 | ; 否则表示缩小 398 | myMinimumWidth := 560 399 | myMinimumHeight := 300 400 | ; 如果是 猫眼浏览器则特殊处理 最小宽度 401 | ; processName := WinGetProcessName("A") 402 | ; switch processName, false { 403 | ; default: 404 | ; } 405 | 406 | if (w <= myMinimumWidth and h <= myMinimumHeight) { 407 | if VerCompare(A_OSVersion, '6.2') >= 0 408 | DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr") 409 | return 410 | } 411 | if (w > myMinimumWidth) { 412 | ; 如果原有宽度大于最小宽度则还有操作空间 413 | if (w + step <= myMinimumWidth) { 414 | finalWidth := myMinimumWidth 415 | finalX := x + (w - myMinimumWidth) / 2.0 416 | } else { 417 | finalWidth := w + step 418 | finalX := x - step / 2.0 419 | } 420 | } else { 421 | finalWidth := w 422 | finalX := x 423 | } 424 | 425 | if (h > myMinimumHeight) { 426 | if (h + step <= myMinimumHeight) { 427 | finalHeight := myMinimumHeight 428 | finalY := y + (h - myMinimumHeight) / 2.0 429 | } else { 430 | finalHeight := h + step 431 | finalY := y - step / 2.0 432 | } 433 | } else { 434 | finalHeight := h 435 | finalY := y 436 | } 437 | } 438 | ; 记住缩小的上次的数值,防止最小状态下再次缩小 439 | static lastStep := 0 440 | static prevW := 0 441 | static prevH := 0 442 | ; 如果是缩小 且 两次数值相等且 非屏幕的宽度和高度 443 | if (lastStep < 0 and step < 0) { 444 | ; 若无再次缩小宽度的可能 445 | if (prevW == w) { 446 | if (prevH == h) { 447 | if VerCompare(A_OSVersion, '6.2') >= 0 448 | DllCall("SetThreadDpiAwarenessContext", 'ptr', -3, 'ptr') 449 | return 450 | } else { 451 | ; 不再缩小 452 | finalWidth := w 453 | finalX := x 454 | } 455 | } 456 | } 457 | 458 | WinMove(finalX, finalY, finalWidth, finalHeight) 459 | lastStep := step 460 | prevW := w 461 | prevH := h 462 | 463 | if NOT A_IsCompiled 464 | tip 'x = ' x ' y = ' y ' w = ' w ' h = ' h '`n finalX = ' finalX ' finalY = ' finalY ' finalWidth = ' finalWidth ' finalHeight = ' finalHeight '`n 屏幕宽度 = ' monitorWidth ' 屏幕高度 = ' monitorHeight 465 | 466 | if VerCompare(A_OSVersion, "6.2") >= 0 467 | DllCall("SetThreadDpiAwarenessContext", 'ptr', -3, 'ptr') 468 | } 469 | 470 | ; 左移动 x 像素,上移动 y 像素 471 | MoveRelative(relativeX, relativeY := 0) { 472 | if NotActiveWin() 473 | return 474 | 475 | WinExist("A") 476 | ; 不适用于最大化和最小化的状态下 477 | if (WindowMaxOrMin()) { 478 | Tip '请在窗口模式下缩放' 479 | } else { 480 | WinGetPos(&x, &y) 481 | WinMove(x + relativeX, y + relativeY) 482 | } 483 | } 484 | 485 | RunAHK(scriptPath) { 486 | if A_IsCompiled 487 | Run('jiejian' . (A_PtrSize == 4 ? '32' : '64') . '.exe /script ' . scriptPath) 488 | else 489 | Run('compiler/AutoHotkey' . (A_PtrSize == 4 ? '32' : '64') . '.exe /script ' . scriptPath) 490 | } 491 | 492 | SoundControl() { 493 | wnd := WinExist('A') 494 | if wnd 495 | ActivateOrRun(, 'tools\SoundControl.exe', "PreviousWindow " wnd) 496 | else 497 | ActivateOrRun(, 'tools\SoundControl.exe') 498 | } 499 | -------------------------------------------------------------------------------- /lib/Functions.ahk: -------------------------------------------------------------------------------- 1 | /** 2 | * 没有活动窗口或是桌面返回 True 反之返回 false 3 | */ 4 | NotActiveWin() { 5 | return IsDesktop() || not WinExist("A") 6 | } 7 | 8 | /** 9 | * 判断当前窗口是不是桌面 10 | */ 11 | IsDesktop() { 12 | return WinActive("Program Manager ahk_class Progman") || WinActive("ahk_class WorkerW") 13 | } 14 | 15 | /** 16 | * 获取当前程序名称 17 | * 自带的 WinGetProcessName 无法获取到 uwp 应用的名称 18 | * 来源:https://www.autohotkey.com/boards/viewtopic.php?style=7&t=112906 19 | * 20 | * @returns {string} 21 | */ 22 | GetProcessName() { 23 | fn := (winTitle) => (WinGetProcessName(winTitle) == 'ApplicationFrameHost.exe') 24 | winTitle := 'A' 25 | if (fn(winTitle)) { 26 | for hCtrl in WinGetControlsHwnd(winTitle) 27 | bool := fn(hCtrl) 28 | until !bool && winTitle := hCtrl 29 | } 30 | return WinGetProcessName(winTitle) 31 | } 32 | 33 | /** 34 | * 将程序路径或参数中的 {selected} 替换为选中的文字 35 | * @param target 程序路径的引用 36 | * @param args 参数的引用 37 | * @returns {void|number} 38 | */ 39 | ReplaceSelectedText(&target, &args) { 40 | text := GetSelectedText() 41 | if not (text) 42 | text := '' 43 | 44 | if InStr(args, "://") || InStr(target, "://") 45 | text := URIEncode(text) 46 | 47 | args := strReplace(args, '{selected}', text) 48 | target := strReplace(target, '{selected}', text) 49 | 50 | return true 51 | } 52 | 53 | /** 54 | * 获取选中的文字 55 | * @returns {void|string} 56 | */ 57 | GetSelectedText() { 58 | temp := A_Clipboard 59 | ; 清空剪贴板 60 | A_Clipboard := "" 61 | 62 | Send("^c") 63 | if not (ClipWait(0.4)) { 64 | Tip("没有选中文本或文件", -700) 65 | return 66 | } 67 | text := A_Clipboard 68 | 69 | A_Clipboard := temp 70 | return RTrim(text, "`r`n") 71 | } 72 | 73 | /** 74 | * url 编码 75 | * 来源: https://www.autohotkey.com/boards/viewtopic.php?t=112741 76 | * @param Uri 需要编码的文本 77 | * @param {string} encoding 编码格式 78 | * @returns {string} 79 | */ 80 | URIEncode(Uri, encoding := "UTF-8") { 81 | if uri == '' 82 | return '' 83 | var := Buffer(StrPut(Uri, encoding), 0) 84 | StrPut(Uri, var, encoding) 85 | pos := 1 86 | ; 按字节遍历 buffer 中的 utf-8 字符串, 注意字符串有 null-terminator 87 | While pos < var.Size { 88 | code := NumGet(var, pos - 1, "UChar") 89 | if (code >= 0x30 && code <= 0x39) || (code >= 0x41 && code <= 0x5A) || (code >= 0x61 && code <= 0x7A) { 90 | res .= Chr(code) 91 | } else { 92 | res .= "%" . Format("{:02X}", code) 93 | } 94 | pos++ 95 | } 96 | return res 97 | } 98 | 99 | /** 100 | * 激活窗口 101 | * @param winTitle AHK中的WinTitle 102 | * @param {number} isHide 窗口是否为隐藏窗口 103 | * @returns {number} 104 | */ 105 | ActivateWindow(winTitle := "", isHide := false) { 106 | ; 如果匹配不到窗口且认为窗口为隐藏窗口时查找隐藏窗口 107 | hwnds := FindWindows(winTitle, (hwnd) => WinGetTitle(hwnd) != "") 108 | if ((!hwnds.Length) && isHide) { 109 | hwnds := FindHiddenWindows(winTitle) 110 | if hwnds.Length { 111 | WinShow(hwnds.Get(1)) 112 | WinActivate(hwnds.Get(1)) 113 | return true 114 | } 115 | } 116 | 117 | ; 如果匹配到则跳转,匹配不到返回0 118 | if (!hwnds.Length) { 119 | return false 120 | } 121 | 122 | ; 只有一个窗口为最小化则切换否则最小化 123 | if (hwnds.Length = 1) { 124 | hwnd := hwnds.Get(1) 125 | ; 指定不为活动窗口或窗口被缩小则显示出来 126 | if (WinExist("A") != hwnd || WinGetMinMax(hwnd) = -1) { 127 | WinActivate(hwnd) 128 | } else { 129 | WinMinimize(hwnd) 130 | } 131 | } else { 132 | ; 如果多个窗口则来回切换 133 | LoopRelatedWindows(winTitle, hwnds) 134 | } 135 | return true 136 | } 137 | 138 | /** 139 | * 返回与指定条件匹配的所有窗口 140 | * @param winTitle AHK中的WinTitle 141 | * @param predicate 过滤窗口方法,传过Hwnd,返回bool 142 | * @returns {array} 143 | */ 144 | FindWindows(winTitle, predicate?) { 145 | temps := WinGetList(winTitle) 146 | ; 不需要做任何匹配直接返回 147 | if not (IsSet(predicate)) { 148 | return temps 149 | } 150 | 151 | hwnds := [] 152 | for i, hwnd in temps { 153 | ; 当有谓词条件且满足时添加这个hwnd 154 | if predicate(hwnd) { 155 | hwnds.Push(hwnd) 156 | } 157 | } 158 | return hwnds 159 | } 160 | 161 | /** 162 | * 查找隐藏窗口返回窗口的Hwnd 163 | * @param winTitle AHK中的WinTitle 164 | * @returns {array} 165 | */ 166 | FindHiddenWindows(winTitle) { 167 | WS_MINIMIZEBOX := 0x00020000 168 | WS_MINIMIZE := 0x20000000 169 | 170 | ; 窗口过滤条件 171 | ; 标题不为空、包含最小化按钮 172 | Predicate(hwnd) { 173 | if (WinGetTitle(hwnd) = "") 174 | return false 175 | 176 | style := WinGetStyle(hwnd) 177 | return style & WS_MINIMIZEBOX 178 | } 179 | 180 | ; 开启可以查找到隐藏窗口 181 | DetectHiddenWindows true 182 | hwnds := FindWindows(winTitle, Predicate) 183 | DetectHiddenWindows false 184 | 185 | return hwnds 186 | } 187 | 188 | /** 189 | * 轮换程序窗口 190 | * @param winTitle AHK中的WinTitle 191 | * @param hwnds 活动窗口的句柄数组 192 | * @returns {void|number} 193 | */ 194 | LoopRelatedWindows(winTitle?, hwnds?) { 195 | ; 如果没有传句柄数组则获取当前窗口的 196 | if not (IsSet(hwnds)) { 197 | predicate := (hwnd) => WinGetTitle(hwnd) != "" 198 | if (GetProcessName() == "explorer.exe") { 199 | predicate := (hwnd) => WinGetClass(hwnd) = "CabinetWClass" 200 | } 201 | hwnds := FindWindows("ahk_pid " WinGetPID("A"), predicate) 202 | } 203 | 204 | ; 只有一个窗口显示出来就行 205 | if (hwnds.Length = 1) { 206 | WinActivate(hwnds.Get(1)) 207 | return 208 | } 209 | 210 | ; 没有传 winTitle 时,则获取当前程序的名称 211 | if not (IsSet(winTitle)) { 212 | class := WinGetClass("A") 213 | if (class == "ApplicationFrameWindow") { 214 | winTitle := WinGetTitle("A") " ahk_class ApplicationFrameWindow" 215 | } else { 216 | winTitle := "ahk_exe " GetProcessName() 217 | } 218 | } 219 | winTitle := Trim(winTitle) 220 | 221 | static winGroup, lastWinTitle := "", lastHwnd := "", gi := 0 222 | if (winTitle != lastWinTitle || lastHwnd != WinExist("A")) { 223 | lastWinTitle := winTitle 224 | winGroup := "AutoName" gi++ 225 | } 226 | 227 | ; 将所有的hwnd都添加到组里 228 | for hwnd in hwnds { 229 | GroupAdd(winGroup, "ahk_id" hwnd) 230 | } 231 | 232 | ; 切换 233 | lastHwnd := GroupActivate(winGroup, "R") 234 | return lastHwnd 235 | } 236 | 237 | /** 238 | * 运行程序或打开目录,用于解决打开的程序无法获取焦点的问题 239 | * @param target 程序路径 240 | * @param {string} args 参数 241 | * @param {string} workingDir 工作目录 242 | * @param {number} admin 是否为管理员启动 243 | * @returns {void} 244 | */ 245 | RunPrograms(target, args := "", workingDir := "", admin := false, runInBackground := false) { 246 | ; 记录当前窗口的hwnd,当软件启动失败时还原焦点 247 | currentHwnd := WinExist("A") 248 | 249 | if !runInBackground { 250 | ActivateDesktop() 251 | } 252 | 253 | try { 254 | ; 补全程序路径 255 | programPath := CompleteProgramPath(target) 256 | 257 | ; 如果是文件夹直接打开 258 | if (InStr(FileExist(programPath), "D")) { 259 | Run(programPath) 260 | return 261 | } 262 | 263 | ; 避免在快捷方式无效,导致的程序卡住 264 | ShortcutTargetExist(programPath) 265 | 266 | if (admin) { 267 | runAsAdmin(programPath, args, workingDir, runInBackground ? "Hide" : "") 268 | } else { 269 | ; 直接 run "https://example.com" 会让 chrome 以管理员启动 270 | ; ShellRun 也支持 ms-setting: 或 shell: 或 http: 之类的链接 271 | ShellRun(programPath, args, workingDir, , runInBackground ? 0 : unset) 272 | } 273 | 274 | } catch Error as e { 275 | Tip(e.Message) 276 | ; 还原窗口焦点 277 | try WinActivate(currentHwnd) 278 | return 279 | } 280 | } 281 | 282 | ActivateDesktop() { 283 | tmp := A_DetectHiddenWindows 284 | DetectHiddenWindows true 285 | if WinExist('ahk_class ForegroundStaging') 286 | WinActivate 287 | DetectHiddenWindows tmp 288 | } 289 | 290 | /** 291 | * 从环境中补全程序的绝对路径 292 | * 来源: https://autohotkey.com/board/topic/20807-fileexist-in-path-environment/ 293 | * @param target 程序路径 294 | * @returns {string|any} 295 | */ 296 | CompleteProgramPath(target) { 297 | ; 工作目录下的程序 298 | PathName := A_WorkingDir . '\' . target 299 | if FileExist(PathName) 300 | return PathName 301 | 302 | ; 本身便是绝对路径 303 | if FileExist(target) 304 | return target 305 | 306 | ; 从环境变量 PATH 中获取 307 | DosPath := EnvGet('PATH') 308 | loop parse DosPath, "`;" { 309 | if A_LoopField == "" 310 | continue 311 | 312 | if FileExist(A_LoopField "\" target) 313 | return A_LoopField "\" target 314 | } 315 | 316 | ; 从安装的程序中获取 317 | try { 318 | PathName := RegRead('HKLM', 'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\' . target) 319 | if FileExist(PathName) 320 | return PathName 321 | } 322 | 323 | return target 324 | } 325 | 326 | /** 327 | * 快捷方式指向目标是否存在,不存在抛出异常 328 | * @param LnkPath 快捷方式路径 329 | */ 330 | ShortcutTargetExist(LnkPath) { 331 | if SubStr(LnkPath, -4) == ".lnk" { 332 | FileGetShortcut(LnkPath, &OutTarget) 333 | 334 | ; 没有获取到目标路径可能是因为是 uwp 应用的快捷方式 335 | ; 也有可能是 ms-setting: 或shell: 之类的连接 336 | if !OutTarget || SubStr(outTarget, 2, 2) != ":\" 337 | return 338 | 339 | if !FileExist(OutTarget) 340 | throw Error("快捷方式指向的目标不存在`n快捷方式: " LnkPath "`n指向目标: " OutTarget) 341 | } 342 | } 343 | 344 | /** 345 | * 以管理员权限打开软件 346 | * @param target 程序路径 347 | * @param args 参数 348 | * @param workingDir 工作目录 349 | */ 350 | RunAsAdmin(target, args, workingDir, options) { 351 | try { 352 | Run("*RunAs " target " " args, workingDir, options) 353 | } catch Error as e { 354 | Tip("使用管理启动失败 " target ", " e.Message) 355 | } 356 | } 357 | 358 | /** 359 | * 通过命令行去启动程序,防止会导致以管理员启动软件的问题 360 | * @param target 程序路径 361 | * @param arguments 参数 362 | * @param directory 工作目录 363 | * @param operation 选项 (runAS/open/edit/print 364 | * @param show 是否显示 365 | */ 366 | ShellRun(target, arguments?, directory?, operation?, show?) { 367 | static VT_UI4 := 0x13, SWC_DESKTOP := ComValue(VT_UI4, 0x8) 368 | ComObject("Shell.Application").Windows.Item(SWC_DESKTOP).Document.Application 369 | .ShellExecute(target, arguments?, directory?, operation?, show?) 370 | } 371 | 372 | /** 373 | * 当前窗口是最大化还是最小化 374 | * @param {string} winTitle AHK中的WinTitle 375 | * @returns {number} 376 | */ 377 | WindowMaxOrMin(winTitle := "A") { 378 | return WinGetMinMax(winTitle) 379 | } 380 | 381 | /** 382 | * 获取当前焦点在哪个显示器上 383 | * @param x 窗口X轴的长度 384 | * @param y 窗口y轴的长度 385 | * @param {number} default 显示器下标 386 | * @returns {string|number} 匹配的显示器下标 387 | */ 388 | GetMonitorAt(x, y, default := 1) { 389 | ; 80 SM_CMONITORS: 桌面上监视器数目(不包括 "不显示的伪监视器") 390 | m := SysGet(80) 391 | loop m { 392 | MonitorGet(A_Index, &l, &t, &r, &b) 393 | if (x >= l && x <= r && y >= t && y <= b) 394 | return A_Index 395 | } 396 | return default 397 | } 398 | -------------------------------------------------------------------------------- /lib/KeymapManager.ahk: -------------------------------------------------------------------------------- 1 | class KeymapManager { 2 | static GlobalKeymap := Keymap("GlobalKeymap") 3 | static Stack := Array(this.GlobalKeymap) 4 | static L := { toLock: false, locked: false, show: false, toggle: false } 5 | 6 | static NewKeymap(globalHotkey, name, delay) { 7 | if globalHotkey == "customHotkeys" { 8 | return this.GlobalKeymap 9 | } 10 | 11 | ; 在全局 keymap 中添加一个 globalHotkey, 用来激活指定的 keymap, 例如 CapsLock 模式 12 | ; 让这些 globalHotkey 在特定程序中被禁用, 也就实现了 MyKeymap 在特定程序中被禁用 13 | winTitle := this.GlobalKeymap.DisabledAt 14 | conditionType := winTitle ? 3 : 0 15 | return this.AddSubKeymap(this.GlobalKeymap, globalHotkey, name, delay, winTitle, conditionType) 16 | } 17 | 18 | static AddSubKeymap(parent, hk, name := "", delay := 0, winTitle := "", conditionType := 0) { 19 | waitKey := ExtractWaitKey(hk) 20 | subKeymap := Keymap(name, waitKey, hk, delay) 21 | handler(thisHotkey) { 22 | this.Activate(subKeymap) 23 | this._postHandler() 24 | } 25 | parent.Map(hk, handler, , winTitle, conditionType) 26 | return subKeymap 27 | } 28 | 29 | static _handleDelay(keymap) { 30 | if keymap.delay { 31 | ; Tip(keymap.Name " " keymap.delay) 32 | ih := InputHook("T" keymap.delay) 33 | ih.KeyOpt("{All}", "E") 34 | ih.Start() 35 | Suspend 36 | while true { 37 | if !ih.InProgress && ih.EndReason == "Timeout" { 38 | Suspend 39 | break 40 | } 41 | if !GetKeyState(keymap.WaitKey, "P") || (!ih.InProgress && ih.EndReason != "Timeout") { 42 | ih.Stop() 43 | Send("{blind}{" keymap.WaitKey "}{" ih.EndKey "}") 44 | KeyWait(keymap.WaitKey) 45 | Suspend 46 | return true 47 | } 48 | } 49 | } 50 | } 51 | 52 | static Activate(keymap) { 53 | if this._handleDelay(keymap) { 54 | return 55 | } 56 | parent := this.Stack[-1] 57 | locked := this.Stack[1] 58 | ; 比如锁住 3 模式再按 3 键触发 3 模式应该没效果 59 | if keymap != locked { 60 | this.Stack.Push(keymap) 61 | keymap.Enable(parent) 62 | } 63 | startTick := A_TickCount 64 | keymap.Wait(startTick) 65 | if keymap != locked { 66 | this.Stack.Pop() 67 | keymap.Disable() 68 | } 69 | } 70 | 71 | static _postHandler() { 72 | ; 等松开全部按钮时才处理锁定逻辑 73 | if this.Stack.Length != 1 || !this.L.toLock { 74 | return 75 | } 76 | 77 | ; 未锁定 78 | if !this.L.locked { 79 | this.ShowToolTip("Lock " this.L.toLock.Name, this.L.show) 80 | this._lock() 81 | ; 锁定时注册个函数, 用于自动关闭锁定, TaskSwitch 模式会用到这个 82 | if this.L.locked.AfterLocked { 83 | SetTimer(this.L.locked.AfterLocked, -1) 84 | } 85 | return 86 | } 87 | 88 | ; 已经锁定了自己 89 | if this.L.toLock == this.L.locked { 90 | this.L.toLock := false ; 别忘了清除状态 91 | if !this.L.toggle { 92 | return 93 | } 94 | this.ShowToolTip("Lock: Off", this.L.show) 95 | this.Unlock() 96 | return 97 | } 98 | 99 | ; 锁定了别的模式, 那么切换成锁定自己 100 | if this.L.toLock != this.L.locked { 101 | this.ShowToolTip("从 " this.L.locked.Name "`n切换到 " this.L.toLock.Name, this.L.show) 102 | this.Unlock() 103 | this._lock() 104 | ; 锁定时注册个函数, 用于自动关闭锁定, TaskSwitch 模式会用到这个 105 | if this.L.locked.AfterLocked { 106 | SetTimer(this.L.locked.AfterLocked, -1) 107 | } 108 | return 109 | } 110 | 111 | } 112 | 113 | static SetLockRequest(toLock, toggle, show) { 114 | this.L.toLock := toLock 115 | this.L.toggle := toggle 116 | this.L.show := show 117 | } 118 | 119 | static ClearLockRequest() { 120 | KeymapManager.L.toLock := false 121 | } 122 | 123 | static _lock() { 124 | if this.L.toLock { 125 | this.L.toLock.Enable(this.GlobalKeymap) 126 | this.Stack[1] := this.L.toLock 127 | this.L.locked := this.L.toLock 128 | this.L.toLock := false 129 | } 130 | } 131 | 132 | static Unlock() { 133 | ; 这里不好用 this, 因为 Unlock 函数会被取出来, 然后 this 指向会变 134 | if KeymapManager.L.locked { 135 | KeymapManager.L.locked.Disable() 136 | KeymapManager.Stack[1] := KeymapManager.GlobalKeymap 137 | KeymapManager.L.locked := false 138 | } 139 | } 140 | 141 | 142 | static ShowToolTip(msg, show := true) { 143 | if !show { 144 | return 145 | } 146 | Tip(msg) 147 | } 148 | 149 | class ActionList { 150 | actions := [] 151 | static conditionMap := Map( 152 | 0, _ => true, 153 | 1, winTitle => WinActive(winTitle), 154 | 2, winTitle => WinExist(winTitle), 155 | 3, winTitle => !WinActive(winTitle), 156 | 4, winTitle => !WinExist(winTitle), 157 | ) 158 | 159 | Run() { 160 | m := KeymapManager.ActionList.conditionMap 161 | for a in this.actions { 162 | if !m.Has(a.conditionType) { 163 | continue 164 | } 165 | if a.conditionType == 0 && !IsSet(fn) { 166 | fn := a.fn 167 | continue 168 | } 169 | if m.Get(a.conditionType)(a.winTitle) { 170 | fn := a.fn 171 | break 172 | } 173 | } 174 | if IsSet(fn) { 175 | fn() 176 | } 177 | } 178 | 179 | Add(conditionType, winTitle, fn) { 180 | this.actions.Push({ 181 | conditionType: conditionType, 182 | winTitle: winTitle, 183 | fn: fn, 184 | }) 185 | } 186 | } 187 | } 188 | 189 | 190 | class Keymap { 191 | __New(name := "", waitKey := "", hotkey := "", delay := 0) { 192 | this.Name := name 193 | this.WaitKey := waitKey 194 | this.Hotkey := hotkey 195 | this.SinglePressAction := KeymapManager.ActionList() 196 | this.M := Map() 197 | this.M.CaseSense := "Off" 198 | this.ToggleLock := this._lockOrUnlock.Bind(this) 199 | this.AfterLocked := false 200 | this.parent := false 201 | this.toRestore := Array() 202 | this.delay := delay 203 | } 204 | 205 | class _Hotkey { 206 | __New(name, handler, options, winTitle, conditionType) { 207 | this.name := ExtractWaitKey(name) ; 把带修饰符的热键视为同名热键 208 | this.rawName := name 209 | this.handler := handler 210 | this.options := options 211 | this.winTitle := winTitle 212 | this.conditionType := conditionType 213 | this.enabled := false 214 | } 215 | 216 | Enable() { 217 | if this.enabled { 218 | MsgBox "bug" 219 | } 220 | this.hotifContext(this.winTitle, this.conditionType, true) 221 | Hotkey(this.rawName, this.handler, "On" this.options) 222 | this.enabled := true 223 | this.hotifContext(this.winTitle, this.conditionType, false) 224 | } 225 | 226 | Disable() { 227 | if !this.enabled { 228 | MsgBox "bug" 229 | } 230 | this.hotifContext(this.winTitle, this.conditionType, true) 231 | Hotkey(this.rawName, "Off") 232 | this.enabled := false 233 | this.hotifContext(this.winTitle, this.conditionType, false) 234 | } 235 | 236 | hotifContext(winTitle, conditionType, begin) { 237 | if winTitle == "" || conditionType == 0 { 238 | HotIf() 239 | } 240 | switch conditionType { 241 | case 1: begin ? HotIfWinactive(winTitle) : HotIfWinactive() 242 | case 2: begin ? HotIfWinExist(winTitle) : HotIfWinExist() 243 | case 3: begin ? HotIfWinNotactive(winTitle) : HotIfWinNotactive() 244 | case 4: begin ? HotIfWinNotExist(winTitle) : HotIfWinNotExist() 245 | case 5: begin ? HotIf(winTitle) : HotIf() 246 | } 247 | } 248 | } 249 | 250 | Map(hotkeyName, handler, keymapToLock := false, winTitle := "", conditionType := 0, options := "") { 251 | wrapper := Keymap._wrapHandler(handler, keymapToLock) 252 | ; 用 = 表示忽略大小写进行字符串比较 253 | if hotkeyName = "singlePress" { 254 | this.SinglePressAction.Add(conditionType, winTitle, wrapper.Bind("singlePress")) 255 | return 256 | } 257 | ; If Action is a hotkey name, its original function is used; 258 | ; This is usually used to restore a hotkey's original function after having changed it 259 | if handler == "handled_in_hot_if" { 260 | wrapper := hotkeyName 261 | } 262 | 263 | hk := Keymap._Hotkey(hotkeyName, wrapper, options, winTitle, conditionType) 264 | if !this.M.Has(hk.name) { 265 | this.M[hk.name] := Array() 266 | } 267 | this.M[hk.name].Push(hk) 268 | } 269 | 270 | 271 | static _wrapHandler(handler, keymapToLock) { 272 | wrapper(thisHotkey) { 273 | handler(thisHotkey) 274 | ; 执行完热键动作后, 可能要锁定某个 keymap 275 | if !keymapToLock { 276 | return 277 | } 278 | KeymapManager.SetLockRequest(keymapToLock, false, false) 279 | 280 | ; 这种情况是, 锁住后直接执行热键, 没有按下任何引导键 ( 比如先锁住 Caps 然后直接按 E ) 281 | if KeymapManager.Stack.Length == 1 { 282 | KeymapManager._postHandler() 283 | } 284 | } 285 | return wrapper 286 | } 287 | 288 | _lockOrUnlock(thiHotkey) { 289 | KeymapManager.SetLockRequest(this, true, true) 290 | ; 这种情况是, 锁住后直接执行热键, 没有按下任何引导键 ( 比如先锁住 Caps 然后直接按 E ) 291 | if KeymapManager.Stack.Length == 1 { 292 | KeymapManager._postHandler() 293 | } 294 | } 295 | 296 | Wait(startTick) { 297 | ; 先处理一般情况, 不用鼠标按钮作为触发键 298 | if !InStr(this.Hotkey, "button") { 299 | KeyWait(this.WaitKey) 300 | if (A_PriorKey = this.WaitKey && (A_TickCount - startTick < 450)) { 301 | this.SinglePressAction.Run() 302 | } 303 | return 304 | } 305 | ; 使用鼠标按钮作为触发键, 尝试兼容其他鼠标手势软件 306 | mouseMoved := false 307 | thisHotkey := A_ThisHotkey 308 | CoordMode("Mouse", "Screen") 309 | 310 | MouseGetPos(&x1, &y1) 311 | while !KeyWait(this.WaitKey, "T0.01") { 312 | MouseGetPos(&x2, &y2) 313 | if Abs(x2 - x1) > 10 || Abs(y2 - y1) > 10 { 314 | mouseMoved := true 315 | break 316 | } 317 | if thisHotkey != A_ThisHotkey { 318 | KeyWait(this.WaitKey) 319 | break 320 | } 321 | } 322 | 323 | if (thisHotkey = A_ThisHotkey && (A_TickCount - startTick < 450)) { 324 | if !mouseMoved { 325 | this.SinglePressAction.Run() 326 | } else { 327 | Send("{blind}{" this.WaitKey " Down}") 328 | KeyWait(this.WaitKey) 329 | Send("{blind}{" this.WaitKey " Up}") 330 | } 331 | } 332 | } 333 | ; 启用 keymap 334 | Enable(parent := false) { 335 | if this.parent && parent { 336 | MsgBox "bug" 337 | } 338 | this.parent := parent 339 | 340 | ; 方案 1 341 | ; if parent { 342 | ; if parent == KeymapManager.Stack[1] { 343 | ; ; 只禁用同名的 344 | ; for name in this.M { 345 | ; km := parent 346 | ; while km { 347 | ; ; 遍历祖先, 如果首个 km 存在同名热键, 那么禁用掉 348 | ; if km.DisableHotkey(name) { 349 | ; item := { keymap: km, hotkey: name } 350 | ; this.toRestore.Push(item) 351 | ; break 352 | ; } 353 | ; km := km.parent 354 | ; } 355 | ; } 356 | ; } else { 357 | ; ; 直接禁用 parent 中所有热键 358 | ; for name in parent.M { 359 | ; if name == this.hotkey { 360 | ; continue 361 | ; } 362 | ; parent.DisableHotkey(name) 363 | ; item := { keymap: parent, hotkey: name } 364 | ; this.toRestore.Push(item) 365 | ; } 366 | ; } 367 | ; } 368 | 369 | for name in this.M { 370 | ; 方案 2 只禁用同名热键 371 | km := parent 372 | while km { 373 | ; 遍历祖先, 如果首个 km 存在同名热键, 那么禁用掉 374 | if km.DisableHotkey(name) { 375 | item := { keymap: km, hotkey: name } 376 | this.toRestore.Push(item) 377 | break 378 | } 379 | km := km.parent 380 | } 381 | this.EnableHotkey(name) 382 | } 383 | } 384 | 385 | 386 | Disable() { 387 | for name in this.M { 388 | this.DisableHotkey(name) 389 | } 390 | while this.toRestore.Length > 0 { 391 | item := this.toRestore.Pop() 392 | item.keymap.EnableHotkey(item.hotkey) 393 | } 394 | this.parent := false 395 | } 396 | 397 | ; 启用 keymap 中所有名为 name 的热键 398 | EnableHotkey(name) { 399 | if !this.M.Has(name) { 400 | return 401 | } 402 | for hk in this.M[name] { 403 | hk.Enable() 404 | } 405 | } 406 | 407 | DisableHotkey(name) { 408 | hks := this.M.Get(name, false) 409 | if !hks { 410 | return 411 | } 412 | for hk in hks { 413 | hk.Disable() 414 | } 415 | return hks.Length > 0 416 | } 417 | 418 | RemapKey(a, b, winTitle := "", conditionType := 0) { 419 | ; Remap 容易让按键卡在按下状态, 改成 Send 好一点 420 | hk := "*" a 421 | keys := "{blind}{" b "}" 422 | this.SendKeys(hk, keys, winTitle, conditionType) 423 | 424 | ; downHandler(thisHotkey) { 425 | ; SetKeyDelay -1 426 | ; Send "{Blind}{" b " DownR}" 427 | ; } 428 | ; upHandler(thisHotkey) { 429 | ; SetKeyDelay -1 430 | ; Send "{Blind}{" b " Up}" 431 | ; } 432 | ; this.Map("*" a, downHandler, , winTitle, conditionType) 433 | ; this.Map("*" a " up", upHandler, , winTitle, conditionType) 434 | } 435 | 436 | SendKeys(hk, keys, winTitle := "", conditionType := 0) { 437 | handler(thisHotkey) { 438 | Send(keys) 439 | } 440 | this.Map(hk, handler, , winTitle, conditionType) 441 | } 442 | 443 | RemapInHotIf(a, b, winTitle := "", conditionType := 0) { 444 | h := "handled_in_hot_if" 445 | ; 跳过这两个特殊玩意, 因为无法引用他们的 handler 446 | if b = "AltTab" || b = "ShiftAltTab" { 447 | return 448 | } 449 | ; 如果 b 的名字不是键名, 那么不构成重映射 450 | if GetKeyName(ExtractWaitKey(b)) == "" { 451 | this.Map(a, h, , winTitle, conditionType) 452 | } else { 453 | this.Map("*" a, h, , winTitle, conditionType) 454 | this.Map("*" a " up", h, , winTitle, conditionType) 455 | } 456 | } 457 | } 458 | 459 | class MouseKeymap extends Keymap { 460 | 461 | __New(name, keepMouseMode, mouseTip, single, repeat, delay1, delay2, scrollOnceLineCount, scrollDelay1, scrollDelay2, slowKeymap := false) { 462 | super.__New(name) 463 | this.keepMouseMode := keepMouseMode 464 | this.single := single 465 | this.repeat := repeat 466 | this.delay1 := delay1 467 | this.delay2 := delay2 468 | this.scrollOnceLineCount := scrollOnceLineCount 469 | this.scrollDelay1 := scrollDelay1 470 | this.scrollDelay2 := scrollDelay2 471 | this.slowKeymap := slowKeymap 472 | this.mouseTip := mouseTip 473 | 474 | this.MoveMouseUp := this._moveMouse.Bind(this, 0, -1) 475 | this.MoveMouseDown := this._moveMouse.Bind(this, 0, 1) 476 | this.MoveMouseLeft := this._moveMouse.Bind(this, -1, 0) 477 | this.MoveMouseRight := this._moveMouse.Bind(this, 1, 0) 478 | this.ScrollWheelUp := this._scrollWheel.Bind(this, 1) 479 | this.ScrollWheelDown := this._scrollWheel.Bind(this, 2) 480 | this.ScrollWheelLeft := this._scrollWheel.Bind(this, 3) 481 | this.ScrollWheelRight := this._scrollWheel.Bind(this, 4) 482 | 483 | ; 鼠标模式中按任意键退出, 经常会忘记按 N/Space 键退出 484 | keys := "abcdefghijklmnopqrstuvwxyz" 485 | h := this.ExitAndSendThisKey() 486 | for _, k in StrSplit(keys) { 487 | this.Map("*" k, h) 488 | } 489 | } 490 | 491 | _moveMouse(directionX, directionY, thisHotkey) { 492 | key := ExtractWaitKey(thisHotkey) 493 | MouseMove(directionX * this.single, directionY * this.single, 0, "R") 494 | (this.mouseTip && this.mouseTip.Show()) 495 | release := KeyWait(key, this.delay1) 496 | if release { 497 | return 498 | } 499 | while !release { 500 | MouseMove(directionX * this.repeat, directionY * this.repeat, 0, "R") 501 | (this.mouseTip && this.mouseTip.Show()) 502 | release := KeyWait(key, this.delay2) 503 | } 504 | } 505 | 506 | _scrollWheel(direction, thisHotkey) { 507 | if this.slowKeymap { 508 | this.clearOrUnlock(false) 509 | } 510 | key := ExtractWaitKey(thisHotkey) 511 | switch (direction) { 512 | case 1: MouseClick("WheelUp", , , this.scrollOnceLineCount) 513 | case 2: MouseClick("WheelDown", , , this.scrollOnceLineCount) 514 | case 3: MouseClick("WheelLeft", , , this.scrollOnceLineCount) 515 | case 4: MouseClick("WheelRight", , , this.scrollOnceLineCount) 516 | } 517 | release := KeyWait(key, this.scrollDelay1) 518 | if release { 519 | return 520 | } 521 | while !release { 522 | switch (direction) { 523 | case 1: MouseClick("WheelUp", , , this.scrollOnceLineCount) 524 | case 2: MouseClick("WheelDown", , , this.scrollOnceLineCount) 525 | case 3: MouseClick("WheelLeft", , , this.scrollOnceLineCount) 526 | case 4: MouseClick("WheelRight", , , this.scrollOnceLineCount) 527 | } 528 | release := KeyWait(key, this.scrollDelay2) 529 | } 530 | } 531 | 532 | clearOrUnlock(keepMouseMode) { 533 | ; 没有 slowKeymap 说明 this 是 slow 模式, 要解锁 534 | if !this.slowKeymap { 535 | ; 用户想点击后不退出鼠标模式 536 | if keepMouseMode { 537 | return 538 | } 539 | ; 进行解锁的前提是没有按下其他模式, 否则会两次禁用同一热键 540 | if KeymapManager.Stack.Length > 1 { 541 | return 542 | } 543 | KeymapManager.Unlock() 544 | (this.mouseTip && this.mouseTip.Hide()) 545 | return 546 | } 547 | ; slowKeymap 不为空说明 this 是 fast 模式, 如果发现已经锁定了 slow 模式, 要解锁 548 | if KeymapManager.L.locked == this.slowKeymap { 549 | if keepMouseMode { 550 | return 551 | } 552 | KeymapManager.SetLockRequest(this.slowKeymap, true, false) ; 通过 toggle 锁定状态实现解锁 553 | (this.mouseTip && this.mouseTip.Hide()) 554 | } else { 555 | ; 清空锁定请求 556 | KeymapManager.ClearLockRequest() 557 | (this.mouseTip && this.mouseTip.Hide()) 558 | } 559 | } 560 | 561 | LButton() { 562 | handler(thisHotkey) { 563 | Send("{blind}{LButton}") 564 | this.clearOrUnlock(this.keepMouseMode) 565 | } 566 | return handler 567 | } 568 | 569 | RButton() { 570 | handler(thisHotkey) { 571 | SendMouseButton("RButton") 572 | this.clearOrUnlock(this.keepMouseMode) 573 | } 574 | return handler 575 | } 576 | 577 | MButton() { 578 | handler(thisHotkey) { 579 | SendMouseButton("MButton") 580 | this.clearOrUnlock(this.keepMouseMode) 581 | } 582 | return handler 583 | } 584 | 585 | LButtonDown() { 586 | handler(thisHotkey) { 587 | Send("{blind}{LButton DownR}") 588 | } 589 | return handler 590 | } 591 | 592 | LButtonUp() { 593 | handler(thisHotkey) { 594 | Send("{blind}{LButton Up}") 595 | this.clearOrUnlock(false) 596 | } 597 | return handler 598 | } 599 | 600 | ExitMouseKeyMap() { 601 | handler(thisHotkey) { 602 | this.clearOrUnlock(false) 603 | } 604 | return handler 605 | } 606 | 607 | ExitAndSendThisKey() { 608 | handler(thisHotkey) { 609 | this.clearOrUnlock(false) 610 | Send("{blind}{" ExtractWaitKey(thisHotkey) "}") 611 | } 612 | return handler 613 | } 614 | } 615 | 616 | 617 | class TaskSwitchKeymap extends Keymap { 618 | 619 | __New(up, down, left, right, delete, enter) { 620 | super.__New("Task Switch") 621 | this.SendKeys("x", "{delete}") ; 为了不影响之前习惯了 x 键关闭的用户 622 | this.SendKeys(up, "{up}") 623 | this.SendKeys(down, "{down}") 624 | this.SendKeys(left, "{left}") 625 | this.SendKeys(right, "{right}") 626 | this.SendKeys(delete, "{delete}") 627 | this.SendKeys(enter, "{enter}") 628 | this.AfterLocked := this.DeactivateTaskSwitch.Bind(this) 629 | GroupAdd("TASK_SWITCH_GROUP", "ahk_class MultitaskingViewFrame") 630 | GroupAdd("TASK_SWITCH_GROUP", "ahk_class XamlExplorerHostIslandWindow") 631 | } 632 | 633 | DeactivateTaskSwitch() { 634 | ; 先等 AltTab 窗口出现, 再等它消失, 然后解锁 635 | notTimedOut := WinWaitActive("ahk_group TASK_SWITCH_GROUP", , 0.5) 636 | if (notTimedOut) { 637 | WinWaitNotActive("ahk_group TASK_SWITCH_GROUP") 638 | } 639 | ; 在 AltTab 窗口出现时, 把锁定的模式切换到 3 模式, 这种情况无需解锁 640 | if KeymapManager.L.locked == this { 641 | KeymapManager.Unlock() 642 | } 643 | } 644 | } 645 | 646 | NoOperation(thisHotkey) { 647 | } 648 | 649 | ExtractWaitKey(hotkey) { 650 | waitKey := Trim(hotkey, " #!^+<>*~$") 651 | if InStr(waitKey, "&") { 652 | sp := StrSplit(waitKey, "&") 653 | waitKey := Trim(sp[2]) 654 | } 655 | return waitKey 656 | } 657 | 658 | matchWinTitleCondition(winTitle, conditionType) { 659 | switch conditionType { 660 | case 1: 661 | return WinActive(winTitle) 662 | case 2: 663 | return WinExist(winTitle) 664 | case 3: 665 | return !WinActive(winTitle) 666 | case 4: 667 | return !WinExist(winTitle) 668 | case 5: 669 | return winTitle 670 | } 671 | return false 672 | } 673 | 674 | SendMouseButton(btn) { 675 | ; MyKeymap 输入的 RButton 被鼠标手势拦截, 鼠标手势认为用户想单击右键, 所以也发送 RButton 676 | ; 然而这个 RButton 又会触发 MyKeymap 的右键功能, 造成死循环, 所以在发送 RButton 前把右键暂停一下 677 | ; try Hotkey("*" btn, "Off") 678 | ; try Hotkey(btn, "Off") 679 | Suspend 680 | Send("{blind}{" btn "}") 681 | Sleep 50 682 | Suspend 683 | ; try Hotkey("*" btn, "On") 684 | ; try Hotkey(btn, "On") 685 | } -------------------------------------------------------------------------------- /lib/MoveWindow.ahk: -------------------------------------------------------------------------------- 1 | MoveWindow() { 2 | MouseGetPos &EWD_MouseStartX, &EWD_MouseStartY, &EWD_MouseWin 3 | 4 | if !WinGetMinMax(EWD_MouseWin) ; Only if the window isn't maximized 5 | SetTimer EWD_WatchMouse, 30 ; Track the mouse as the user drags it. 6 | 7 | EWD_WatchMouse() { 8 | if !GetKeyState("LButton", "P") ; Button has been released, so drag is complete. 9 | { 10 | SetTimer , 0 11 | return 12 | } 13 | ; Otherwise, reposition the window to match the change in mouse coordinates 14 | ; caused by the user having dragged the mouse: 15 | CoordMode "Mouse" 16 | MouseGetPos(&EWD_MouseX, &EWD_MouseY) 17 | WinGetPos(&EWD_WinX, &EWD_WinY,,, EWD_MouseWin) 18 | 19 | SetWinDelay 39 ; Makes the below move faster/smoother. 20 | WinMove EWD_WinX + EWD_MouseX - EWD_MouseStartX, EWD_WinY + EWD_MouseY - EWD_MouseStartY,,, EWD_MouseWin 21 | EWD_MouseStartX := EWD_MouseX ; Update for the next timer-call to this subroutine. 22 | EWD_MouseStartY := EWD_MouseY 23 | } 24 | } 25 | 26 | ; 目前在用 27 | MoveWindow2() { 28 | ; Get the initial mouse position and window id, and 29 | ; abort if the window is maximized. 30 | MouseGetPos(&KDE_X1, &KDE_Y1, &KDE_id) 31 | if WinGetMinMax(KDE_id) 32 | return 33 | ; Get the initial window position. 34 | WinGetPos(&KDE_WinX1, &KDE_WinY1,,, KDE_id) 35 | Loop { 36 | if !GetKeyState("LButton", "P") ; Break if button has been released. 37 | break 38 | MouseGetPos &KDE_X2, &KDE_Y2 ; Get the current mouse position. 39 | KDE_X2 -= KDE_X1 ; Obtain an offset from the initial mouse position. 40 | KDE_Y2 -= KDE_Y1 41 | KDE_WinX2 := (KDE_WinX1 + KDE_X2) ; Apply this offset to the window position. 42 | KDE_WinY2 := (KDE_WinY1 + KDE_Y2) 43 | WinMove KDE_WinX2, KDE_WinY2,,, KDE_id ; Move the window to the new position. 44 | } 45 | } 46 | 47 | resizeWindow() { 48 | ; Get the initial mouse position and window id, and 49 | ; abort if the window is maximized. 50 | MouseGetPos &KDE_X1, &KDE_Y1, &KDE_id 51 | if WinGetMinMax(KDE_id) 52 | return 53 | ; Get the initial window position and size. 54 | WinGetPos(&KDE_WinX1, &KDE_WinY1, &KDE_WinW, &KDE_WinH, KDE_id) 55 | 56 | ; Define the window region the mouse is currently in. 57 | ; The four regions are Up and Left, Up and Right, Down and Left, Down and Right. 58 | if (KDE_X1 < KDE_WinX1 + KDE_WinW / 2) 59 | KDE_WinLeft := 1 60 | else 61 | KDE_WinLeft := -1 62 | if (KDE_Y1 < KDE_WinY1 + KDE_WinH / 2) 63 | KDE_WinUp := 1 64 | else 65 | KDE_WinUp := -1 66 | 67 | Loop { 68 | if !GetKeyState("RButton", "P") ; Break if button has been released. 69 | break 70 | ; 不断获得当前的鼠标位置 和 屏幕位置 71 | MouseGetPos &KDE_X2, &KDE_Y2 ; Get the current mouse position. 72 | ; Get the current window position and size. 73 | WinGetPos &KDE_WinX1, &KDE_WinY1, &KDE_WinW, &KDE_WinH, KDE_id 74 | 75 | KDE_X2 -= KDE_X1 ; Obtain an offset from the initial mouse position. 76 | KDE_Y2 -= KDE_Y1 77 | 78 | ; 设定最小位移 79 | if Abs(KDE_X2) < 20 and Abs(KDE_Y2) < 20 80 | continue 81 | ; 如果横向位移 小于 纵向位移,则忽略横向位移 82 | if (Abs(KDE_X2) < Abs(KDE_Y2)) { 83 | KDE_X2 := 0 84 | } else { 85 | KDE_Y2 := 0 86 | } 87 | 88 | ; Then, act according to the defined region. 89 | WinMove KDE_WinX1 + KDE_X2 * (KDE_WinLeft + 1) / 2 ; X of resized window 90 | , KDE_WinY1 + KDE_Y2 * (KDE_WinUp + 1) / 2 ; Y of resized window 91 | , KDE_WinW - KDE_X2 * KDE_WinLeft ; W of resized window 92 | , KDE_WinH - KDE_Y2 * KDE_WinUp ; H of resized window 93 | , KDE_id 94 | KDE_X1 := (KDE_X2 + KDE_X1) ; Reset the initial position for the next iteration. 95 | KDE_Y1 := (KDE_Y2 + KDE_Y1) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/Utils.ahk: -------------------------------------------------------------------------------- 1 | /** 2 | * 自动关闭的提示窗口 3 | * @param message 要提示的文本 4 | * @param {number} time 超时后关闭 5 | */ 6 | Tip(message, time := -1299, X := unset, Y := unset) { 7 | ; 不喜欢不吉利的🔢🌶 8 | if IsSet(X) { 9 | ToolTip(message, X, Y) 10 | } else { 11 | ToolTip(message) 12 | } 13 | SetTimer(() => ToolTip(), time) 14 | } 15 | 16 | OpenURLOrSearch(text) { 17 | ; 如果是网址 18 | if RegExMatch(text, 'i)^\s*((?:https?://)?(?:[\w-]+\.)+[\w-]+(?:/[\w-./?%&=]*)?\s*)$', ®ExMatchInfo) { 19 | text := regExMatchInfo.1 20 | if NOT InStr(text, 'http') 21 | text := ('http://' . text) 22 | Run text 23 | } else if (FileExist(text)) { 24 | Sleep 200 25 | Run text 26 | } else { 27 | Run('https://www.baidu.com/s?wd=' . text) 28 | } 29 | } 30 | 31 | /** 32 | * 没有获取到文字直接返回,否则若选中的是网址则打开,否则进行百度搜索。这个功能可以暴露出去作为服务 export 33 | * 34 | * @param text 35 | */ 36 | OpenSelectedText() { 37 | text := GetSelectedText() 38 | if text 39 | OpenURLOrSearch text 40 | } 41 | -------------------------------------------------------------------------------- /lib/WinHole.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | 基于 v1 进行改造为 v2 的版本 by acc8226.github.io 3 | 4 | 原始代码 [Script] WinHole - AutoHotkey Community 5 | https://www.autohotkey.com/boards/viewtopic.php?f=6&t=30622 6 | 7 | -------------------------------- 8 | */ 9 | 10 | global radius := IniRead('setting.ini', "WinHole", "radius", 266) ; Starting radius of the hole. 11 | global increment := 25 ; Amount to decrease/increase radius of circle when turning scroll wheel 每次的增量 12 | global inverted := false ; If false, the region is see-throughable. 默认是 false,表示穿透该区域,否则表示不穿透,保留该区域 13 | global rate := 100 ; The period (ms) of the timer. 40 ms is 25 "fps" 如果是 100 则是 10 fps 14 | 15 | global toggle := 0 ; 切换窗口的显示状态 16 | global isPause := false ; 是否暂停跟随鼠标 17 | 18 | CoordMode 'Mouse' 19 | 20 | ; Make the region 21 | global region := makeCircle(radius) 22 | F18::{ 23 | global toggle 24 | ; 如果当前未开启 且 起始点在桌面则不开启 25 | if !toggle and IsDesktop() 26 | return 27 | toggle := !toggle 28 | global isPause := false 29 | timer(toggle, region, inverted, rate) ; Toggle on/off 30 | } 31 | 32 | #HotIf toggle 33 | 34 | F19::{ ; When on, toggle inverted setting 35 | global inverted := !inverted, isPause := false 36 | timer(1, region, inverted, rate) 37 | } 38 | F20::{ ; When on, toggle pause. 在 1 和 -1 之间来回切换 39 | global isPause := !isPause 40 | timer(isPause ? -1 : 1) 41 | } 42 | 43 | ;;; 滚轮我还是通过 ini 取值了 44 | ; WheelUp:: ; Increase the radius of the circle 45 | ; WheelDown::{ ; Decrease 46 | ; global radius, region 47 | ; InStr(A_ThisHotkey, "Up") ? radius += increment : radius -= increment 48 | ; radius<100 ? radius := 100 : "" ; Ensure greater than 0 radius 49 | ; region := makeCircle(radius) 50 | ; timer(1, region, inverted) 51 | ; } 52 | Esc::{ 53 | global toggle := 0, isPause := false 54 | timer(toggle, region, inverted, rate) 55 | } 56 | #HotIf 57 | 58 | /** 59 | * timer 函数根据 state 参数控制定时器的开启、关闭和暂停,以及窗口区域的更新 60 | * 61 | * @param state Call with state=0 to restore window and stop timer, state=-1 stop timer but do not restore 0 表示停止和退出 -1 表示停止但是不还原 62 | * @param {String} region see WinSetRegion() 63 | * @param {Integer} inverted see WinSetRegion() 64 | * @param {Integer} rate the period of the timer. 65 | * @returns {Integer} 66 | */ 67 | timer(state, region := "", inverted := false, rate := 50) { 68 | static timerFn, paused, hWinID, aot 69 | ; Restore window and turn off timer 70 | if (state = 0) { 71 | if timerFn 72 | SetTimer timerFn, 0 73 | if !hWinID 74 | return 75 | WinSetRegion , hWinID 76 | if !aot ; Restore not being aot if appropriate. 77 | WinSetAlwaysOnTop false, hWinID 78 | timerFn := "" 79 | paused := false 80 | hWinID := aot := "" 81 | return 82 | } else if (IsSet(timerFn) && timerFn) { ; Pause/unpause or... 83 | if (state = -1) { 84 | SetTimer(timerFn, 0) 85 | return paused := true 86 | } 87 | ; 走到此处则 state 必为 1 88 | else if (IsSet(paused) && paused) { 89 | ; 以原来的周期重新启用之前禁用的计时器. 如果计时器不存在, 则进行创建(使用默认的周期 250). 计时器也会重置 90 | SetTimer timerFn 91 | return paused := false 92 | } else { ; ... stop timer before starting a new one. 93 | SetTimer timerFn, 0 94 | } 95 | } 96 | ; 走到此处则 state 必为 1 97 | if !(IsSet(hWinID) && hWinID) { ; Get the window under the mouse. 确定窗口是否具有 WS_EX_TOPMOST 样式(置顶). 98 | MouseGetPos ,, &hWinID 99 | aot := WinGetExStyle(hWinID) & 0x8 ; Get always-on-top state, to preserve it. 100 | if !aot 101 | WinSetAlwaysOnTop true, hWinID ; 必须指定当前窗口,防止后面的窗口获取焦点 102 | } 103 | timerFn := timerFunction.Bind(hWinID, region, inverted) ; Initialise the timer. 104 | timerFn.Call(1) ; For better responsiveness, 1 is for reset static 105 | SetTimer(timerFn, rate) 106 | } 107 | 108 | /** 109 | * 负责获取鼠标位置,并根据这个位置更新窗口区域,使其跟随鼠标移动 110 | * 111 | * @param hWinID handle to the window to apply region to. 112 | * @param region should be on the form, region:=[{x:x0,y:y0},{x:x1,y:y1},...,{x:xn,y:yn},{x:x0,y:y0}] 113 | * @param inverted inverted=true, make the region the only part visible, vs the only part see-throughable for inverted=false 布尔值,决定区域是可见部分还是透明部分 114 | * @param {Integer} resetStatic 115 | */ 116 | timerFunction(hWinID, region, inverted, resetStatic := 0) { 117 | ; Get mouse position and convert coords to win coordinates, for displacing the circle 118 | static px, py 119 | ; 当前窗口的 x 和 y 坐标 120 | WinGetPos &wx, &wy,,, hWinID 121 | ; 当前鼠标的坐标 122 | MouseGetPos &x, &y 123 | ; 位移量 124 | x -= wx, y -= wy 125 | if (IsSet(px) && x = px && y = py && !resetStatic) 126 | return 127 | else 128 | px:=x , py:=y 129 | setRegion(hWinID, region, x, y, inverted) 130 | return 131 | } 132 | 133 | /** 134 | * 设置窗口的区域 135 | * 136 | * @param hWinID handle to the window to apply region to. 137 | * @param region should be on the form, region:=[{x:x0,y:y0},{x:x1,y:y1},...,{x:xn,y:yn},{x:x0,y:y0}] 138 | * @param {Integer} dx displacing the the region by fixed amount in x and y direction, respectively. 139 | * @param {Integer} dy displacing the the region by fixed amount in x and y direction, respectively. 140 | * @param {Integer} inverted inverted=true, make the region the only part visible, vs the only part see-throughable for inverted=false 布尔值,决定区域是可见部分还是透明部分 141 | */ 142 | setRegion(hWinID, region, dx:=0, dy:=0, inverted := false) { 143 | if (!inverted) { 144 | WinGetPos ,, &w, &h, hWinID 145 | regionDefinition .= "0-0 0-" . h . " " . w . "-" . h . " " . w . "-0 0-0 " ; 0-0 0-h w-h w-0 0-0 146 | } 147 | for k, pt in region 148 | regionDefinition .= dx + pt.x "-" dy + pt.y " " ; regionDefinition 字符串包含了定义区域的所有点 149 | WinSetRegion regionDefinition, hWinID 150 | } 151 | 152 | makeCircle(r := 100, n := -1) { 153 | ; r is the radius. 154 | ; n is the number of points, let n=-1 to set automatically (highest quality). 155 | static pi := atan(1) * 4 156 | pts := [] 157 | n:= n=-1 ? Ceil(2 * r * pi) : n 158 | n:= n>=1994 ? 1994 : n ; There is a maximum of 2000 points for WinSet,Region,... 159 | loop n + 1 160 | t := 2 * pi * (A_Index - 1) / n, pts.push({x:round(r * cos(t)), y:round(r * sin(t))}) 161 | return pts 162 | } 163 | 164 | makeTriangle(side := 100) { 165 | ; 使用等边三角形高的公式计算高 166 | heigth := Round(side * sqrt(3) / 2) 167 | ; 初始化顶点坐标:顶点 右下角 左下角 168 | region := [{x:0,y:0}, {x:side//2, y:heigth}, {x:-side//2, y:heigth}, {x:0,y:0}] 169 | oY := -heigth // 2 ; Make center, note: oX:=0 170 | ; 调整顶点坐标以使三角形居中:需要整体下移 171 | for k, pt in region { 172 | pt.y += oY ; Make correction for center, note: pt.x+=oX 173 | } 174 | return region 175 | ; 0 . -65 176 | ; 75 65 177 | ; -75 65 178 | ; 0 . -65 179 | } 180 | -------------------------------------------------------------------------------- /lib/WindowSpy.ahk: -------------------------------------------------------------------------------- 1 | #Requires AutoHotkey v2.0 2 | 3 | #NoTrayIcon 4 | #SingleInstance Ignore 5 | SetWorkingDir A_ScriptDir 6 | CoordMode "Pixel", "Screen" 7 | 8 | Global oGui 9 | 10 | WinSpyGui() 11 | 12 | WinSpyGui() { 13 | DllCall("shell32\SetCurrentProcessExplicitAppUserModelID", "wstr", "AutoHotkey.WindowSpy") 14 | 15 | Global oGui := Gui("AlwaysOnTop Resize MinSize +DPIScale","Window Spy for AHKv2") 16 | oGui.OnEvent("Close",WinSpyClose) 17 | oGui.OnEvent("Size",WinSpySize) 18 | 19 | oGui.SetFont('s9', "Segoe UI") 20 | 21 | oGui.Add("Text",,"Window Title, Class and Process:") 22 | oGui.Add("Checkbox","yp xp+200 w120 Right vCtrl_FollowMouse","Follow Mouse").Value := 1 23 | 24 | oGui.Add("Edit","xm w320 r5 ReadOnly -Wrap vCtrl_Title") 25 | 26 | oGui.Add("Text",,"Mouse Position:") 27 | 28 | oGui.Add("Edit","w320 r4 ReadOnly vCtrl_MousePos") 29 | 30 | oGui.Add("Text","w320 vCtrl_CtrlLabel",(txtFocusCtrl := "Focused Control") . ":") 31 | oGui.Add("Edit","w320 r4 ReadOnly vCtrl_Ctrl") 32 | 33 | oGui.Add("Text",,"Active Window Position:") 34 | oGui.Add("Edit","w320 r2 ReadOnly vCtrl_Pos") 35 | 36 | oGui.Add("Text",,"Status Bar Text:") 37 | oGui.Add("Edit","w320 r2 ReadOnly vCtrl_SBText") 38 | oGui.Add("Checkbox","vCtrl_IsSlow","Slow TitleMatchMode") 39 | 40 | oGui.Add("Text",,"Visible Text:") 41 | oGui.Add("Edit","w320 r2 ReadOnly vCtrl_VisText") 42 | 43 | oGui.Add("Text",,"All Text:") 44 | oGui.Add("Edit","w320 r2 ReadOnly vCtrl_AllText") 45 | oGui.Add("Text","w320 r1 vCtrl_Freeze",(txtNotFrozen := "(Hold Ctrl or Shift to suspend updates)")) 46 | 47 | oGui.Show("NoActivate") 48 | WinGetClientPos(&x_temp,,,,"ahk_id " oGui.hwnd) 49 | 50 | ; oGui.horzMargin := x_temp*96//A_ScreenDPI - 320 ; now using oGui.MarginX 51 | 52 | oGui.txtNotFrozen := txtNotFrozen ; create properties for futur use 53 | oGui.txtFrozen := "(Updates suspended)" 54 | oGui.txtMouseCtrl := "Control Under Mouse Position" 55 | oGui.txtFocusCtrl := txtFocusCtrl 56 | 57 | SetTimer Update, 250 58 | } 59 | 60 | WinSpySize(GuiObj, MinMax, Width, Height) { 61 | Global oGui 62 | 63 | If !oGui.HasProp("txtNotFrozen") ; WinSpyGui() not done yet, return until it is 64 | return 65 | 66 | SetTimer Update, (MinMax=0) ? 250 : 0 ; suspend updates on minimize 67 | 68 | ctrlW := Width - (oGui.MarginX * 2) ; ctrlW := Width - horzMargin 69 | list := "Title,MousePos,Ctrl,Pos,SBText,VisText,AllText,Freeze" 70 | Loop Parse list, "," 71 | oGui["Ctrl_" A_LoopField].Move(,,ctrlW) 72 | } 73 | 74 | WinSpyClose(GuiObj) { 75 | ExitApp 76 | } 77 | 78 | Update() { ; timer, no params 79 | Try TryUpdate() ; Try 80 | } 81 | 82 | TryUpdate() { 83 | Global oGui 84 | 85 | If !oGui.HasProp("txtNotFrozen") ; WinSpyGui() not done yet, return until it is 86 | return 87 | 88 | Ctrl_FollowMouse := oGui["Ctrl_FollowMouse"].Value 89 | CoordMode "Mouse", "Screen" 90 | MouseGetPos &msX, &msY, &msWin, &msCtrl, 2 ; get ClassNN and hWindow 91 | actWin := WinExist("A") 92 | 93 | if (Ctrl_FollowMouse) { 94 | curWin := msWin, curCtrl := msCtrl 95 | WinExist("ahk_id " curWin) ; updating LastWindowFound? 96 | } else { 97 | curWin := actWin 98 | curCtrl := ControlGetFocus() ; get focused control hwnd from active win 99 | } 100 | curCtrlClassNN := "" 101 | Try curCtrlClassNN := ControlGetClassNN(curCtrl) 102 | 103 | t1 := WinGetTitle(), t2 := WinGetClass() 104 | if (curWin = oGui.hwnd || t2 = "MultitaskingViewFrame") { ; Our Gui || Alt-tab 105 | UpdateText("Ctrl_Freeze", oGui.txtFrozen) 106 | return 107 | } 108 | 109 | UpdateText("Ctrl_Freeze", oGui.txtNotFrozen) 110 | t3 := WinGetProcessName(), t4 := WinGetPID() 111 | 112 | WinDataText := t1 "`n" ; ZZZ 113 | . "ahk_class " t2 "`n" 114 | . "ahk_exe " t3 "`n" 115 | . "ahk_pid " t4 "`n" 116 | . "ahk_id " curWin 117 | 118 | UpdateText("Ctrl_Title", WinDataText) 119 | CoordMode "Mouse", "Window" 120 | MouseGetPos &mrX, &mrY 121 | CoordMode "Mouse", "Client" 122 | MouseGetPos &mcX, &mcY 123 | mClr := PixelGetColor(msX,msY,"RGB") 124 | mClr := SubStr(mClr, 3) 125 | 126 | mpText := "Screen:`t" msX ", " msY "`n" 127 | . "Window:`t" mrX ", " mrY "`n" 128 | . "Client:`t" mcX ", " mcY " (default)`n" 129 | . "Color:`t" mClr " (Red=" SubStr(mClr, 1, 2) " Green=" SubStr(mClr, 3, 2) " Blue=" SubStr(mClr, 5) ")" 130 | 131 | UpdateText("Ctrl_MousePos", mpText) 132 | 133 | UpdateText("Ctrl_CtrlLabel", (Ctrl_FollowMouse ? oGui.txtMouseCtrl : oGui.txtFocusCtrl) ":") 134 | 135 | if (curCtrl) { 136 | ctrlTxt := ControlGetText(curCtrl) 137 | WinGetClientPos(&sX, &sY, &sW, &sH, curCtrl) 138 | ControlGetPos &cX, &cY, &cW, &cH, curCtrl 139 | 140 | cText := "ClassNN:`t" curCtrlClassNN "`n" 141 | . "Text:`t" textMangle(ctrlTxt) "`n" 142 | . "Screen:`tx: " sX "`ty: " sY "`tw: " sW "`th: " sH "`n" 143 | . "Client:`tx: " cX "`ty: " cY "`tw: " cW "`th: " cH 144 | } else { 145 | cText := "" 146 | } 147 | 148 | UpdateText("Ctrl_Ctrl", cText) 149 | wX := "", wY := "", wW := "", wH := "" 150 | WinGetPos &wX, &wY, &wW, &wH, "ahk_id " curWin 151 | WinGetClientPos(&wcX, &wcY, &wcW, &wcH, "ahk_id " curWin) 152 | 153 | wText := "Screen:`tx: " wX "`ty: " wY "`tw: " wW "`th: " wH "`n" 154 | . "Client:`tx: " wcX "`ty: " wcY "`tw: " wcW "`th: " wcH 155 | 156 | UpdateText("Ctrl_Pos", wText) 157 | sbTxt := "" 158 | 159 | Loop { 160 | ovi := "" 161 | Try ovi := StatusBarGetText(A_Index) 162 | if (ovi = "") 163 | break 164 | sbTxt .= "(" A_Index "):`t" textMangle(ovi) "`n" 165 | } 166 | 167 | sbTxt := SubStr(sbTxt,1,-1) ; StringTrimRight, sbTxt, sbTxt, 1 168 | UpdateText("Ctrl_SBText", sbTxt) 169 | bSlow := oGui["Ctrl_IsSlow"].Value ; GuiControlGet, bSlow,, Ctrl_IsSlow 170 | 171 | if (bSlow) { 172 | DetectHiddenText False 173 | ovVisText := WinGetText() ; WinGetText, ovVisText 174 | DetectHiddenText True 175 | ovAllText := WinGetText() ; WinGetText, ovAllText 176 | } else { 177 | ovVisText := WinGetTextFast(false) 178 | ovAllText := WinGetTextFast(true) 179 | } 180 | 181 | UpdateText("Ctrl_VisText", ovVisText) 182 | UpdateText("Ctrl_AllText", ovAllText) 183 | } 184 | 185 | ; =========================================================================================== 186 | ; WinGetText ALWAYS uses the "slow" mode - TitleMatchMode only affects 187 | ; WinText/ExcludeText parameters. In "fast" mode, GetWindowText() is used 188 | ; to retrieve the text of each control. 189 | ; =========================================================================================== 190 | WinGetTextFast(detect_hidden) { 191 | controls := WinGetControlsHwnd() 192 | 193 | static WINDOW_TEXT_SIZE := 32767 ; Defined in AutoHotkey source. 194 | 195 | buf := Buffer(WINDOW_TEXT_SIZE * 2, 0) 196 | 197 | text := "" 198 | 199 | Loop controls.Length { 200 | hCtl := controls[A_Index] 201 | if !detect_hidden && !DllCall("IsWindowVisible", "ptr", hCtl) 202 | continue 203 | if !DllCall("GetWindowText", "ptr", hCtl, "Ptr", buf.ptr, "int", WINDOW_TEXT_SIZE) 204 | continue 205 | 206 | text .= StrGet(buf) "`r`n" 207 | } 208 | return text 209 | } 210 | 211 | ; =========================================================================================== 212 | ; Unlike using a pure GuiControl, this function causes the text of the 213 | ; controls to be updated only when the text has changed, preventing periodic 214 | ; flickering (especially on older systems). 215 | ; =========================================================================================== 216 | UpdateText(vCtl, NewText) { 217 | Global oGui 218 | static OldText := {} 219 | ctl := oGui[vCtl], hCtl := Integer(ctl.hwnd) 220 | 221 | if (!oldText.HasProp(hCtl) Or OldText.%hCtl% != NewText) { 222 | ctl.Value := NewText 223 | OldText.%hCtl% := NewText 224 | } 225 | } 226 | 227 | textMangle(x) { 228 | elli := false 229 | if (pos := InStr(x, "`n")) 230 | x := SubStr(x, 1, pos-1), elli := true 231 | else if (StrLen(x) > 40) 232 | x := SubStr(x,1,40), elli := true 233 | if elli 234 | x .= " (...)" 235 | return x 236 | } 237 | 238 | suspend_timer() { 239 | Global oGui 240 | SetTimer Update, 0 241 | UpdateText("Ctrl_Freeze", oGui.txtFrozen) 242 | } 243 | 244 | ~*Shift:: 245 | ~*Ctrl::suspend_timer() 246 | 247 | ~*Ctrl up:: 248 | ~*Shift up::SetTimer Update, 250 249 | -------------------------------------------------------------------------------- /lib/WindowsTheme.ahk: -------------------------------------------------------------------------------- 1 | class WindowsTheme { 2 | static PreferredAppMode := Map("Default", 0, "AllowDark", 1, "ForceDark", 2, "ForceLight", 3, "Max", 4) 3 | static uxtheme := DllCall("GetModuleHandle", "str", "uxtheme", "ptr") 4 | static SetPreferredAppMode := DllCall("GetProcAddress", "ptr", this.uxtheme, "ptr", 135, "ptr") 5 | static FlushMenuThemes := DllCall("GetProcAddress", "ptr", this.uxtheme, "ptr", 136, "ptr") 6 | 7 | static SetAppMode(DarkMode := True) { 8 | switch DarkMode{ 9 | case True: 10 | DllCall(this.SetPreferredAppMode, "Int", this.PreferredAppMode["ForceDark"]) 11 | DllCall(this.FlushMenuThemes) 12 | default: 13 | DllCall(this.SetPreferredAppMode, "Int", this.PreferredAppMode["Default"]) 14 | DllCall(this.FlushMenuThemes) 15 | } 16 | } 17 | 18 | static SetWindowAttribute(GuiObj, DarkMode := True) { 19 | global DarkColors := Map("Background", "0x202020", "Controls", "0x404040", "Font", "0xE0E0E0") 20 | global TextBackgroundBrush := DllCall("gdi32\CreateSolidBrush", "UInt", DarkColors["Background"], "Ptr") 21 | 22 | if (VerCompare(A_OSVersion, "10.0.17763") >= 0) { 23 | DWMWA_USE_IMMERSIVE_DARK_MODE := 19 24 | if (VerCompare(A_OSVersion, "10.0.18985") >= 0) { 25 | DWMWA_USE_IMMERSIVE_DARK_MODE := 20 26 | } 27 | switch DarkMode { 28 | case True: 29 | { 30 | DllCall("dwmapi\DwmSetWindowAttribute", "Ptr", GuiObj.hWnd, "Int", DWMWA_USE_IMMERSIVE_DARK_MODE, "Int*", True, "Int", 4) 31 | DllCall(this.SetPreferredAppMode, "Int", this.PreferredAppMode["ForceDark"]) 32 | DllCall(this.FlushMenuThemes) 33 | GuiObj.BackColor := DarkColors["Background"] 34 | } 35 | default: 36 | { 37 | DllCall("dwmapi\DwmSetWindowAttribute", "Ptr", GuiObj.hWnd, "Int", DWMWA_USE_IMMERSIVE_DARK_MODE, "Int*", False, "Int", 4) 38 | DllCall(this.SetPreferredAppMode, "Int", this.PreferredAppMode["Default"]) 39 | DllCall(this.FlushMenuThemes) 40 | GuiObj.BackColor := "Default" 41 | } 42 | } 43 | } 44 | } 45 | 46 | static SetWindowTheme(GuiObj, DarkMode := True) { 47 | static GWL_WNDPROC := -4 48 | static GWL_STYLE := -16 49 | static ES_MULTILINE := 0x0004 50 | static LVM_GETTEXTCOLOR := 0x1023 51 | static LVM_SETTEXTCOLOR := 0x1024 52 | static LVM_GETTEXTBKCOLOR := 0x1025 53 | static LVM_SETTEXTBKCOLOR := 0x1026 54 | static LVM_GETBKCOLOR := 0x1000 55 | static LVM_SETBKCOLOR := 0x1001 56 | static LVM_GETHEADER := 0x101F 57 | static GetWindowLong := A_PtrSize = 8 ? "GetWindowLongPtr" : "GetWindowLong" 58 | static SetWindowLong := A_PtrSize = 8 ? "SetWindowLongPtr" : "SetWindowLong" 59 | static Init := False 60 | static LV_Init := False 61 | global IsDarkMode := DarkMode 62 | 63 | Mode_Explorer := (DarkMode ? "DarkMode_Explorer" : "Explorer") 64 | Mode_CFD := (DarkMode ? "DarkMode_CFD" : "CFD") 65 | Mode_ItemsView := (DarkMode ? "DarkMode_ItemsView" : "ItemsView") 66 | 67 | for hWnd, GuiCtrlObj in GuiObj { 68 | switch GuiCtrlObj.Type { 69 | case "Button", "CheckBox", "ListBox", "UpDown": 70 | { 71 | DllCall("uxtheme\SetWindowTheme", "Ptr", GuiCtrlObj.hWnd, "Str", Mode_Explorer, "Ptr", 0) 72 | } 73 | case "ComboBox", "DDL": 74 | { 75 | DllCall("uxtheme\SetWindowTheme", "Ptr", GuiCtrlObj.hWnd, "Str", Mode_CFD, "Ptr", 0) 76 | } 77 | case "Edit": 78 | { 79 | if (DllCall("user32\" GetWindowLong, "Ptr", GuiCtrlObj.hWnd, "Int", GWL_STYLE) & ES_MULTILINE) { 80 | DllCall("uxtheme\SetWindowTheme", "Ptr", GuiCtrlObj.hWnd, "Str", Mode_Explorer, "Ptr", 0) 81 | } else { 82 | DllCall("uxtheme\SetWindowTheme", "Ptr", GuiCtrlObj.hWnd, "Str", Mode_CFD, "Ptr", 0) 83 | } 84 | } 85 | case "ListView": 86 | { 87 | if !(LV_Init) { 88 | static LV_TEXTCOLOR := SendMessage(LVM_GETTEXTCOLOR, 0, 0, GuiCtrlObj.hWnd) 89 | static LV_TEXTBKCOLOR := SendMessage(LVM_GETTEXTBKCOLOR, 0, 0, GuiCtrlObj.hWnd) 90 | static LV_BKCOLOR := SendMessage(LVM_GETBKCOLOR, 0, 0, GuiCtrlObj.hWnd) 91 | LV_Init := True 92 | } 93 | GuiCtrlObj.Opt("-Redraw") 94 | switch DarkMode { 95 | case True: 96 | { 97 | SendMessage(LVM_SETTEXTCOLOR, 0, DarkColors["Font"], GuiCtrlObj.hWnd) 98 | SendMessage(LVM_SETTEXTBKCOLOR, 0, DarkColors["Background"], GuiCtrlObj.hWnd) 99 | SendMessage(LVM_SETBKCOLOR, 0, DarkColors["Background"], GuiCtrlObj.hWnd) 100 | } 101 | default: 102 | { 103 | SendMessage(LVM_SETTEXTCOLOR, 0, LV_TEXTCOLOR, GuiCtrlObj.hWnd) 104 | SendMessage(LVM_SETTEXTBKCOLOR, 0, LV_TEXTBKCOLOR, GuiCtrlObj.hWnd) 105 | SendMessage(LVM_SETBKCOLOR, 0, LV_BKCOLOR, GuiCtrlObj.hWnd) 106 | } 107 | } 108 | DllCall("uxtheme\SetWindowTheme", "Ptr", GuiCtrlObj.hWnd, "Str", Mode_Explorer, "Ptr", 0) 109 | 110 | ; To color the selection - scrollbar turns back to normal 111 | ;DllCall("uxtheme\SetWindowTheme", "Ptr", GuiCtrlObj.hWnd, "Str", Mode_ItemsView, "Ptr", 0) 112 | 113 | ; Header Text needs some NM_CUSTOMDRAW coloring 114 | LV_Header := SendMessage(LVM_GETHEADER, 0, 0, GuiCtrlObj.hWnd) 115 | DllCall("uxtheme\SetWindowTheme", "Ptr", LV_Header, "Str", Mode_ItemsView, "Ptr", 0) 116 | GuiCtrlObj.Opt("+Redraw") 117 | } 118 | } 119 | } 120 | 121 | if !(Init) { 122 | ; https://www.autohotkey.com/docs/v2/lib/CallbackCreate.htm#ExSubclassGUI 123 | global WindowProcNew := CallbackCreate(WindowProc) ; Avoid fast-mode for subclassing. 124 | global WindowProcOld := DllCall("user32\" SetWindowLong, "Ptr", GuiObj.Hwnd, "Int", GWL_WNDPROC, "Ptr", WindowProcNew, "Ptr") 125 | Init := True 126 | } 127 | } 128 | } 129 | 130 | WindowProc(hwnd, uMsg, wParam, lParam) { 131 | critical 132 | static WM_CTLCOLOREDIT := 0x0133 133 | static WM_CTLCOLORLISTBOX := 0x0134 134 | static WM_CTLCOLORBTN := 0x0135 135 | static WM_CTLCOLORSTATIC := 0x0138 136 | static DC_BRUSH := 18 137 | 138 | if (IsDarkMode) { 139 | switch uMsg { 140 | case WM_CTLCOLOREDIT, WM_CTLCOLORLISTBOX: 141 | { 142 | DllCall("gdi32\SetTextColor", "Ptr", wParam, "UInt", DarkColors["Font"]) 143 | DllCall("gdi32\SetBkColor", "Ptr", wParam, "UInt", DarkColors["Controls"]) 144 | DllCall("gdi32\SetDCBrushColor", "Ptr", wParam, "UInt", DarkColors["Controls"], "UInt") 145 | return DllCall("gdi32\GetStockObject", "Int", DC_BRUSH, "Ptr") 146 | } 147 | case WM_CTLCOLORBTN: 148 | { 149 | DllCall("gdi32\SetDCBrushColor", "Ptr", wParam, "UInt", DarkColors["Background"], "UInt") 150 | return DllCall("gdi32\GetStockObject", "Int", DC_BRUSH, "Ptr") 151 | } 152 | case WM_CTLCOLORSTATIC: 153 | { 154 | DllCall("gdi32\SetTextColor", "Ptr", wParam, "UInt", DarkColors["Font"]) 155 | DllCall("gdi32\SetBkColor", "Ptr", wParam, "UInt", DarkColors["Background"]) 156 | return TextBackgroundBrush 157 | } 158 | } 159 | } 160 | return DllCall("user32\CallWindowProc", "Ptr", WindowProcOld, "Ptr", hwnd, "UInt", uMsg, "Ptr", wParam, "Ptr", lParam) 161 | } 162 | -------------------------------------------------------------------------------- /modules/Anyrun.ahk: -------------------------------------------------------------------------------- 1 | #Include 'Sort.ahk' 2 | 3 | ; 正则匹配最大支持长度默认为 32 位 4 | GLOBAL SUPPORT_LEN := 32 5 | GLOBAL DATA_FILTER_REG := 'i)^(?:' . DataType.app . '|' . DataType.file . '|' . DataType.web . '|' . DataType.inner . '|' . DataType.ext . '|' . DataType.dl . ')$' 6 | ; 端口判断过于简单 但是基本够用了 7 | GLOBAL IS_HTTP_Reg := 'i)^(?:https?://)?' ; 协议 8 | ; local 或 IP 或 英文网址 9 | . '(?:localhost' 10 | . '|(?:\d{1,2}|1\d{2}|2[0-4]\d|25[0-5])\.(?:\d{1,2}|1\d{2}|2[0-4]\d|25[0-5])\.(?:\d{1,2}|1\d{2}|2[0-4]\d|25[0-5])\.(?:\d{1,2}|1\d{2}|2[0-4]\d|25[0-5])' 11 | . '|(?!(?:\d+\.)+\d+)(?:[\w-一-龥]+\.)+[\w-一-龥]+' ; 华为.网址 12 | ; 警惕 中文.com 的网络诈骗:有些诈骗者可能会利用账户变更的名义来诱骗用户泄露个人信息。如果收到任何要求你提供账户信息的邮件或消息,务必通过官方客服渠道进行核实 13 | ; .com 是全球最广泛认可和使用的顶级域名,而 .网址是中国的国家级域名,但是在国内还是 .网址 比较正规,因为要有备案 14 | . ')' 15 | . '(:(?!0)(?![7-9]\d{4})\d{1,5})?' ; 端口 16 | . '(?:/[\w-./?%&=#一-龻]*)?\s*$' ; 路径(可以包含中文) 17 | GLOBAL MY_GUI 18 | GLOBAL MY_GUI_TITLE := '快捷启动' 19 | 20 | GLOBAL MyActionArray := [ 21 | MyAction('打开网址', 'list', isLegitimateWebsite, AppendWebsiteName, jumpURL), ; 是否提前些比较好,不用了,兜底挺好 22 | MyAction('生成二维码(磁力链)', 'list', isMagnetUrl,, CreateQRcode), 23 | MyAction('生成二维码(网址)', 'list', isLegitimateWebsite,, CreateQRcode), 24 | MyAction('在线预览 Office 文件', 'list', isOfficeUrl,, JumpOfficeUrl), 25 | ; 打开(文件,可能是 mp3 或者 mp4 或者 mov) 26 | MyAction('打开', 'list', path => isFileOrDirExists(path) && NOT DirExist(path), AppendFileType, path => Run(path)) , 27 | MyAction('前往文件夹', 'list', isDir,, OpenDir), 28 | MyAction('查看属性', 'list', isFileOrDirExists,, path => Run('properties "' . path . '"')), 29 | MyAction('打印文件', 'list', path => path ~= 'i).+\.(?:bmp|docx?|gif|jpe?g|ofd|pdf|png|pptx?|xlsx?)$' && isFileOrDirExists(path) && NOT DirExist(path),, path => Run('print "' . path . '"')), 30 | MyAction('删除文件', 'list', isFileOrDirExists,, DelFileOrDir), 31 | ] 32 | 33 | if IsSet(MY_BASH) 34 | MyActionArray.Push(MyAction('在 Bash 中打开所在位置', 'list', isFileOrDirExists,, OpenInBash)) 35 | if IsSet(MY_NEW_TERMINAL) 36 | MyActionArray.Push(MyAction('在新终端中打开', 'list', isFileOrDirExists,, OpenInNewTerminal)) 37 | else 38 | MyActionArray.Push(MyAction('在终端中打开', 'list', isFileOrDirExists,, OpenInTerminal)) 39 | if IsSet(MY_VSCode) 40 | MyActionArray.Push(MyAction('在 VSCode 中打开', 'list', isCodeFileOrDir,, path => Run(MY_VSCode . ' ' . path))) 41 | if IsSet(MY_IDEA) 42 | MyActionArray.Push(MyAction('在 IDEA 中打开', 'list', isCodeFileOrDir,, path => Run(MY_IDEA . ' ' . path))) 43 | 44 | ; 彩蛋 本机 IP 45 | MyActionArray.Push(MyAction('myip', 'edit',,, GetIPAddresses)) 46 | MyActionArray.Push(MyAction('myshu', 'edit',,, GetMouseInfo)) 47 | 48 | ; 设置监听 49 | #HotIf WinActive(MY_GUI_TITLE . " ahk_class i)^AutoHotkeyGUI$") 50 | ~Down::{ 51 | ; 当前焦点在 edit 上 且如果 listbox 有东西 则 焦点移动到 listbox 52 | if MY_GUI.FocusedCtrl.type = 'Edit' && StrLen(MY_GUI['listbox1'].Text) > 0 53 | ControlFocus 'listbox1' 54 | } 55 | ~UP::{ 56 | ; 如果焦点在 listbox 首项 再向上则焦点移动到 edit 57 | if MY_GUI.FocusedCtrl.type = 'ListBox' && MY_GUI['listbox1'].value == 1 58 | ControlFocus 'Edit1' 59 | } 60 | #HotIf 61 | 62 | Anyrun() { 63 | ; reload 过程中 MY_GUI_TITLE 会未定义 64 | if NOT IsSet(MY_GUI_TITLE) 65 | return 66 | ; 检查窗口是否已经存在,如果窗口已经存在,如果窗口不存在,则创建新窗口 67 | if (WinExist(MY_GUI_TITLE)) { 68 | 69 | WinClose ; 使用由上一句 WinExist 找到的窗口 70 | } else { 71 | ; S: 字体尺寸(单位为磅) 72 | ; AlwaysOnTop 使窗口保持在所有其他窗口的顶部 73 | ; Owner 可以让当前窗口从属于另一个窗口。从属的窗口默认不显示在任务栏, 并且它总是显示在其父窗口的上面. 当其父窗口销毁时它也被自动销毁 74 | ; -Caption 移除背景透明的窗口的边框和标题栏 75 | ; -Resize 禁止用户重新调整窗口的大小 76 | GLOBAL MY_GUI := Gui('AlwaysOnTop Owner -Caption -Resize', MY_GUI_TITLE) 77 | ; 横向和纵向边框收窄 78 | MY_GUI.MarginY := MY_GUI.MarginX := 2.8 79 | fontSize := 's21' 80 | MY_GUI.SetFont(fontSize, 'Microsoft YaHei') ; 设置优先字体(21 磅) 微软雅黑 81 | guiWidth := 432 82 | edit := MY_GUI.AddEdit(Format("w{1}", guiWidth)) 83 | ; R7:做到贴边 默认只显示 7 行 84 | ; Hidden:让控件初始为隐藏状态 85 | listBox := MY_GUI.AddListBox(Format("R7 w{1} XM+0 Y+0 BackgroundF0F0F0 Hidden", guiWidth)) 86 | button := MY_GUI.Add('Button', "default X0 Y0 Hidden", 'OK') 87 | edit.OnEvent('Change', onEditChange) 88 | ; 当 edit 控件都失去焦点则关闭窗口 89 | edit.OnEvent('LoseFocus', onEditLoseFocus) 90 | 91 | ; 当 listbox 控件都失去焦点则关闭窗口 92 | listbox.OnEvent('LoseFocus', onListboxLoseFocus) 93 | ; 双击列表条目 触发事件 94 | listbox.OnEvent('DoubleClick', onListBoxDoubleClick) 95 | 96 | ; 按回车触发 点击事件 97 | button.OnEvent('Click', onButtonClick) 98 | 99 | ; 按住 esc 销毁 窗口 100 | MY_GUI.OnEvent('Escape', onEscape) 101 | 102 | ; 居中但是稍微往上偏移些 103 | MY_GUI.Show(Format("xCenter y{1} AutoSize", A_ScreenHeight / 2 - 300)) 104 | 105 | ; 判断剪切板有没有内容 如果输入内容是文件或者网址 且离最后一次 ctrl + c/x 操作小于 13 秒则打开 anyrun 组件 这样给用户的自主性更大 106 | if (A_Clipboard != '' && DateDiff(A_NowUTC, CTRL_TIMESTAMP, 'Seconds') < 13) { 107 | pasteText := Trim(A_Clipboard, ' `t`r`n') 108 | if (pasteText ~= IS_HTTP_Reg || FileExist(pasteText)) 109 | Send '^v' 110 | } 111 | 112 | onEscape(*) { 113 | if (IsSet(MY_GUI)) { 114 | 115 | MY_GUI.Destroy 116 | MY_GUI := unset 117 | } 118 | } 119 | 120 | onEditChange(thisGui, *) { 121 | ; 一旦文本框有变化立即清空 122 | listBox.Delete 123 | ; 获取文本框输入内容 124 | editValue := thisGui.Value 125 | if (editValue == '') { 126 | listBox.Visible := false 127 | MY_GUI.Show 'AutoSize' 128 | return 129 | } 130 | dataArray := unset 131 | ; 精确匹配失败 将 转到模糊匹配 132 | ; 若为空则清空列表 或 大于设定长度 或 满足正则。然而, 集合 \.*?+[{|()^$ 中的任何字符则必须在其前面加上反斜杠才能被视为原义 133 | if (StrLen(editValue) <= SUPPORT_LEN && editValue ~= '^[-\+\s\d\.a-zA-Z一-龥]+$') { 134 | needleRegEx := 'i)' 135 | Loop Parse, editValue { 136 | ; 如果 edit 中键入了一个空格 则 只认为是一个空格 137 | if A_LoopField == ' ' 138 | needleRegEx .= '( )' 139 | else if A_LoopField == '+' 140 | needleRegEx .= '(\+).*' 141 | else 142 | needleRegEx .= '(' . A_LoopField . ').*' 143 | } 144 | dataArray := Array() 145 | for it in DATA_LIST { 146 | if (it.type ~= DATA_FILTER_REG) { 147 | if ('Array' == Type(it.alias)) { 148 | ; 如果有则选出最匹配的 array 149 | ; maxData 为 最佳匹配对象 150 | maxData := unset 151 | Loop it.alias.Length { 152 | ; 如果能匹配 153 | if RegExMatch(it.alias[A_Index], needleRegEx, ®ExMatchInfo) { 154 | data := {degree: computeDegree(regExMatchInfo) ; 匹配度 155 | , title: it.title ; 标题 156 | , type: it.type ; 类型 157 | } 158 | if NOT IsSet(maxData) 159 | maxData := data 160 | else if (dataArrayCompare(maxData, data) < 0) 161 | maxData := data 162 | } 163 | } 164 | if IsSet(maxData) 165 | dataArray.Push(maxData) 166 | } else if RegExMatch(it.alias, needleRegEx, ®ExMatchInfo) 167 | ; 如果能匹配 168 | dataArray.Push({degree: computeDegree(regExMatchInfo) 169 | , title: it.title ; . '-' . computeDegree(regExMatchInfo) 测试用 170 | , type: it.type 171 | }) 172 | } 173 | } 174 | } 175 | listBoxDataArray := unset 176 | if (NOT IsSet(dataArray) || dataArray.Length == 0) { 177 | listBoxDataArray := [] 178 | } else { 179 | dataArraySort(dataArray) 180 | listBoxDataArray := listBoxData(dataArray) 181 | } 182 | 183 | ; 搜索匹配 184 | ; 查询出所有搜索,如果前缀满足则添加到列表 185 | for it in DATA_LIST { 186 | if it.type == DataType.action and 1 == InStr(editValue, it.alias) 187 | listBoxDataArray.push(it.title . '-' . it.type) 188 | } 189 | 190 | ; 模糊匹配 按顺序 191 | for action in MyActionArray { 192 | ; 如果是 list 类型 且 符合条件 193 | if (action.type = 'list' && action.isMatch.Call(editValue)) { 194 | if action.HasOwnProp('appendTitle') 195 | listBoxDataArray.push(action.title . action.appendTitle.Call(editValue)) ; 最终的标题 196 | else 197 | listBoxDataArray.push(action.title) 198 | } 199 | } 200 | if IsSet(MY_GUI) { 201 | ; 显示出来 202 | listBox.Visible := listBoxDataArray.Length > 0 203 | if (listBox.Visible) { 204 | listBox.Add listBoxDataArray 205 | listBox.Choose 1 206 | } 207 | MY_GUI.Show "AutoSize" 208 | } else { 209 | MsgBox 'Anyrun 组件异常销毁', APP_NAME 210 | } 211 | } 212 | 213 | onEditLoseFocus(*) { 214 | if (IsSet(MY_GUI) && NOT listBox.Focused && NOT edit.Focused) { 215 | MY_GUI.Destroy 216 | MY_GUI := unset 217 | } 218 | } 219 | 220 | onListboxLoseFocus(*) { 221 | if (IsSet(MY_GUI) && NOT edit.Focused) { 222 | MY_GUI.Destroy 223 | MY_GUI := unset 224 | } 225 | } 226 | 227 | ; listbox 和 序号(从 1 开始) 228 | onListBoxDoubleClick(listBoxObj?, info?) { 229 | ; 此时 listbox 必定有焦点,则根据 title 反查 path 230 | item := unset 231 | if (StrLen(listBox.Text) > 0) { 232 | split := StrSplit(listBox.Text, '-') 233 | ; 分离出类型 和 名称 234 | if (split.Length == 2) { 235 | type := split[split.Length] 236 | title := split[1] 237 | ; 能否根据序号直接找到 item 呢,而不是通过反查,感觉不高效因此不采用 238 | item := findItemByTypeAndTitle(type, title) 239 | } else if (split.Length == 3 and split[2] == '') { 240 | type := split[split.Length] 241 | title := split[1] . '-' 242 | item := findItemByTypeAndTitle(type, title) 243 | } else if (split.Length == 4 and split[2] == '' and split[3] == '') { 244 | type := split[split.Length] 245 | title := split[1] . '--' 246 | item := findItemByTypeAndTitle(type, title) 247 | } 248 | } 249 | editValue := edit.Value 250 | 251 | ; 不能精确匹配,则尝试(打开网址 打开文件 前往文件夹 等),否则无事发生 252 | if (!IsSet(item) || item == '') { 253 | for action in MyActionArray { 254 | ; 如果是 list 则表示必须在列表中存在 255 | if (action.type = 'list') { 256 | ; 必须满足匹配规则:action.title 在 listBox.Text 首部 257 | if (1 == InStr(listBox.Text, action.title)) { 258 | action.run.Call(editValue) 259 | break 260 | } 261 | } 262 | ; 不在列表中显示,但是依旧有作用 263 | else if (action.type = 'edit') { 264 | ; 没有匹配规则 265 | if (editValue = action.title) { 266 | action.run.Call() 267 | break 268 | } 269 | } 270 | } 271 | } 272 | ; 用于 action 匹配,形如 bd + 关键字 273 | else if (item.type = DataType.action) { 274 | ; 拿到 alias 例如为 bd 则去除头部 bd 275 | realStr := SubStr(editValue, StrLen(item.alias) + 1) 276 | ; GitHub 加速服务 js 由 https://www.7ed.net/gitmirror/hub.html 提供 277 | if NOT(item.alias == 'pi' || item.alias == 'js' || item.alias == 'yl') { 278 | ; 特殊处理 ip 279 | if (item.alias == 'ip') { 280 | ; 去掉 http:// 和 https:// 281 | len := InStr(realStr, "://") 282 | if len 283 | realStr := SubStr(realStr, len + 3) 284 | } 285 | ; 非 pi 和 js、yl 一律会做 urlencode 286 | realStr := URIEncode(realStr) 287 | } 288 | if InStr(item.path, "{query}") 289 | runUrl := strReplace(item.path, "{query}", realStr) 290 | else 291 | runUrl := item.path . realStr 292 | Run runUrl 293 | } 294 | ; 兜底 精确匹配 DATA_FILTER_REG 295 | else { 296 | openPathByType item 297 | } 298 | 299 | if (IsSet(MY_GUI)) { 300 | 301 | MY_GUI.Destroy 302 | MY_GUI := unset 303 | } 304 | } 305 | 306 | onButtonClick(*) { 307 | ; 如果 listbox 有焦点 308 | if listBox.Focused 309 | onListBoxDoubleClick listBox 310 | else if Trim(edit.Value) == '' ; 如果焦点在 编辑框 且按下回车则会触发弹窗提示 311 | MsgBox '输入内容不能为空' 312 | else ; 否则表示焦点在 edit,如果列表有匹配项 则获取编辑框文本内容 313 | onListBoxDoubleClick listBox 314 | } 315 | } 316 | } 317 | 318 | /** 319 | * 计算匹配度 320 | * 321 | * @param regExMatchInfo 322 | * @returns {number} 323 | */ 324 | ComputeDegree(regExMatchInfo) { 325 | ; 总的匹配度 326 | degree := 0 327 | loop regExMatchInfo.Count { 328 | ; A_Index 第一次循环体执行时, 它为 1. 第二次, 它的值为 2; 依次类推 329 | item := SUPPORT_LEN - regExMatchInfo.Pos[A_Index] 330 | if item < 0 331 | break 332 | degree += (2 ** item) 333 | } 334 | return degree 335 | } 336 | 337 | ; 用到了 item.type、item.path 和 item.title 338 | OpenPathByType(item) { 339 | ; 处理 web 和 下载 340 | if (item.type = DataType.web || item.type = DataType.dl) { 341 | jumpURL(item.path) 342 | } else if (item.type = DataType.inner) { ; 精确处理:内部 343 | OpenInnerCommand(item.title, True) 344 | } else if (item.type = DataType.ext) { ; 精确处理:外部 345 | if InStr(item.path, '.ahk') { 346 | RunAHK(item.path) 347 | } else if InStr(item.path, '.exe') { 348 | ; 音量调节的特殊处理 349 | if 'tools\SoundControl.exe' = item.path 350 | SoundControl() 351 | else 352 | ActivateOrRun(, item.path) 353 | } else { 354 | MsgBox '不支持打开的文件类型:' . item.path 355 | } 356 | } else if (item.type = DataType.app && item.title == '微信') { ; 对 微信 优化体验:自动登录微信 357 | try { 358 | Run(item.path,,, &pid) 359 | WinWaitActive("ahk_pid " . pid) 360 | ; 手动等待 1.1 秒,否则可能会跳到扫码页 361 | Sleep 1100 362 | Send "{Space}" 363 | } catch 364 | MsgBox "找不到目标应用" 365 | } else if (item.type = DataType.app && item.title = 'alist') { ; 对 alist 特殊处理:使用命令行打开并添加 server 参数 366 | SplitPath(item.path,, &dir) 367 | Run(A_ComSpec " /C " . item.path . " server", dir) 368 | } else { 369 | ; 启动逻辑为每次都新建应用,而非通过 ActivateOrRun('', item.path) 打开已有应用 370 | try Run(item.path) 371 | catch as e ; 处理由上面区块抛出的首个错误 372 | { 373 | MsgBox "运行错误!`n" . e.Extra 374 | } 375 | } 376 | } 377 | 378 | class MyAction { 379 | __new(title, type, isMatch?, appendTitle?, run?) { 380 | ; 显示标题 381 | this.title := title 382 | ; 类型 edit or list 383 | this.type := type 384 | 385 | ; 过滤条件 是否符合匹配 386 | if IsSet(isMatch) 387 | this.isMatch := isMatch 388 | 389 | ; 拼接到 title 后面,意为进一步的描述 390 | if IsSet(appendTitle) 391 | this.appendTitle := appendTitle 392 | 393 | ; 执行动作 匹配后选定的行为 394 | if IsSet(run) 395 | this.run := run 396 | } 397 | } 398 | 399 | ; --- appendTitle 开始 --- 400 | AppendWebsiteName(path) { 401 | ; 对网址进行细化处理 402 | ; 从 dava.csv 中抽取符合条件的 b 列 (http 网址),若满足则赋值 d 列 403 | appendName := '' 404 | for (it in DATA_LIST) { 405 | if (it.type == DataType.web) { 406 | ; 完全匹配 407 | if (path == it.path) { 408 | appendName := '-' . it.title 409 | break 410 | } else { 411 | ; 可以匹配 www.doubao.com doubao.com https://www.doubao.com http://www.doubao.com https://www.doubao.com/ http://www.doubao.com/a/b.html 但不能匹配 abc.doubao.com https://edf.doubao.com http://ghi.doubao.com 412 | ; 提取关键部位 413 | ; 去掉 www 414 | if (1 == InStr(it.path, 'http://www.') || 1 == InStr(it.path, 'https://www.') || 1 == InStr(it.path, 'www.')) { 415 | uri := SubStr(it.path, InStr(it.path, 'www.') + StrLen('www.')) 416 | newUriReg := 'i)^(?:https?://)?(?:www\.)?' . StrReplace(uri, '.', "\.") . '(?:/.*)?$' 417 | } else { 418 | ; 去掉 :// 419 | if InStr(it.path, '://') 420 | uri := SubStr(it.path, InStr(it.path, '://') + StrLen('://')) 421 | else 422 | uri := it.path 423 | ; 如果是顶级域名(简单认为分段数为 2)则加上 www 424 | is_top_level_domain := (2 == StrSplit(uri, '.').Length) 425 | newUriReg := 'i)^(?:https?://)?' . (is_top_level_domain ? '(?:www\.)?' : '') . StrReplace(uri, '.', "\.") . '(?:/.*)?$' 426 | } 427 | if (path ~= newUriReg) { 428 | appendName := '-' . it.title 429 | break 430 | } 431 | } 432 | } 433 | } 434 | return appendName 435 | } 436 | 437 | AppendFileType(path) { 438 | ; 取出文件后缀名,若有的话 439 | if RegExMatch(path, '\.([^.]*$)', &matchInfo) { 440 | extension := matchInfo.1 441 | switch extension, false { 442 | case '3gp', 'avi', 'flv', 'mkv', 'mov', 'mp4', 'wmv': return "视频" 443 | case '7z', 'bz2', 'gz', 'gz2', 'rar', 'tar', 'zip': return "压缩包" 444 | case 'aac', 'flac', 'mp3', 'ogg', 'wav', 'wma': return "音频" 445 | case 'apk': return "安卓安装包" 446 | case 'bat', 'cmd': return " Windows 批处理文件" 447 | case 'bmp', 'gif', 'ico', 'jpeg', 'jpg', 'tiff', 'png', 'webp': return "图片" 448 | case 'css': return " CSS 样式表" 449 | case 'csv': return "逗号分隔值文件(二维表格)" 450 | case 'cer', 'crt': return "安全证书文件" 451 | case 'doc': return " Word 文档(旧版)" 452 | case 'docx': return " Word 文档" 453 | case 'dps': return " WPS 演示文件" 454 | case 'dwg': return " AutoCAD 绘图文件" 455 | case 'epub': return "开放式电子书" 456 | case 'et': return " WPS 表格文件" 457 | case 'exe', 'msi': return "可执行文件" 458 | case 'htm', 'html': return "网页文件" 459 | case 'ipa': return " iOS 应用安装包" 460 | case 'iso': return "光盘映像文件" 461 | case 'lnk': return "快捷方式" 462 | case 'log': return "日志文件" 463 | case 'json': return " JSON 数据文件" 464 | case 'md', 'markdown': return " markdown 文档" 465 | case 'mobi': return " Kindle 电子书格式" 466 | case 'js': return " JavaScript 脚本" 467 | case 'odf': return "电子表格" 468 | case 'odg': return " OpenDocument 绘图" 469 | case 'odp': return " OpenDocument 演示文稿" 470 | case 'ods': return " OpenDocument 表格" 471 | case 'odt': return " OpenDocument 文本" 472 | case 'ofd': return "国产 OFD 文档" 473 | case 'pdf': return " PDF 文档" 474 | case 'php': return " PHP 脚本文件" 475 | case 'ppt': return " PowerPoint 演示文稿(旧版)" 476 | case 'pptx': return " PowerPoint 演示文稿" 477 | case 'psd': return " Microsoft Visio 文件" 478 | case 'ps1': return " PowerShell 脚本文件" 479 | case 'py': return " Python 脚本文件" 480 | case 'rtf': return " RTF 文档" 481 | case 'svg': return "可缩放矢量图形文件" 482 | case 'torrent': return "种子文件" 483 | case 'txt': return "纯文本" 484 | case 'url' : return "网络书签" 485 | case 'vsd': return ' Adobe Photoshop 文件' 486 | case 'wps': return ' WPS 文档' 487 | case 'xls': return ' Excel 表格(旧版)' 488 | case 'xlsx': return ' Excel 表格' 489 | case 'xml': return ' xml 文件' 490 | default: return '文件' 491 | } 492 | } 493 | return "文件" 494 | } 495 | 496 | ; --- isMatch 开始 --- 497 | 498 | ; 是否是合法网站 499 | isLegitimateWebsite(url) { 500 | return url ~= IS_HTTP_Reg 501 | } 502 | 503 | ; 是否是合法磁力链 504 | isMagnetUrl(url) { 505 | return RegExMatch(url, '^magnet:.+') 506 | } 507 | 508 | ; 是否是在线 office 文件 509 | isOfficeUrl(url) { 510 | return RegExMatch(url, 'i)^(?:https?://)?.{3,}\.(?:doc|docx|ppt|pptx|xls|xlsx)$') 511 | } 512 | 513 | isFileOrDirExists(path) { 514 | ; con 文件或目录 为何存在,我用不到 515 | return path !== '*' && path !== '/' && 'con' != SubStr(path, 1, 3) && FileExist(path) 516 | } 517 | 518 | ; 是否是文件夹,如果当前是文件则提取 519 | isDir(path) { 520 | if path == '*' || path == '/' 521 | return false 522 | 523 | if DirExist(path) { 524 | isMatch := true 525 | } else if FileExist(path) { 526 | ; 抽出文件夹 527 | if RegExMatch(path, '.*[\\/]', ®ExMatchInfo) 528 | isMatch := DirExist(regExMatchInfo.0) 529 | else 530 | isMatch := false 531 | } else { 532 | isMatch := false 533 | } 534 | return isMatch 535 | } 536 | 537 | isCodeFileOrDir(path) { 538 | ; con 文件或目录 为何存在,我用不到 539 | if path == '*' || path == '/' || 'con' = SubStr(path, 1, 3) 540 | return false 541 | if FileExist(path) { 542 | if DirExist(path) 543 | return true 544 | return path ~= 'i)\.(?:txt|html?|xml|xslt?|js|ts|css|php|asp|pl|py|java|c|cpp|cc|h|hpp|hh|cs|vb|sql|sh|ini|bat|cmd|aspx|ashx|asmx|ascx|cfg|conf|json|yaml|yml|rb|lua|swift|m|mm|r|pas|go|rs|dart|kt|scala|hs|erl|hrl|clj|groovy|fs|ps1|vhd|vhdl|verilog|markdown|md|ahk)' 545 | } 546 | return false 547 | } 548 | 549 | ; --- run 开始 --- 550 | CreateQRcode(path) { 551 | Run('https://api.cl2wm.cn/api/qrcode/code?text=' . URIEncode(path)) 552 | } 553 | 554 | JumpOfficeUrl(path) { 555 | Run('https://view.officeapps.live.com/op/view.aspx?src=' . path) 556 | } 557 | 558 | OpenDir(path) { 559 | if (DirExist(path)) { 560 | Run path 561 | } else { 562 | RegExMatch(path, '.*[\\/]', ®ExMatchInfo) 563 | Run(regExMatchInfo.0) 564 | } 565 | } 566 | 567 | DelFileOrDir(path) { 568 | DirExist(path) ? DirDelete(path) : FileDelete(path) 569 | } 570 | 571 | OpenInBash(path) { 572 | ; 在 bash 中打开所在文件夹 573 | if (DirExist(path)) { 574 | ; 盘符根目录需要以 \ 结尾才生效,所以都统一加上 575 | endChar := SubStr(path, StrLen(path)) 576 | addSomething := (endChar = '\' || endChar = '/') ? "" : "\" 577 | Run(MY_BASH, path . addSomething) 578 | } else { 579 | RegExMatch(path, '.*[\\/]', ®ExMatchInfo) 580 | Run(MY_BASH, regExMatchInfo.0) 581 | } 582 | } 583 | 584 | OpenInTerminal(path) { 585 | ; 在终端中打开所在文件夹 586 | if (DirExist(path)) { 587 | ; 盘符根目录需要以 \ 结尾才生效 588 | endChar := SubStr(path, StrLen(path)) 589 | addSomething := (endChar = '\' || endChar = '/') ? "" : "\" 590 | Run(A_ComSpec, path . addSomething) 591 | } else { 592 | RegExMatch(path, '.*[\\/]', ®ExMatchInfo) 593 | Run(A_ComSpec, regExMatchInfo.0) 594 | } 595 | } 596 | 597 | OpenInNewTerminal(path) { 598 | ; 在终端中打开所在文件夹 599 | if (DirExist(path)) { 600 | ; 盘符根目录需要以 \ 结尾才生效,所以都统一加上 601 | endChar := SubStr(path, StrLen(path)) 602 | addSomething := (endChar = '\' || endChar = '/') ? "" : "\" 603 | Run(MY_NEW_TERMINAL ' /d .', path . addSomething) 604 | } else { 605 | RegExMatch(path, '.*[\\/]', ®ExMatchInfo) 606 | Run(MY_NEW_TERMINAL ' /d .', regExMatchInfo.0) 607 | } 608 | } 609 | 610 | ; 包含了所有我预设的内部命令 611 | OpenInnerCommand(title, isConfirm := false) { 612 | switch title { 613 | case '环境变量': Run 'rundll32 sysdm.cpl,EditEnvironmentVariables' 614 | case '终端': Run A_ComSpec ; 在用户根目录打开文件夹 615 | ; 系统操作 616 | case '重启': 617 | if (isConfirm) { 618 | if MsgBox("立即" . title . "?", APP_NAME, "OKCancel") = "OK" 619 | SystemReboot 620 | } else { 621 | SystemReboot 622 | } 623 | case '关机': 624 | if (isConfirm) { 625 | if MsgBox("立即" . title . "?", APP_NAME, "OKCancel") = "OK" 626 | SystemShutdown 627 | } else { 628 | SystemShutdown 629 | } 630 | case '锁屏', '锁定', '锁屏/锁定' : SystemLockScreen 631 | case '睡眠': SystemSleep() 632 | case '激活屏幕保护程序': SendMessage(0x0112, 0xF140, 0,, "Program Manager") ; 0x0112 为 WM_SYSCOMMAND, 而 0xF140 为 SC_SCREENSAVE 633 | 634 | case '清空回收站': FileRecycleEmpty() 635 | case '息屏': SystemSleepScreen() 636 | case '注销': SystemLogoff() 637 | ; 媒体类 638 | case '静音': Send '{Volume_Mute}' 639 | case '上一曲': Send '{Media_Prev}' 640 | case '下一曲': Send '{Media_Next}' 641 | case '暂停': Send '{Media_Play_Pause}' 642 | 643 | case '音量设为10': SoundSetVolume 10 644 | case '音量设为20': SoundSetVolume 20 645 | case '音量设为30': SoundSetVolume 30 646 | case '音量设为40': SoundSetVolume 40 647 | case '音量设为50': SoundSetVolume 50 648 | 649 | case '音量设为60': SoundSetVolume 60 650 | case '音量设为70': SoundSetVolume 70 651 | case '音量设为80': SoundSetVolume 80 652 | case '音量设为90': SoundSetVolume 90 653 | case '音量设为最大': SoundSetVolume 100 654 | 655 | case '取消关机任务': Run('shutdown /a',, 'Hide') 656 | ; 其他 657 | case '关闭程序': SmartCloseWindow() 658 | default: 659 | if FoundPos := InStr(title, "秒后关机") { 660 | second := SubStr(title, 1, FoundPos - 1) 661 | Run('shutdown /a', , 'Hide') 662 | Run('shutdown /s /t ' . second,, 'Hide') 663 | } else if FoundPos := InStr(title, "分钟后关机") { 664 | min := SubStr(title, 1, FoundPos - 1) 665 | Run('shutdown /a', , 'Hide') 666 | Run('shutdown /s /t ' . min * 60,, 'Hide') 667 | } else if FoundPos := InStr(title, "小时后关机") { 668 | hour := SubStr(title, 1, FoundPos - 1) 669 | Run('shutdown /a', , 'Hide') 670 | Run('shutdown /s /t ' . hour * 60 * 60,, 'Hide') 671 | } else { 672 | MsgBox '非系统内置命令!', APP_NAME 673 | } 674 | } 675 | } 676 | 677 | GetIPAddresses() { 678 | addresses := SysGetIPAddresses() 679 | msg := "IP 地址:`n" 680 | for address in addresses 681 | msg .= (address . "`n") 682 | MsgBox msg, 'IP 信息-' . APP_NAME 683 | } 684 | 685 | GetMouseInfo() { 686 | temp := A_Clipboard 687 | RunWait(A_ComSpec . " /c " . A_WorkingDir . "\tools\MouseSC_Query.bat | clip", , "Hide") 688 | MsgBox A_Clipboard, '鼠标信息-' . APP_NAME 689 | A_Clipboard := temp 690 | } 691 | -------------------------------------------------------------------------------- /modules/CheckUpdate.ahk: -------------------------------------------------------------------------------- 1 | CheckUpdate(isNeedCallback := False) { 2 | localIsAlphaOrBeta := InStr(CODE_VERSION, 'alpha') || InStr(CODE_VERSION, 'beta') 3 | regValueName := 'last_check_date' 4 | ; 手动检查更新 或 本地为调试版本 或 正式版的检查间隔需大于 24 小时 5 | if (isNeedCallback || localIsAlphaOrBeta || DateDiff(A_NowUTC, RegRead(REG_KEY_NAME, regValueName, '20000101000000'), 'days') >= 1) { 6 | req := ComObject('Msxml2.XMLHTTP') 7 | ; 打开启用异步的请求. 8 | checkUrl := 'https://acc8226.atomgit.net/jiejian/' . (localIsAlphaOrBeta ? 'SNAPSHOT' : 'RELEASE') 9 | req.open('GET', checkUrl, true) 10 | 11 | ; 设置回调函数 12 | req.onreadystatechange := ready 13 | ; 发送请求 Ready() 将在其完成后被调用 14 | req.send() 15 | 16 | ready() { 17 | if req.readyState != 4 ; 没有完成 18 | return 19 | if (req.status == 200) { 20 | serverVersion := req.responseText 21 | ; 正式版需要写入当前日期信息 22 | if !localIsAlphaOrBeta 23 | RegWrite(A_NowUTC, 'REG_SZ', REG_KEY_NAME, regValueName) 24 | if (VerCompare(CODE_VERSION, serverVersion) < 0) { 25 | if MsgBox('捷键 ' . CODE_VERSION . ' 非最新,去下载最新版 ' . serverVersion . '?', '检查更新', 'YesNo') = 'Yes' 26 | Run 'https://acc8226.onrender.com/mypage/pages/downloadJiejian' 27 | } else if (isNeedCallback) { 28 | MsgBox('当前已是最新版本', '检查更新-捷键') 29 | } 30 | } else if (isNeedCallback) { 31 | switch req.status { 32 | ; 0 表示安全证书的吊销信息不可用, 12007 表示没有网 33 | case 0, 12007: 34 | MsgBox "请连接网络后重试", '检查更新-捷键' 35 | SetTimer CheckUpdate, -60 * 60 * 1000 ; 无网络则 60 分钟后重试 36 | case 404: 37 | MsgBox '升级页面找不到', '检查更新-捷键' 38 | case 12029: 39 | MsgBox "网络连接错误,请稍候再试", '检查更新-捷键' 40 | default: 41 | MsgBox('检测升级失败,错误码为 ' . req.status . ',请稍候再试', '检查更新-捷键') 42 | } 43 | } 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /modules/ConfigMouse.ahk: -------------------------------------------------------------------------------- 1 | ; 左边界 2 | #HotIf MouseIsOnLeftEdge() 3 | MButton::{ 4 | ; 静音/不静音主音量 5 | Send '{Volume_Mute}' 6 | Tip '静音/恢复', -399 7 | } 8 | RButton::{ 9 | Send '{Media_Play_Pause}' 10 | Tip '暂停/恢复', -399 11 | } 12 | 13 | WheelUp::{ 14 | Send '{Volume_Up}' 15 | ; win 8 以前,由于没有侧边音量指示器 16 | if (VerCompare(A_OSVersion, '6.2') < 0) 17 | SetTimer(TipRoundSoundVolume, -80) 18 | } 19 | 20 | WheelDown::{ 21 | Send '{Volume_Down}' 22 | ; win 8 以前,由于没有侧边音量指示器 23 | if (VerCompare(A_OSVersion, '6.2') < 0) 24 | SetTimer(TipRoundSoundVolume, -80) 25 | } 26 | 27 | ; 上一曲 28 | XButton2::{ 29 | Send '{Media_Prev}' 30 | Tip('上一曲', -399) 31 | } 32 | ; 下一曲 33 | XButton1::{ 34 | Send '{Media_Next}' 35 | Tip('下一曲', -399) 36 | } 37 | 38 | ; 上边界 39 | #HotIf MouseIsOnTopEdge() 40 | MButton::{ 41 | Send '{Volume_Mute}' 42 | Tip('静音/恢复', -399) 43 | } 44 | RButton::{ 45 | Send '{Media_Play_Pause}' 46 | Tip('暂停/恢复', -399) 47 | } 48 | 49 | WheelUp::{ 50 | Send '{Media_Next}' 51 | Tip('下一曲', -399) 52 | } 53 | WheelDown::{ 54 | Send '{Media_Prev}' 55 | Tip('上一曲', -399) 56 | } 57 | 58 | ; 鼠标移动到任务栏上 59 | #HotIf MouseIsOver('ahk_class Shell_TrayWnd') 60 | MButton::Send '{Volume_Mute}' 61 | WheelUp::{ 62 | Send '{Volume_Up}' 63 | ; win 11 22533 版本更新了音量指示器,底部居中显示更加美观了 64 | if (VerCompare(A_OSVersion, '10.0.22533') < 0) 65 | SetTimer(TipRoundSoundVolume, -80) 66 | } 67 | 68 | WheelDown::{ 69 | Send '{Volume_Down}' 70 | if (VerCompare(A_OSVersion, '10.0.22533') < 0) 71 | SetTimer(TipRoundSoundVolume, -80) 72 | } 73 | 74 | TipRoundSoundVolume() { 75 | Tip('音量 ' . Round(SoundGetVolume()), -399) 76 | } 77 | 78 | ; 下一曲 79 | XButton1::Send '{Media_Next}' 80 | ; 上一曲 81 | XButton2::Send '{Media_Prev}' 82 | 83 | ; 音量小组件的滑动用起来 84 | #HotIf WinActive('ahk_class MyKeymap_Sound_Control') 85 | WheelUp::Send 'e' 86 | WheelDown::Send 'd' 87 | #HotIf 88 | 89 | ; 鼠标移动到屏幕左边缘 90 | MouseIsOnLeftEdge() { 91 | MouseGetPos(&OutputVarX) 92 | return OutputVarX >= 0 && OutputVarX <= 2 93 | } 94 | 95 | ; 鼠标移动到屏幕上边缘 96 | MouseIsOnTopEdge() { 97 | MouseGetPos(, &OutputVarY) 98 | return OutputVarY >= 0 && OutputVarY <= 2 99 | } 100 | 101 | MouseIsOver(WinTitle) { 102 | MouseGetPos(,, &Win) 103 | return WinExist(WinTitle . ' ahk_id ' . Win) 104 | } 105 | -------------------------------------------------------------------------------- /modules/Crypt.ahk: -------------------------------------------------------------------------------- 1 | ; XOR 加密/解密函数 2 | XOR_Crypt(str, key := "li") { 3 | encrypted := "" 4 | Loop Parse str { 5 | keyIndex := Mod(A_Index, StrLen(key)) + 1 6 | keyChar := SubStr(key, keyIndex, 1) 7 | encrypted .= Chr(Ord(A_LoopField) ^ Ord(keyChar)) 8 | } 9 | return encrypted 10 | } 11 | 12 | IS_Crypt_String(originalStr) { 13 | return SubStr(originalStr, 1, 1) = "密" 14 | } 15 | 16 | ; ; 加密 17 | ; originalStr := "1334598467" 18 | ; encryptedStr := '密' . XOR_Crypt(originalStr) 19 | ; MsgBox "加密后的字符串(可能不可见):`n" encryptedStr 20 | ; A_Clipboard := encryptedStr 21 | 22 | ; ; 解密 23 | ; decryptedStr := XOR_Crypt(SubStr(encryptedStr, 2)) 24 | ; MsgBox "解密后的字符串:`n" decryptedStr 25 | -------------------------------------------------------------------------------- /modules/GenerateShortcuts.ahk: -------------------------------------------------------------------------------- 1 | #Requires AutoHotkey v2.0 2 | #SingleInstance Force 3 | #NoTrayIcon 4 | 5 | SetWorkingDir(A_ScriptDir . "\..") 6 | 7 | ; 注:每次更新代码后需要覆盖 extra 下的 GenerateShortcuts.exe 8 | 9 | ; 由于 windows 系统不允许存在同名文件和文件夹,故预先删除之 10 | try FileDelete "shortcuts" 11 | try DirDelete "shortcuts", true 12 | ; 休息 0.05 s,防止 delete 操作未完成引起的 shortcuts 目录被占用问题 13 | Sleep 50 14 | try DirCreate "shortcuts" 15 | 16 | ; 排除特定的快捷方式 17 | ; 不能包含特定关键字 18 | useless := "i)uninstall|卸载|help|iSCSI 发起程序|ODBC 数据源|Data Sources \(ODBC\)" 19 | . "|ODBC Data|Windows 内存诊断|恢复驱动器|组件服务|碎片整理和优化驱动器|Office 语言首选项" 20 | . "|手册|更新|帮助|Tools Command Prompt for|license|Website|设置向导|More Games from Microsoft" 21 | . "|细胞词库|意见反馈|输入法管理器|输入法修复器|皮肤下载|官方网站| 网站|火绒日志" 22 | . "|Welcome Center|\(安全模式\)|on the Web" 23 | ; 不能以 开头 24 | . "|^(?:Windows Easy Transfer Reports|皮肤盒子|打字入门" 25 | . "|Microsoft Office 文档关联中心|Internet Explorer \(No Add-ons\)|Windows Easy Transfer Reports" 26 | . "|Microsoft Office 2007 控制中心|Microsoft Office 语言设置|iSCSI Initiator" 27 | . "|Add to archive|Backup and Restore Center|Configure PeaZip|Extract here|网页按键精灵|遥测日志|遥测仪表板" 28 | . ")" 29 | ; 不能是 30 | . "|^(?:" 31 | Loop Files A_StartupCommon . "\*.lnk*" 32 | useless .= "|" . A_LoopFileName 33 | Loop Files A_Startup . "\*.lnk*" 34 | useless .= "|" . A_LoopFileName 35 | useless .= ')$' 36 | 37 | ; 把开始菜单中的快捷方式都拷贝到 shortcuts 目录 38 | CopyFiles(A_ProgramsCommon "\*.lnk", "shortcuts\", useless) 39 | CopyFiles(A_Programs "\*.lnk", "shortcuts\", useless) 40 | ; 然后再生成 UWP 相关的快捷方式 41 | oFolder := ComObject("Shell.Application").NameSpace("shell:AppsFolder") 42 | if Type(oFolder) = 'ComObject' 43 | for item in oFolder.Items 44 | if NOT(FileExist("shortcuts\" item.Name ".lnk") || item.Name . '.lnk' ~= useless) 45 | try FileCreateShortcut("shell:appsfolder\" item.Path, "shortcuts\" item.Name ".lnk") 46 | 47 | CopyFiles(pattern, dest, ignore := "") { 48 | Loop Files pattern, "R" { 49 | if A_LoopFileName ~= ignore 50 | continue 51 | try FileCopy(A_LoopFilePath, dest, true) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /modules/MyTrayMenu.ahk: -------------------------------------------------------------------------------- 1 | class MyTrayMenu { 2 | 3 | __new() { 4 | ; 当前是否是选中状态 5 | GLOBAL IS_AUTO_START_UP 6 | ; 是否启用定时提醒 7 | GLOBAL ENABLE_DARK_MODE := RegRead(REG_KEY_NAME, REG_DARK_MODE, true) 8 | GLOBAL ENABLE_TIMER_REMINDER := RegRead(REG_KEY_NAME, REG_RELAX_REMINDER, false) 9 | 10 | ; 读取当前语言状态,如果读取不到则默认是中文 11 | LANG_PATH := A_ScriptDir . "\lang\" . CURRENT_LANG . ".ini" 12 | 13 | try { 14 | this.editScript:= IniRead(LANG_PATH, "Tray", "editScript") 15 | } catch as e { 16 | ; 发生 error,语言恢复成英文 17 | MsgBox "An error was thrown!`nSpecifically: " e.Message 18 | global CURRENT_LANG := 'en' 19 | LANG_PATH := A_ScriptDir "\lang\" . CURRENT_LANG . ".ini" 20 | this.editScript:= IniRead(LANG_PATH, "Tray", "editScript", 'Edit Script (&E)') 21 | } 22 | this.listVars:= IniRead(LANG_PATH, "Tray", "listVars", 'List Variables') 23 | 24 | this.pause := IniRead(LANG_PATH, "Tray", "pause", 'Disable Shortcuts (&S)') 25 | this.restart:= IniRead(LANG_PATH, "Tray", "restart", 'Restart Program (&R)') 26 | this.search:= IniRead(LANG_PATH, "Tray", "search", 'Search (&Q)') 27 | this.startUp:= IniRead(LANG_PATH, "Tray", "startUp", 'Startup on Boot') 28 | 29 | this.switchLang:= IniRead(LANG_PATH, "Tray", "switchLang", 'Language (&L)') 30 | 31 | this.more:= IniRead(LANG_PATH, "Tray", "more", 'More (&M)') 32 | this.document:= IniRead(LANG_PATH, "Tray", "document", 'Help Documentation (&H)') 33 | this.video:= IniRead(LANG_PATH, "Tray", "video", 'Video Tutorial (&V)') 34 | this.statistics:= IniRead(LANG_PATH, "Tray", "statistics", 'Usage Statistics (&S)') 35 | this.viewWinId:= IniRead(LANG_PATH, "Tray", "viewWinId", 'View Window Identifier (&V)') 36 | this.followMeCSDN:= IniRead(LANG_PATH, "Tray", "followMeCSDN", 'Follow Me on CSDN (&F)') 37 | this.softwareHomepage:= IniRead(LANG_PATH, "Tray", "followMeGH", 'Software Homepage (&G)') 38 | this.enableDarkMode:= IniRead(LANG_PATH, "Tray", "enableDarkMode", "Enable Dark Mode") 39 | this.enableTimerReminder:= IniRead(LANG_PATH, "Tray", "enableTimerReminder", "Enable Eye Care Reminder") 40 | this.update:= IniRead(LANG_PATH, "Tray", "update", 'Check for Updates (&U)...') 41 | this.about:= IniRead(LANG_PATH, "Tray", "about", 'About (&A)') 42 | 43 | this.exit:= IniRead(LANG_PATH, "Tray", "exit", 'Exit (&X)') 44 | 45 | ; 快捷方式以 lnk 结尾 46 | this.linkFile := A_Startup . "\jiejian.lnk" 47 | 48 | if A_IsCompiled 49 | this.shortcut := 'jiejian' . (A_Is64bitOS ? '64' : '32' ) . '.exe' 50 | else 51 | this.shortcut := 'jiejian.ahk' 52 | this.shortcut := A_WorkingDir . '\' . this.shortcut 53 | 54 | trayMenuHandlerFunc := this.TrayMenuHandler.Bind(this) 55 | 56 | ; 删除原有的 菜单项 57 | A_TrayMenu.Delete() 58 | if (!A_IsCompiled) { 59 | A_TrayMenu.Add(this.editScript, trayMenuHandlerFunc) 60 | A_TrayMenu.Add(this.listVars, trayMenuHandlerFunc) 61 | A_TrayMenu.Add 62 | } 63 | 64 | ; 右对齐不好使,我醉了 65 | A_TrayMenu.Add(this.pause, trayMenuHandlerFunc) 66 | A_TrayMenu.Add(this.restart, trayMenuHandlerFunc) 67 | A_TrayMenu.Add(this.search, trayMenuHandlerFunc) 68 | A_TrayMenu.Add(this.startUp, trayMenuHandlerFunc) 69 | A_TrayMenu.Add 70 | 71 | ; 切换语言 72 | this.langMenu := Menu() 73 | A_TrayMenu.Add(this.switchLang, this.langMenu) 74 | this.CreateLangMenu() 75 | 76 | ; 添加子菜单到上面的菜单中 77 | moreMenu := Menu() 78 | moreMenu.Add(this.document, trayMenuHandlerFunc) 79 | moreMenu.Add(this.video, trayMenuHandlerFunc) 80 | moreMenu.Add(this.statistics, trayMenuHandlerFunc) 81 | moreMenu.Add(this.viewWinId, trayMenuHandlerFunc) 82 | moreMenu.Add(this.followMeCSDN, trayMenuHandlerFunc) 83 | moreMenu.Add(this.softwareHomepage, trayMenuHandlerFunc) 84 | moreMenu.Add(this.enableDarkMode, trayMenuHandlerFunc) 85 | moreMenu.Add(this.enableTimerReminder, trayMenuHandlerFunc) 86 | moreMenu.Add(this.update, trayMenuHandlerFunc) 87 | moreMenu.Add(this.about, trayMenuHandlerFunc) 88 | A_TrayMenu.Add(this.more, moreMenu) 89 | this.moreMenu := moreMenu 90 | 91 | A_TrayMenu.Add(this.exit, trayMenuHandlerFunc) 92 | 93 | ; 检查是否是自启状态 94 | if (FileExist(this.LinkFile)) { 95 | ; 获取快捷方式(.lnk) 文件的信息, 例如其目标文件 96 | FileGetShortcut(this.LinkFile, &OutTarget) 97 | if (OutTarget !== this.shortcut) { 98 | IS_AUTO_START_UP := false 99 | A_TrayMenu.UnCheck this.startUp 100 | } else { 101 | IS_AUTO_START_UP := true 102 | A_TrayMenu.Check this.startUp 103 | } 104 | } else { 105 | IS_AUTO_START_UP := false 106 | A_TrayMenu.UnCheck this.startUp 107 | } 108 | 109 | WindowsTheme.SetAppMode ENABLE_DARK_MODE 110 | if (ENABLE_DARK_MODE) { 111 | WindowsTheme.SetAppMode ENABLE_DARK_MODE 112 | moreMenu.Check this.enableDarkMode 113 | } else { 114 | moreMenu.UnCheck this.enableDarkMode 115 | } 116 | ; 自动获取系统的深色模式开关 117 | ; SYSTEM_THEME_MODE := RegRead("HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize", "SystemUsesLightTheme", true) 118 | ; WindowsTheme.SetAppMode(!SYSTEM_THEME_MODE) 119 | 120 | ; 是否开启定时提醒 121 | this.counter := RelaxCounter() 122 | if (ENABLE_TIMER_REMINDER) { 123 | ; 测试 this.counter.Tick 124 | this.counter.Start 125 | moreMenu.Check(this.enableTimerReminder) 126 | } else { 127 | moreMenu.UnCheck(this.enableTimerReminder) 128 | } 129 | 130 | A_TrayMenu.Default := this.pause 131 | A_TrayMenu.ClickCount := 1 ; 单击可以暂停 132 | } 133 | 134 | CreateLangMenu() { 135 | this.langMenu.Delete 136 | 137 | switchLanguageFunc := this.switchLanguage.Bind(this) 138 | Loop Files A_ScriptDir "\lang\*.ini" { 139 | SplitPath A_LoopFileName, , , , &FileNameNoExt 140 | 141 | currentLang := Code2Language(FileNameNoExt) 142 | this.langMenu.Add(currentLang ? currentLang : FileNameNoExt, switchLanguageFunc) 143 | } 144 | 145 | currentLang := Code2Language(CURRENT_LANG) 146 | this.langMenu.Check(currentLang ? currentLang : CURRENT_LANG) 147 | 148 | Code2Language(code) { 149 | switch code { 150 | case 'zh-Hans': return '简体中文 🇨🇳' 151 | case 'zh-Hant': return '繁体中文 🇨🇳' 152 | case 'ar': return '(العربية)' 153 | case 'de': return 'Deutsch 🇩🇪' 154 | case 'en': return 'English' 155 | case 'es': return 'Español 🇪🇸' 156 | case 'fr': return 'Français 🇫🇷' 157 | case 'it': return 'Italiano 🇮🇹' 158 | case 'ja': return '日本語 🇯🇵' 159 | case 'ko': return '한국어 🇰🇷' 160 | case 'pt': return 'Português 🇵🇹' 161 | case 'ru': return 'Русский 🇷🇺' 162 | case 'tr': return 'Türkçe 🇹🇷' 163 | default: return false 164 | } 165 | } 166 | } 167 | 168 | switchLanguage(ItemName, ItemPos, MyMenu) { 169 | global CURRENT_LANG 170 | switch ItemName { 171 | case '简体中文 🇨🇳': CURRENT_LANG := 'zh-Hans' 172 | case '繁体中文 🇨🇳': CURRENT_LANG := 'zh-Hant' 173 | case '(العربية)': CURRENT_LANG := 'ar' 174 | case 'Deutsch 🇩🇪': CURRENT_LANG := 'de' 175 | case 'English': CURRENT_LANG := 'en' 176 | case 'Español 🇪🇸': CURRENT_LANG := 'es' 177 | case 'Français 🇫🇷': CURRENT_LANG := 'fr' 178 | case 'Italiano 🇮🇹': CURRENT_LANG := 'it' 179 | case '日本語 🇯🇵': CURRENT_LANG := 'ja' 180 | case '한국어 🇰🇷': CURRENT_LANG := 'ko' 181 | case 'Português 🇵🇹': CURRENT_LANG := 'pt' 182 | case 'Русский 🇷🇺': CURRENT_LANG := 'ru' 183 | case 'Türkçe 🇹🇷': CURRENT_LANG := 'tr' 184 | default: CURRENT_LANG := ItemName 185 | } 186 | Reload 187 | } 188 | 189 | /** 190 | * 托盘菜单被点击 191 | * 192 | * @param ItemName 193 | * @param ItemPos 194 | * @param MyMenu 195 | */ 196 | TrayMenuHandler(ItemName, ItemPos, MyMenu) { 197 | GLOBAL IS_AUTO_START_UP 198 | GLOBAL ENABLE_DARK_MODE 199 | GLOBAL ENABLE_TIMER_REMINDER 200 | 201 | switch ItemName, 'off' { 202 | case this.editScript: Edit() 203 | case this.listVars: ListVars() 204 | case this.pause: this.ToggleSuspend() 205 | case this.restart: Reload() 206 | case this.search: Anyrun() 207 | 208 | case this.viewWinId: Run("extra/WindowSpyU32.exe") 209 | case this.statistics: 210 | ; 统计软件使用总分钟数 211 | recordMins := RegRead(REG_KEY_NAME, REG_RECORD_MINS, 0) + DateDiff(A_NowUTC, START_TIME, 'Minutes') 212 | ; 统计软件使用次数 213 | launchCount := RegRead(REG_KEY_NAME, REG_LAUNCH_COUNT, 1) 214 | 215 | if recordMins < 10000 216 | tit := '青铜' 217 | else if recordMins < 20000 218 | tit := '白银' 219 | else if recordMins < 40000 220 | tit := '黄金' 221 | else if recordMins < 80000 222 | tit := '铂金' 223 | else if recordMins < 160000 224 | tit := '钻石' 225 | else 226 | tit := '传说' 227 | sb := '尊敬的' . tit . '用户:`n' . '  您当前总启动次数 ' . launchCount . ' 次,您目前已使用捷键 ' . recordMins . ' 分钟' 228 | if (recordMins >= 60) { 229 | sb .= '(' 230 | recordYears := recordMins // (365 * 24 * 60) 231 | if recordYears > 0 232 | sb .= recordYears . ' 年 ' 233 | recordDays := recordMins // (24 * 60) - recordYears * 365 234 | if recordDays >= 1 235 | sb .= recordDays . ' 天 ' 236 | recordHours := recordMins // 60 - recordYears * 365 * 24 - recordDays * 24 237 | if recordHours >= 1 238 | sb .= recordHours . ' 小时 ' 239 | mins := recordMins - recordMins // 60 * 60 240 | if mins >= 1 241 | sb .= mins . ' 分钟' 242 | sb .= ')' 243 | } 244 | MsgBox(sb, '捷键-使用统计') 245 | case this.startUp: 246 | ; 当前是开机自启则设置为开机不自启,否则设置为开机自启 247 | if IS_AUTO_START_UP 248 | FileDelete(this.linkFile) 249 | else 250 | FileCreateShortcut(this.shortcut, this.linkFile, A_WorkingDir) 251 | A_TrayMenu.ToggleCheck(this.startUp) 252 | IS_AUTO_START_UP := !IS_AUTO_START_UP 253 | 254 | case this.document: Run 'https://acc8226.onrender.com/mypage' ; 还是选用国内服务访问最快 255 | case this.video: Run 'https://www.bilibili.com/video/BV19H4y1e7hJ' 256 | 257 | case this.followMeCSDN: Run 'https://blog.csdn.net/acc8226' 258 | case this.softwareHomepage: Run 'https://github.com/acc8226/jiejian' 259 | 260 | case this.enableDarkMode: 261 | RegWrite(ENABLE_DARK_MODE ? false : true, "REG_DWORD", REG_KEY_NAME, REG_DARK_MODE) 262 | this.moreMenu.ToggleCheck this.enableDarkMode 263 | ENABLE_DARK_MODE := !ENABLE_DARK_MODE 264 | WindowsTheme.SetAppMode ENABLE_DARK_MODE 265 | 266 | case this.enableTimerReminder: 267 | if ENABLE_TIMER_REMINDER { 268 | RegWrite(0, "REG_DWORD", REG_KEY_NAME, REG_RELAX_REMINDER) 269 | this.counter.Stop 270 | } else { 271 | this.counter.start 272 | RegWrite(1, "REG_DWORD", REG_KEY_NAME, REG_RELAX_REMINDER) 273 | } 274 | this.moreMenu.ToggleCheck(this.enableTimerReminder) 275 | ENABLE_TIMER_REMINDER := !ENABLE_TIMER_REMINDER 276 | 277 | case this.update: CheckUpdate true 278 | case this.about: this.AboutFunc() 279 | case this.exit: ExitApp() 280 | } 281 | } 282 | 283 | /** 284 | * 菜单中的暂停 285 | */ 286 | ToggleSuspend() { 287 | Suspend(!A_IsSuspended) 288 | if (A_IsSuspended) { 289 | A_TrayMenu.Check(this.pause) 290 | Tip ' 热键已禁用 ⏸️ ', -500 291 | } else { 292 | A_TrayMenu.UnCheck(this.pause) 293 | Tip ' 热键已恢复 🚀 ', -500 294 | } 295 | } 296 | 297 | /** 298 | * 快捷键用的暂停 299 | */ 300 | MySuspend() { 301 | Suspend(!A_IsSuspended) 302 | A_IsSuspended ? A_TrayMenu.Check(this.pause) : A_TrayMenu.UnCheck(this.pause) 303 | } 304 | 305 | AboutFunc() { 306 | MsgBox( 307 | '版本: ' . CODE_VERSION 308 | . "`nAHK 主程序版本: " . A_AhkVersion 309 | . "`n系统默认语言: " . this.LocalLang(A_Language) 310 | . "`nWindows " . A_OSVersion . (A_Is64bitOS ? ' 64 位' : '') 311 | . "`n计算机名: " . A_ComputerName 312 | . "`n当前用户: " . A_UserName 313 | . "`n是否管理员权限运行: " . (A_IsAdmin ? '是' : '否') 314 | . "`n是否 64 位程式: " . (A_PtrSize == 8 ? '是' : '否') 315 | , APP_NAME, 'Iconi T60') 316 | } 317 | 318 | LocalLang(language) { 319 | if language = '7804' 320 | return '中文' 321 | else if language = '0004' 322 | return '简体中文' 323 | else if language = '0804' 324 | return '简体中文(中国)' 325 | else if language = '1004' 326 | return '简体中文(新加坡)' 327 | else if language = '7C04' 328 | return '繁体中文' 329 | else if language = '0C04' 330 | return '繁体中文(香港特别行政区)' 331 | else if language = '1404' 332 | return '繁体中文(澳门特别行政区)' 333 | else if language = '0404' 334 | return '繁体中文(台湾)' 335 | LCID := Integer('0x' . language) 336 | LOCALE_ALLOW_NEUTRAL_NAMES := 0x08000000 337 | LOCALE_SENGLISHDISPLAYNAME := 0x72 338 | LocaleName := this.LCIDToLocaleName(LCID, LOCALE_ALLOW_NEUTRAL_NAMES) 339 | if LocaleName { 340 | DisplayName := this.getLocaleInfo(LocaleName, LOCALE_SENGLISHDISPLAYNAME) 341 | } else { 342 | DisplayName := 'unknown' 343 | } 344 | return DisplayName 345 | } 346 | 347 | LCIDToLocaleName(LCID, Flags := 0) { 348 | reqBufSize := DllCall("LCIDToLocaleName", "UInt", LCID, "Ptr", 0, "UInt", 0, "UInt", Flags) 349 | out := Buffer(reqBufSize * 2) 350 | DllCall("LCIDToLocaleName", "UInt", LCID, "Ptr", out, "UInt", out.Size, "UInt", Flags) 351 | return StrGet(out) 352 | } 353 | 354 | getLocaleInfo(LocaleName, LCType) { 355 | reqBufSize := DllCall("GetLocaleInfoEx", "Str", LocaleName, "UInt", LCType, "Ptr", 0, "UInt", 0) 356 | out := Buffer(reqBufSize * 2) 357 | DllCall("GetLocaleInfoEx", "Str", LocaleName, "UInt", LCType, "Ptr", out, "UInt", out.Size) 358 | return StrGet(out) 359 | } 360 | } 361 | 362 | initLanguage() { 363 | if !FileExist(A_ScriptDir . '\' . 'lang') 364 | DirCreate(A_ScriptDir . '\' . 'lang') 365 | if !FileExist(A_ScriptDir . '\' . 'tools') 366 | DirCreate(A_ScriptDir . '\' . 'tools') 367 | 368 | ; 在已编译的脚本中包含指定的文件 369 | if (A_IsCompiled) { 370 | ; 要添加到已编译可执行文件中的文件名. 如果没有指定绝对路径, 则假定该文件位于(或相对于) 脚本自己的目录中 371 | FileInstall 'lang\ar.ini', 'lang\ar.ini', true 372 | FileInstall 'lang\de.ini', 'lang\de.ini', true 373 | FileInstall 'lang\en.ini', 'lang\en.ini', true 374 | FileInstall 'lang\es.ini', 'lang\es.ini', true 375 | FileInstall 'lang\fr.ini', 'lang\fr.ini', true 376 | FileInstall 'lang\it.ini', 'lang\it.ini', true 377 | FileInstall 'lang\ja.ini', 'lang\ja.ini', true 378 | FileInstall 'lang\ko.ini', 'lang\ko.ini', true 379 | FileInstall 'lang\pt.ini', 'lang\pt.ini', true 380 | FileInstall 'lang\ru.ini', 'lang\ru.ini', true 381 | FileInstall 'lang\tr.ini', 'lang\tr.ini', true 382 | ; 简体中文为主 383 | FileInstall 'lang\zh-Hans.ini', 'lang\zh-Hans.ini', true 384 | FileInstall 'lang\zh-Hant.ini', 'lang\zh-Hant.ini', true 385 | 386 | ; 配置文件 387 | FileInstall 'setting.ini', 'setting.ini', true 388 | 389 | ; 鼠标控制 只是加载,目前不在 anyrun 组件中体现 390 | FileInstall 'tools\MouseSC_x64.exe', 'tools\MouseSC_x64.exe', true 391 | FileInstall 'tools\MouseSC_Query.bat', 'tools\MouseSC_Query.bat', true 392 | 393 | ; 重建图标缓存 https://www.sordum.org/9194/rebuild-shell-icon-cache-v1-3/ 394 | FileInstall 'tools\ReIconCache_x64.exe', 'tools\ReIconCache_x64.exe', true 395 | ; Rexplorer_x64 用于重启文件资源管理器 396 | FileInstall 'tools\Rexplorer_x64.exe', 'tools\Rexplorer_x64.exe', true 397 | 398 | ; https://www.sordum.org/16219 399 | FileInstall 'tools\SkipUAC.ini', 'tools\SkipUAC.ini', true 400 | FileInstall 'tools\SkipUAC_x64.exe', 'tools\SkipUAC_x64.exe', true 401 | 402 | ; 声音控制 403 | FileInstall 'tools\SoundControl.exe', 'tools\SoundControl.exe', true 404 | 405 | ; Windows 11 Classic Context Menu 406 | if (VerCompare(A_OSVersion, "10.0.20000") > 0) { 407 | FileInstall 'tools\W11ClassicMenu.exe', 'tools\W11ClassicMenu.exe', true 408 | FileInstall 'tools\W11ClassicMenu.ini', 'tools\W11ClassicMenu.ini', true 409 | } 410 | 411 | ; 禁用 windows update https://www.sordum.org/9470/windows-update-blocker-v1-8/ 412 | FileInstall 'tools\Wub.ini', 'tools\Wub.ini', true 413 | FileInstall 'tools\Wub_x64.exe', 'tools\Wub_x64.exe', true 414 | } 415 | } 416 | 417 | ; 一个记录秒数的示例类 418 | class RelaxCounter { 419 | __New() { 420 | ; 每小时提醒 1 次 421 | this.interval := 3600000 422 | ; Tick() 有一个隐式参数 "this", 其引用一个对象。所以, 我们需要创建一个封装了 "this " 和调用方法的函数 423 | this.timer := ObjBindMethod(this, "Tick") 424 | } 425 | 426 | Start() { 427 | SetTimer this.timer, this.interval 428 | } 429 | 430 | Stop() { 431 | ; 要关闭计时器, 我们必须传递和之前一样的对象 432 | SetTimer this.timer, 0 433 | } 434 | 435 | ; 本例中, 计时器调用了以下方法: 436 | Tick() { 437 | MyGui := Gui('-Caption +AlwaysOnTop +ToolWindow') 438 | MyGui.SetFont("c1A9F55 s15", 'Consolas') 439 | MyGui.SetFont("c1A9F55 s15", 'Microsoft YaHei') 440 | MyGui.BackColor := "030704" ; 可以是任何 RGB 颜色(下面会变成透明的) 441 | guiWidth := 420 442 | progressBarPaddingLeft := 72 443 | textGUI1 := MyGui.AddText('w' . (guiWidth - MyGui.MarginX * 2) . ' Center', '休息提醒(当前) ' . FormatTime(, 'HH:mm') . '`n下次提醒时间   ' . FormatTime(DateAdd(A_Now, 60, "Minutes"), 'HH:mm')) 444 | MyGui.AddProgress("XM" . progressBarPaddingLeft . " w" . (guiWidth - (MyGui.MarginX + progressBarPaddingLeft) * 2) . " h23 c1A9F55 vMyProgress") 445 | ; 当窗口处于最小化或最大化状态时, 还原窗口. 窗口显示但不进行激活. 446 | MyGui.Show 'NoActivate W' . guiWidth . ' H116' 447 | 448 | loop { 449 | if (MyGui["MyProgress"].Value >= 100) { 450 | ; 消失前短暂停留 451 | Sleep 300 452 | MyGui.Destroy 453 | break 454 | } 455 | ; 每 1 秒,进度增长 10% =(100/10) 456 | Sleep 1000 457 | MyGui["MyProgress"].Value += (100/60) ; 进度增长 458 | } 459 | } 460 | } 461 | -------------------------------------------------------------------------------- /modules/ReadApp.ahk: -------------------------------------------------------------------------------- 1 | ParseAppCSV() 2 | ParseAppCSV() { 3 | applist := parseApp('app.csv') 4 | ; 注册热键 和 热字符串 5 | Loop applist.Length { 6 | if A_Index <= 1 7 | continue 8 | 9 | it := applist[A_Index] 10 | if (it.highLevel) { 11 | ; 高优先级 12 | ; e 列 关闭 13 | switch it.close, 'Off' { 14 | case "Esc", "{Esc}": GroupAdd('HL_close_esc', it.exe) 15 | case "!F4", "!{F4}": GroupAdd('HL_close_alt_F4', it.exe) 16 | case "^F4", "^{F4}": GroupAdd('HL_close_ctrl_F4', it.exe) 17 | } 18 | } else { 19 | ; 低优先级 20 | ; d 列 新建 21 | switch it.new, 'Off' { 22 | case "F3", "{F3}": GroupAdd("new_F3", it.exe) 23 | case 'F8', "{F8}": GroupAdd("new_F8", it.exe) 24 | case 'L', : GroupAdd("new_L", it.exe) 25 | case 'O', : GroupAdd("new_O", it.exe) 26 | case "!a": GroupAdd("new_alt_a", it.exe) 27 | 28 | case "!c": GroupAdd("new_alt_c", it.exe) 29 | case "!n": GroupAdd("new_alt_n", it.exe) 30 | case "!o": GroupAdd("new_alt_o", it.exe) 31 | case "^n": GroupAdd("new_ctrl_n", it.exe) 32 | case "^o": GroupAdd("new_ctrl_o", it.exe) 33 | 34 | case "^t": GroupAdd("new_ctrl_t", it.exe) 35 | case "^!t", "!^t": GroupAdd('new_ctrl_alt_t', it.exe) 36 | case "^+t", "+^t": GroupAdd('new_ctrl_shift_t', it.exe) 37 | case "^+n", "+^n": GroupAdd('new_ctrl_shift_n', it.exe) 38 | } 39 | ; e 列 关闭 和 鼠标后退键 40 | switch it.close, 'Off' { 41 | case "WinClose", '关闭': GroupAdd("close_WinClose", it.exe) 42 | case "Esc", "{Esc}": GroupAdd("close_Esc", it.exe) 43 | case "]": GroupAdd("close_closeBracket", it.exe) 44 | case '!F4', "!{F4}": GroupAdd("close_alt_F4", it.exe) 45 | case "!l": GroupAdd("close_alt_L", it.exe) 46 | 47 | case "!q": GroupAdd("close_alt_q", it.exe) 48 | case "!w": GroupAdd("close_alt_w", it.exe) 49 | case "^c": GroupAdd("close_ctrl_c", it.exe) 50 | case "^v": GroupAdd("close_ctrl_v", it.exe) 51 | case "^w": GroupAdd("close_ctrl_w", it.exe) 52 | case "^!q", "!^q": GroupAdd("close_ctrl_alt_q", it.exe) 53 | case "^+w", "+^w": GroupAdd("close_ctrl_shift_w", it.exe) 54 | ; ctrl + f4 将做特殊处理 55 | case "^F4", "^{F4}": GroupAdd("close_ctrl_F4", it.exe) 56 | } 57 | ; f 列 前进 58 | switch it.forward, 'Off' { 59 | case "Media_Next", "{Media_Next}": GroupAdd "forward_MediaNext", it.exe 60 | case "PgDn", "{PgDn}": GroupAdd "forward_PgDn", it.exe 61 | case "Right", "{Right}": GroupAdd "forward_Right", it.exe 62 | case "b": GroupAdd "forward_b", it.exe 63 | case "f": GroupAdd "forward_f", it.exe 64 | case "n": GroupAdd "forward_n", it.exe 65 | 66 | case "z": GroupAdd("forward_z", it.exe) 67 | case "^Right", "^{Right}": GroupAdd("forward_ctrl_Right", it.exe) 68 | case "^Tab", "^{Tab}": GroupAdd("forward_ctrl_Tab", it.exe) 69 | case '^]': GroupAdd("forward_ctrl_closeBracket", it.exe) 70 | case "^n" : GroupAdd("forward_ctrl_n", it.exe) 71 | 72 | case "^!Right", "^!{Right}", "!^Right", "!^{Right}": GroupAdd("forward_ctrl_alt_Right", it.exe) 73 | case "^+Right", "^+{Right}", "+^Right", "+^{Right}": GroupAdd("forward_ctrl_shift_Right", it.exe) 74 | case "^+f", "+^f": GroupAdd("forward_ctrl_shift_f", it.exe) 75 | } 76 | ; g 列 下个标签 77 | switch it.nextTag, 'Off' { 78 | case ']': GroupAdd("next_closeBracket", it.exe) 79 | case 'Down', "{Down}": GroupAdd("next_Down", it.exe) 80 | case 'Media_Next', "{Media_Next}": GroupAdd("next_MediaNext", it.exe) 81 | case 'PgDn', '{PgDn}': GroupAdd("next_PgDn", it.exe) 82 | case "b": GroupAdd("next_b", it.exe) 83 | 84 | case "f": GroupAdd('next_f', it.exe) 85 | case "n": GroupAdd('next_n', it.exe) 86 | case "z": GroupAdd('next_z', it.exe) 87 | case "!]": GroupAdd('next_alt_closeBracket', it.exe) 88 | case "^]": GroupAdd('next_ctrl_closeBracket', it.exe) 89 | case "!Right", "!{Right}": GroupAdd('next_alt_Right', it.exe) 90 | 91 | case "^PgDn", "^{PgDn}": GroupAdd('next_ctrl_PgDn', it.exe) 92 | case "^Right", "^{Right}": GroupAdd("next_ctrl_Right", it.exe) 93 | case "^f": GroupAdd("next_ctrl_f", it.exe) 94 | case "^n": GroupAdd("next_ctrl_n", it.exe) 95 | case "^!PgDn", "^!{PgDn}", "!^PgDn", "!^{PgDn}": GroupAdd("next_ctrl_alt_PgDn", it.exe) 96 | 97 | case "^!Right", "^!{Right}", "!^Right", "!^{Right}": GroupAdd("next_ctrl_alt_Right", it.exe) 98 | case "^+Right", "^+{Right}", "+^Right", "+^{Right}": GroupAdd("next_ctrl_shift_Right", it.exe) 99 | } 100 | ; h 列 后退 101 | switch it.back, 'Off' { 102 | case 'BackSpace', "{BackSpace}", "退格": GroupAdd("back_BackSpace", it.exe) 103 | case 'Left', "{Left}": GroupAdd("back_Left", it.exe) 104 | case 'Media_Prev', "{Media_Prev}": GroupAdd("back_MediaPrev", it.exe) 105 | case 'PgUp', "{PgUp}": GroupAdd("back_PgUp", it.exe) 106 | case 'Up', "{Up}": GroupAdd("back_Up", it.exe) 107 | 108 | case "s": GroupAdd('back_s', it.exe) 109 | case "v": GroupAdd('back_v', it.exe) 110 | case "z": GroupAdd('back_z', it.exe) 111 | 112 | case "^[": GroupAdd('back_ctrl_openBracket', it.exe) 113 | case "^Left", "^{Left}": GroupAdd('back_ctrl_Left', it.exe) 114 | case "^b": GroupAdd('back_ctrl_b', it.exe) 115 | 116 | case "^!Left", "^!{Left}", "!^Left", "!^{Left}": GroupAdd "back_ctrl_alt_Left", it.exe 117 | case "^+b", "+^b": GroupAdd('back_ctrl_shift_b', it.exe) 118 | case "^+Tab", "^+{Tab}", "+^Tab", "+^{Tab}": GroupAdd "back_ctrl_shift_Tab", it.exe 119 | case "^+Left", "^+{Left}", "+^Left", "+^{Left}": GroupAdd "back_ctrl_shift_Left", it.exe 120 | } 121 | ; I 列 上个标签 和 前进键 122 | switch it.previousTag, 'Off' { 123 | case 'Left', "{Left}": GroupAdd("previous_Left", it.exe) 124 | 125 | case "p": GroupAdd('previous_p', it.exe) 126 | case "s": GroupAdd('previous_s', it.exe) 127 | case "v": GroupAdd('previous_v', it.exe) 128 | case "z": GroupAdd('previous_z', it.exe) 129 | 130 | case "[": GroupAdd('previous_openBracket', it.exe) 131 | case 'Media_Play_Pause', "{Media_Play_Pause}": GroupAdd("previous_MediaPlayPause", it.exe) 132 | case 'Media_Prev', "{Media_Prev}": GroupAdd("previous_MediaPrev", it.exe) 133 | 134 | case 'PgUp', "{PgUp}": GroupAdd('previous_PgUp', it.exe) 135 | case 'Space', "{Space}", "空格": GroupAdd('previous_Space', it.exe) 136 | case 'Up', "{Up}": GroupAdd('previous_Up', it.exe) 137 | case "!0": GroupAdd('previous_alt_0', it.exe) 138 | 139 | case "![": GroupAdd('previous_alt_openBracket', it.exe) 140 | case "!Left", "!{Left}": GroupAdd('previous_alt_Left', it.exe) 141 | case "^[": GroupAdd('previous_ctrl_openBracket', it.exe) 142 | case "^Left", "^{Left}": GroupAdd('previous_ctrl_Left', it.exe) 143 | case "^PgUp", "^{PgUp}": GroupAdd('previous_ctrl_PgUp', it.exe) 144 | 145 | case "^c": GroupAdd('previous_ctrl_c', it.exe) 146 | case "^v": GroupAdd('previous_ctrl_v', it.exe) 147 | case "^b": GroupAdd('previous_ctrl_b', it.exe) 148 | case "^!Left", "^!{Left}", "!^Left", "!^{Left}": GroupAdd("previous_ctrl_alt_Left", it.exe) 149 | case "^+Left", "^+{Left}", "+^Left", "+^{Left}": GroupAdd("previous_ctrl_shift_Left", it.exe) 150 | } 151 | ; J 列 新建窗口 152 | switch it.newWin, 'Off' { 153 | case "^n": GroupAdd("newWin_ctrl_n", it.exe) 154 | case "^!n", "!^n": GroupAdd("newWin_ctrl_alt_n", it.exe) 155 | case "^+n", "+^n": GroupAdd("newWin_ctrl_shift_n", it.exe) 156 | } 157 | ; K 列 全屏 158 | switch it.fs, 'Off' { 159 | case "DoubleClick", "双击": GroupAdd("fullscreen_DoubleClick", it.exe) 160 | case "Enter", "{Enter}", "回车": GroupAdd("fullscreen_Enter", it.exe) 161 | case "f": GroupAdd("fullscreen_f", it.exe) 162 | case "!f": GroupAdd("fullscreen_alt_f", it.exe) 163 | case "!Enter", "!{Enter}": GroupAdd("fullscreen_alt_Enter", it.exe) 164 | 165 | case "^+F12", "^+{F12}", "+^F12", "+^{F12}": GroupAdd("fullscreen_ctrl_shift_F12", it.exe) 166 | } 167 | } 168 | } 169 | } 170 | 171 | parseApp(fileName) { 172 | appList := [] 173 | eachLineLen := 12 174 | ; 每次从字符串中检索字符串(片段) 175 | Loop Parse, FileRead(fileName), "`n", "`r" { 176 | ; 跳过首行 177 | if (A_Index >= 2) { 178 | appInfo := parseAppLine(A_LoopField, eachLineLen) 179 | if appInfo 180 | appList.Push(appInfo) 181 | } 182 | } 183 | return appList 184 | } 185 | 186 | parseAppLine(line, eachLineLen) { 187 | split := StrSplit(line, ",") 188 | ; 跳过不符合条件的行 189 | if split.Length < eachLineLen 190 | return 191 | splitEachLineLen := Trim(split[eachLineLen]) 192 | ; 过滤不启用的行 193 | if NOT (splitEachLineLen == '' || splitEachLineLen = 'y' || splitEachLineLen == '是') 194 | return 195 | 196 | info := {} 197 | ; 跳过 exe 为空的行 198 | info.exe := Trim(split[3]) 199 | if info.exe = '' 200 | return 201 | ; 判断是否是高等级 202 | info.highLevel := split[2] == "高" 203 | 204 | info.new := Trim(split[4]) 205 | info.close := Trim(split[5]) 206 | 207 | info.forward := Trim(split[6]) 208 | info.nextTag := Trim(split[7]) 209 | info.back := Trim(split[8]) 210 | info.previousTag := Trim(split[9]) 211 | info.newWin := Trim(split[10]) 212 | 213 | info.fs := Trim(split[11]) 214 | 215 | ; 过滤空行 216 | if (info.new == '' 217 | && info.close == '' 218 | 219 | && info.forward == '' 220 | && info.nextTag == '' 221 | && info.back == '' 222 | && info.previousTag == '' 223 | && info.fs == '' 224 | 225 | && info.newWin == '' 226 | ) { 227 | return 228 | } 229 | 230 | name := Trim(split[1]) 231 | if name !== '' and info.exe !== '' and 1 == InStr(name, '【浏览器】') 232 | GroupAdd("browser_group", info.exe) 233 | return info 234 | } 235 | 236 | ; 高等级 237 | ; e. 关闭 打头 238 | #HotIf WinActive('ahk_group HL_close_esc') 239 | F13:: 240 | XButton1::Send '{Esc}' 241 | #HotIf WinActive('ahk_group HL_close_alt_F4') 242 | F13:: 243 | XButton1::Send "!{F4}" 244 | ; 主要为窗口服务,若遇到 ctrl + f4 则必须捕获后处理,而非兜底处理 245 | #HotIf WinActive("ahk_group HL_close_ctrl_F4") 246 | F13:: 247 | XButton1::Send "^{F4}" 248 | 249 | ; 低等级 250 | ; d. 新建 251 | #HotIf WinActive("ahk_group new_F3") 252 | F14::Send "{F3}" 253 | #HotIf WinActive("ahk_group new_F8") 254 | F14::Send "{F8}" 255 | #HotIf WinActive("ahk_group new_L") 256 | F14::Send 'l' 257 | #HotIf WinActive("ahk_group new_O") 258 | F14::Send 'o' 259 | #HotIf WinActive("ahk_group new_alt_a") 260 | F14::Send '!a' 261 | 262 | #HotIf WinActive("ahk_group new_alt_c") 263 | F14::Send '!c' 264 | #HotIf WinActive("ahk_group new_alt_n") 265 | F14::Send "!n" 266 | #HotIf WinActive('ahk_group new_alt_o') 267 | F14::Send "!o" 268 | #HotIf WinActive("ahk_group new_ctrl_n") 269 | F14::Send '^n' 270 | #HotIf WinActive("ahk_group new_ctrl_o") 271 | F14::Send '^o' 272 | 273 | #HotIf WinActive("ahk_group new_ctrl_alt_t") 274 | F14::Send '^!t' 275 | #HotIf WinActive("ahk_group new_ctrl_shift_t") 276 | F14::Send '^+t' 277 | #HotIf WinActive("ahk_group new_ctrl_shift_n") 278 | F14::Send '^+n' 279 | ; 新建标签还是默认 ctrl + t 280 | #HotIf 281 | F14::Send '^t' 282 | 283 | ; e. 关闭 打头 和 鼠标后退键 用 284 | #HotIf WinActive("ahk_group close_WinClose") 285 | F13:: 286 | XButton1::WinClose 287 | #HotIf WinActive("ahk_group close_Esc") 288 | F13:: 289 | XButton1::Send '{Esc}' 290 | #HotIf WinActive('ahk_group close_alt_F4') 291 | F13:: 292 | XButton1::Send '!{F4}' 293 | #HotIf WinActive("ahk_group close_closeBracket") 294 | F13:: 295 | XButton1::Send ']' 296 | #HotIf WinActive("ahk_group close_alt_L") 297 | F13:: 298 | XButton1::Send '!l' 299 | 300 | #HotIf WinActive("ahk_group close_alt_q") 301 | F13:: 302 | XButton1::Send '!q' 303 | #HotIf WinActive("ahk_group close_alt_w") 304 | F13:: 305 | XButton1::Send '!w' 306 | #HotIf WinActive("ahk_group close_ctrl_c") 307 | F13:: 308 | XButton1::Send '^c' 309 | #HotIf WinActive("ahk_group close_ctrl_v") 310 | F13:: 311 | XButton1::Send '^v' 312 | #HotIf WinActive("ahk_group close_ctrl_w") 313 | F13:: 314 | XButton1::Send '^w' 315 | #HotIf WinActive('ahk_group close_ctrl_alt_q') 316 | F13:: 317 | XButton1::Send '^!q' 318 | #HotIf WinActive('ahk_group close_ctrl_shift_w') 319 | F13:: 320 | XButton1::Send '^+w' 321 | 322 | ; 如果填写的不是 ctrl + f4 则采取兜底处理:智能关闭 323 | #HotIf !WinActive('ahk_group close_ctrl_F4') 324 | F13:: 325 | XButton1::SmartCloseWindow() ; 比 WinClose "A" 好使 326 | ; 否则走到此步必定是 ctrl + f4。则凡是遇到【#32770 窗口】则统一使用 esc 关闭窗口 327 | #HotIf WinActive('ahk_class #32770') 328 | F13:: 329 | XButton1::Send '{Esc}' 330 | ; 最终则是 ctrl + f4 且非窗口则 ctrl + f4 331 | ; XButton1 兜底 332 | #HotIf 333 | F13:: 334 | XButton1::Send '^{F4}' 335 | 336 | ; f. 前进键 337 | #HotIf WinActive('ahk_group forward_MediaNext') 338 | !Right::Send "{Media_Next}" ; 下一曲 339 | #HotIf WinActive("ahk_group forward_PgDn") 340 | !Right::Send "{PgDn}" 341 | #HotIf WinActive("ahk_group forward_Right") 342 | !Right::Send "{Right}" 343 | #HotIf WinActive("ahk_group forward_b") 344 | !Right::Send "b" 345 | #HotIf WinActive("ahk_group forward_f") 346 | !Right::Send "f" 347 | #HotIf WinActive("ahk_group forward_n") 348 | !Right::Send "n" 349 | 350 | #HotIf WinActive('ahk_group forward_z') 351 | !Right::Send "z" 352 | #HotIf WinActive("ahk_group forward_ctrl_Right") 353 | !Right::Send "^{Right}" 354 | #HotIf WinActive("ahk_group forward_ctrl_Tab") 355 | !Right::Send "^{Tab}" 356 | #HotIf WinActive("ahk_group forward_ctrl_closeBracket") 357 | !Right::Send "^]" 358 | #HotIf WinActive("ahk_group forward_ctrl_n") 359 | !Right::Send "^n" 360 | 361 | #HotIf WinActive("ahk_group forward_ctrl_alt_Right") 362 | !Right::Send "^!{Right}" 363 | #HotIf WinActive("ahk_group forward_ctrl_shift_Right") 364 | !Right::Send "^+{Right}" 365 | #HotIf WinActive("ahk_group forward_ctrl_shift_f") 366 | !Right::Send "^+f" 367 | 368 | ; g. 下个标签 369 | #HotIf WinActive('ahk_group next_closeBracket') 370 | ^Tab::Send "]" 371 | #HotIf WinActive("ahk_group next_Down") 372 | ^Tab::Send "{Down}" 373 | #HotIf WinActive("ahk_group next_MediaNext") 374 | ^Tab::Send "{Media_Next}" 375 | #HotIf WinActive("ahk_group next_PgDn") 376 | ^Tab::Send "{PgDn}" 377 | #HotIf WinActive('ahk_group next_b') 378 | ^Tab::Send "b" 379 | #HotIf WinActive('ahk_group next_f') 380 | ^Tab::Send "f" 381 | 382 | #HotIf WinActive('ahk_group next_n') 383 | ^Tab::Send "n" 384 | #HotIf WinActive("ahk_group next_z") 385 | ^Tab::Send "z" 386 | #HotIf WinActive("ahk_group next_alt_closeBracket") 387 | ^Tab::Send "!]" 388 | #HotIf WinActive("ahk_group next_ctrl_closeBracket") 389 | ^Tab::Send "^]" 390 | #HotIf WinActive("ahk_group next_alt_Right") 391 | ^Tab::Send "!{Right}" 392 | 393 | #HotIf WinActive("ahk_group next_ctrl_PgDn") 394 | ^Tab::Send "^{PgDn}" 395 | #HotIf WinActive("ahk_group next_ctrl_Right") 396 | ^Tab::Send "^{Right}" 397 | #HotIf WinActive("ahk_group next_ctrl_f") 398 | ^Tab::Send "^f" 399 | #HotIf WinActive("ahk_group next_ctrl_n") 400 | ^Tab::Send "^n" 401 | #HotIf WinActive("ahk_group next_ctrl_alt_PgDn") 402 | ^Tab::Send "^!{PgDn}" 403 | 404 | #HotIf WinActive("ahk_group next_ctrl_alt_Right") 405 | ^Tab::Send "^!{Right}" 406 | #HotIf WinActive("ahk_group next_ctrl_shift_Right") 407 | ^Tab::Send "^+{Right}" 408 | 409 | ; h. 后退 410 | #HotIf WinActive("ahk_group back_BackSpace") 411 | !Left::Send "{BackSpace}" 412 | #HotIf WinActive("ahk_group back_MediaPrev") 413 | !Left::Send "{Media_Prev}" ; 上一曲 414 | #HotIf WinActive("ahk_group back_PgUp") 415 | !Left::Send "{PgUp}" 416 | #HotIf WinActive("ahk_group back_Left") 417 | !Left::Send "{Left}" 418 | #HotIf WinActive("ahk_group back_Up") 419 | !Left::Send "{Up}" 420 | 421 | #HotIf WinActive("ahk_group back_s") 422 | !Left::Send "s" 423 | #HotIf WinActive("ahk_group back_v") 424 | !Left::Send "v" 425 | #HotIf WinActive("ahk_group back_z") 426 | !Left::Send "z" 427 | #HotIf WinActive("ahk_group back_ctrl_openBracket") 428 | !Left::Send "^[" 429 | #HotIf WinActive("ahk_group back_ctrl_Left") 430 | !Left::Send "^{Left}" 431 | #HotIf WinActive("ahk_group back_ctrl_b") 432 | !Left::Send "^b" 433 | #HotIf WinActive("ahk_group back_ctrl_alt_Left") 434 | !Left::Send "^!{Left}" 435 | #HotIf WinActive("ahk_group back_ctrl_shift_b") 436 | !Left::Send "^+b" 437 | #HotIf WinActive("ahk_group back_ctrl_shift_Tab") 438 | !Left::Send "^+{Tab}" 439 | #HotIf WinActive("ahk_group back_ctrl_shift_Left") 440 | !Left::Send "^+{Left}" 441 | 442 | ; I 列:上个标签 和 鼠标前进键 用 443 | #HotIf WinActive("ahk_group previous_Left") 444 | ^+Tab:: 445 | XButton2::Send '{Left}' 446 | #HotIf WinActive("ahk_group previous_p") 447 | ^+Tab:: 448 | XButton2::Send 'p' 449 | #HotIf WinActive("ahk_group previous_s") 450 | ^+Tab:: 451 | XButton2::Send 's' 452 | #HotIf WinActive("ahk_group previous_z") 453 | ^+Tab:: 454 | XButton2::Send 'z' 455 | #HotIf WinActive('ahk_group previous_openBracket') 456 | ^+Tab:: 457 | XButton2::Send "[" ; 对 bilibili 不好用,由于会触发 ctrl + shift 切换输入法我就醉了 458 | #HotIf WinActive("ahk_group previous_MediaPlayPause") 459 | ^+Tab:: 460 | XButton2::Send "{Media_Play_Pause}" 461 | #HotIf WinActive("ahk_group previous_MediaPrev") 462 | ^+Tab:: 463 | XButton2::Send "{Media_Prev}" 464 | 465 | #HotIf WinActive("ahk_group previous_PgUp") 466 | ^+Tab:: 467 | XButton2::Send '{PgUp}' 468 | #HotIf WinActive("ahk_group previous_Space") 469 | ^+Tab:: 470 | XButton2::Send "{Space}" 471 | #HotIf WinActive("ahk_group previous_Up") 472 | ^+Tab:: 473 | XButton2::Send "{Up}" 474 | #HotIf WinActive("ahk_group previous_v") 475 | ^+Tab:: 476 | XButton2::Send "v" 477 | #HotIf WinActive("ahk_group previous_alt_0") 478 | ^+Tab:: 479 | XButton2::Send "!0" 480 | 481 | #HotIf WinActive("ahk_group previous_alt_openBracket") 482 | ^+Tab:: 483 | XButton2::Send "![" 484 | #HotIf WinActive('ahk_group previous_alt_Left') 485 | ^+Tab:: 486 | XButton2::Send "!{Left}" 487 | #HotIf WinActive("ahk_group previous_ctrl_openBracket") 488 | ^+Tab:: 489 | XButton2::Send '^[' 490 | #HotIf WinActive("ahk_group previous_ctrl_Left") 491 | ^+Tab:: 492 | XButton2::Send "^{Left}" 493 | #HotIf WinActive('ahk_group previous_ctrl_PgUp') 494 | ^+Tab:: 495 | XButton2::Send "^{PgUp}" 496 | 497 | #HotIf WinActive('ahk_group previous_ctrl_c') 498 | ^+Tab:: 499 | XButton2::Send "^c" 500 | #HotIf WinActive('ahk_group previous_ctrl_v') 501 | ^+Tab:: 502 | XButton2::Send "^v" 503 | #HotIf WinActive('ahk_group previous_ctrl_b') 504 | ^+Tab:: 505 | XButton2::Send "^b" 506 | #HotIf WinActive('ahk_group previous_ctrl_alt_Left') 507 | ^+Tab:: 508 | XButton2::Send '^!{Left}' 509 | #HotIf WinActive('ahk_group previous_ctrl_shift_Left') 510 | ^+Tab:: 511 | XButton2::Send '^+{Left}' 512 | 513 | ; XButton2 兜底 514 | #HotIf 515 | XButton2::Send '^+{Tab}' 516 | 517 | ; J 列 新建窗口 518 | #HotIf WinActive('ahk_group newWin_ctrl_n') 519 | F15::Send '^n' 520 | #HotIf WinActive('ahk_group newWin_ctrl_alt_n') 521 | F15::Send '^!n' 522 | #HotIf WinActive('ahk_group newWin_ctrl_shift_n') 523 | F15::Send '^+n' 524 | 525 | ; K 列 F11 功能键增强 全屏 526 | ; 如果是浏览器 且 打开的是 bilibili 或 YouTube 则特殊处理,将 f11 转成按键 f 527 | #HotIf WinActive("(?:- YouTube -|哔哩哔哩_bilibili) ahk_group browser_group") 528 | F11::Send 'f' 529 | 530 | #HotIf WinActive('ahk_group fullscreen_DoubleClick') 531 | F11::MouseClick("left", , , 2) 532 | #HotIf WinActive('ahk_group fullscreen_Enter') 533 | F11::Send '{Enter}' 534 | #HotIf WinActive('ahk_group fullscreen_f') 535 | F11::Send 'f' 536 | #HotIf WinActive('ahk_group fullscreen_alt_f') 537 | F11::Send '!f' 538 | #HotIf WinActive('ahk_group fullscreen_alt_Enter') 539 | F11::Send '!{Enter}' 540 | #HotIf WinActive('ahk_group fullscreen_ctrl_shift_F12') 541 | F11::Send '^+{F12}' 542 | 543 | ; 增强:火狐浏览器 的 新建隐私窗口 544 | #HotIf WinActive('ahk_class ^MozillaWindowClass$') 545 | ^+n::Send '{Blind}^+p' 546 | 547 | ; 通用:置顶/取消置顶 548 | #HotIf 549 | F16::ToggleWindowTopMost 550 | -------------------------------------------------------------------------------- /modules/ReadData.ahk: -------------------------------------------------------------------------------- 1 | GLOBAL EACH_LINE_LEN := 8 2 | GLOBAL DATA_LIST := ParseData("data.csv") 3 | 4 | class DataType { 5 | static text := '文本' ; 用于热字符串替换 6 | 7 | static app := '程序' ; 精确匹配 8 | static file := '文件' ; 精确匹配 9 | static web := '网址' ; 精确匹配 10 | static dl := '下载' ; 精确匹配 11 | static inner := '内部' ; 精确匹配 12 | static ext := '外部' ; 精确匹配 13 | 14 | static action := '动作' ; 用于 bd + 关键字 15 | 16 | static d_alt := '双击Alt' ; 用于热字符串替换 17 | static d_home := '双击Home' ; 用于热字符串替换 18 | static d_end := '双击End' ; 用于热字符串替换 19 | static d_esc := '双击ESC' 20 | } 21 | 22 | ; 注册热键 和 热字符串 23 | RegHotKeyAndString() 24 | RegHotKeyAndString() { 25 | Loop DATA_LIST.Length { 26 | it := DATA_LIST[A_Index] 27 | 28 | ; 热键:目前仅作用于程序、文本 和 网址跳转。Hotkey 的规则是如果有多个变体符合触发条件, 那么仅触发最早创建的那个 29 | if StrLen(it.hk) > 0 AND StrLen(it.path) > 0 30 | Hotkey(it.hk, startByHotKey) 31 | 32 | ; 热串:web 类型 用作 网址跳转 33 | if (DataType.web = it.type AND StrLen(it.hs) > 0) { 34 | ; 排除在 编辑器中 可跳转网址 35 | Hotstring('::' . it.hs, JumlURLByHotString) 36 | ; 要关闭上下文相关性(也就是说, 使后续创建的热键和 热字串在所有窗口中工作), 调用任意 HotIf 或其中一个 HotIfWin 函数, 但省略参数. 例如: HotIf 或 HotIfWinActive 37 | } 38 | } 39 | } 40 | 41 | ; 只供内部调用 42 | ParseData(fileName) { 43 | ParseDataLine(line) { 44 | GLOBAL MY_BASH, MY_VSCode, MY_IDEA, MY_NEW_TERMINAL 45 | GLOBAL MY_DOUBLE_HOME, MY_DOUBLE_END, MY_DOUBLE_ESC 46 | 47 | split := StrSplit(line, ',') 48 | ; 跳过不符合条件的行 49 | if split.Length < EACH_LINE_LEN 50 | return 51 | splitEACH_LINE_LEN := Trim(split[EACH_LINE_LEN]) 52 | ; 过滤不启用的行 53 | if NOT (splitEACH_LINE_LEN = '' || splitEACH_LINE_LEN = 'y') 54 | return 55 | info := {} 56 | info.type := Trim(split[1]) 57 | 58 | ; 去掉首尾的双引号,但不知为何只要行内出现 " 则首尾会加入一对 "",然后里面的每个 " 都会转义为 "" 59 | ; info.path := Trim(split[2]) 60 | ; if (StrLen(info.path) > 1 && '"' == SubStr(info.path, 1, 1) && '"' == SubStr(info.path, -1)) 61 | ; info.path := SubStr(info.path, 2, -1) 62 | info.path := RegExReplace(Trim(split[2]), '"+') 63 | ; 过滤空行 64 | if info.type == '' && info.path == '' 65 | return 66 | ; 过滤无效路径 67 | if (info.type = DataType.file) { 68 | if NOT FileExist(info.path) 69 | return 70 | } else if (info.type = DataType.app) { ; 程序:精确匹配 71 | ; 如果包含竖线则进行分割,并按照从左到右进行匹配 72 | pathSplit := StrSplit(info.path, '|') 73 | isPathExist := false 74 | Loop pathSplit.Length { 75 | ; 如果能匹配 76 | item := pathSplit[A_Index] 77 | ; 过滤空行 78 | if item == '' 79 | continue 80 | ; 如果是以字母开头 并且 不包含 : 或者 :的位置为 2 则认为是文件形式则要求必须存在 81 | if (IsAlpha(SubStr(item, 1, 1)) && InStr(item, ':', false) <= 2) { 82 | ; 如果出现 C:\Users\\ 则替换当前为 UserName 用户 83 | if (InStr(item, 'C:\Users\\') == 1 ) { 84 | item := StrReplace(item, 's\\', "s\" . A_UserName . "\") 85 | } 86 | if (FileExist(item)) { 87 | ; 特殊处理 .lnk 则目标必须存在 88 | if (SubStr(item, -4) == ".lnk") { 89 | FileGetShortcut(item, &OutTarget) 90 | if (OutTarget && FileExist(OutTarget)) { 91 | isPathExist := true 92 | } 93 | } else { 94 | isPathExist := true 95 | } 96 | } else if (InStr(item, 'shortcuts\') != 1 and !InStr(item, ':')) { ; 若文件不存在 且为相对路径 97 | ; 从环境变量 PATH 中获取 98 | dosPath := EnvGet('PATH') 99 | isEndsWithExe := '.exe' = SubStr(item, StrLen(item) - 3) 100 | loop parse dosPath, "`;" { 101 | if A_LoopField == '' 102 | continue 103 | if (FileExist(A_LoopField . '\' . item)) { 104 | isPathExist := true 105 | break 106 | } 107 | ; 如果不以 exe 结尾则拼接 exe 继续尝试 108 | if (!isEndsWithExe && FileExist(A_LoopField . "\" . item . '.exe')) { 109 | isPathExist := true 110 | break 111 | } 112 | } 113 | } 114 | } else { ; 否则认为该项扫描通过 115 | isPathExist := true 116 | } 117 | if (isPathExist) { 118 | info.path := item 119 | break 120 | } 121 | } 122 | ; 若文件路径找不到则跳过该条目 123 | if NOT isPathExist 124 | return 125 | } else if (info.type = DataType.web || info.type = DataType.dl) { 126 | ; 为节约内存。若以 https 开头则默认去掉 127 | if InStr(info.path, 'https://') 128 | info.path := SubStr(info.path, StrLen('https://') + 1) 129 | } 130 | 131 | ; 热串关键字 132 | info.hs := Trim(split[7]) 133 | if (info.type = DataType.text) { 134 | ; text 类型用完即走 不加入 array 中 135 | if (StrLen(info.hs) > 0) { 136 | if IS_Crypt_String(info.path) { 137 | Hotstring('::' . info.hs, XOR_Crypt(SubStr(info.path, 2))) 138 | } else { 139 | Hotstring('::' . info.hs, info.path) 140 | } 141 | } 142 | return 143 | } 144 | 145 | ; 运行名称:可能是息屏、睡眠、关机 146 | info.title := Trim(split[4]) 147 | 148 | if (info.type = DataType.d_home) { 149 | MY_DOUBLE_HOME := info.title 150 | return 151 | } 152 | if (info.type = DataType.d_end) { 153 | MY_DOUBLE_END := info.title 154 | return 155 | } 156 | if (info.type = DataType.d_esc) { 157 | MY_DOUBLE_ESC := info.title 158 | return 159 | } 160 | 161 | ; 要激活的窗口 162 | info.winTitle := Trim(split[3]) 163 | ; 运行关键字 164 | split4 := Trim(split[5]) 165 | 166 | aliases := StrSplit(split4, "|") 167 | ; 如果数组长度 > 1 则存成数组 168 | info.alias := (aliases.Length > 1) ? aliases : split4 169 | ; 热键关键字 170 | info.hk := Trim(split[6]) 171 | 172 | ; 设置 bash 全局变量,如果存在的话,最终会供给启动器使用 173 | if (info.type = DataType.app) { 174 | if (info.title = 'Bash') { 175 | if (InStr(info.path, '.lnk')) { 176 | FileGetShortcut(info.path, &OutTarget) 177 | MY_BASH := OutTarget 178 | } else { 179 | MY_BASH := info.path 180 | } 181 | } else if (info.title = 'VSCode') { 182 | if (InStr(info.path, '.lnk')) { 183 | FileGetShortcut(info.path, &OutTarget) 184 | MY_VSCode := OutTarget 185 | } else { 186 | MY_VSCode := info.path 187 | } 188 | } else if (info.title = 'IDEA') { 189 | if (InStr(info.path, '.lnk')) { 190 | FileGetShortcut(info.path, &OutTarget) 191 | MY_IDEA := OutTarget 192 | } else { 193 | MY_IDEA := info.path 194 | } 195 | } else if (info.title = '新终端') { 196 | if (InStr(info.path, '.lnk')) { 197 | FileGetShortcut(info.path, &OutTarget) 198 | MY_NEW_TERMINAL := OutTarget 199 | } else { 200 | MY_NEW_TERMINAL := info.path 201 | } 202 | } 203 | } 204 | return info 205 | } 206 | 207 | dataList := [] 208 | ; 每次从字符串中检索字符串(片段) 209 | Loop Parse, FileRead(fileName), '`n', '`r' { 210 | ; 跳过首行 211 | if A_Index = 1 212 | continue 213 | appInfo := ParseDataLine(A_LoopField) 214 | if appInfo 215 | dataList.Push(appInfo) 216 | } 217 | return dataList 218 | } 219 | -------------------------------------------------------------------------------- /modules/Sort.ahk: -------------------------------------------------------------------------------- 1 | ; 交换数组两元素 2 | Swap(arr, i, j) { 3 | temp := arr[i] 4 | arr[i] := arr[j] 5 | arr[j] := temp 6 | } 7 | 8 | ; 冒泡排序,从大到小 属于稳定排序 9 | DataArraySort(dataArray) { 10 | ; 对 listBoxData 进行冒泡排序 11 | i := 0 12 | while (i < dataArray.Length - 1) { 13 | flag := false 14 | j := 0 15 | while (j < dataArray.Length - 1 - i) { 16 | if (dataArrayCompare(dataArray[j + 1], dataArray[j + 2]) < 0) { 17 | swap(dataArray, j + 1, j + 2) 18 | flag := true 19 | } 20 | j++ 21 | } 22 | if NOT flag 23 | break 24 | i++ 25 | } 26 | } 27 | 28 | /** 29 | * 比较大小,如果第一个比较则返回负数,否则返回 0 相等 或者 1 表示第 2 个数较大 30 | * 31 | * @param it1 32 | * @param it2 33 | * @returns {number} 34 | */ 35 | DataArrayCompare(it1, it2) { 36 | ; 排序按照 degree 降序、title 升序、type 升序 37 | result := it1.degree - it2.degree 38 | if (result == 0) { 39 | result := StrCompare(it2.title, it1.title) 40 | if result == 0 41 | result := StrCompare(it2.type, it1.type) 42 | } 43 | return result 44 | } 45 | 46 | ; 提取名称 47 | ListBoxData(dataArray) { 48 | retArray := Array() 49 | for it in dataArray 50 | ; 根据 title 添加 “-类型” 的后缀 51 | retArray.push(it.title . '-' . it.type) 52 | return retArray 53 | } 54 | -------------------------------------------------------------------------------- /modules/Utils.ahk: -------------------------------------------------------------------------------- 1 | ; 根据显示内容反查 item,例如在 anyrun 组件中 b站-网址,其中 title 在前,type 在后 2 | FindItemByTypeAndTitle(type, title) { 3 | if StrLen(title) > 0 4 | for it in DATA_LIST 5 | ; 两个条件的匹配更精确 6 | if title = it.title && type = it.type 7 | return it 8 | } 9 | 10 | ; 热键启动 11 | StartByHotKey(hotkey) { 12 | for it in DATA_LIST 13 | if (it.hk == hotkey) { 14 | ; 如果是打开 web 或者 file 类型则会有友好提示 15 | if (it.type = DataType.web) { 16 | jumpURL(it.path) 17 | Tip('打开 ' . it.title) 18 | } else if (it.type = DataType.file) { 19 | Run(it.path) 20 | Tip('打开 ' . it.title) 21 | } else if (it.type = DataType.app) { 22 | ; 如果是 app 类型会频繁唤醒则不加提示 23 | ActivateOrRun(it.winTitle, it.path) 24 | } 25 | break 26 | } 27 | } 28 | 29 | ; 热串启动网址 30 | JumlURLByHotString(hotstring) { 31 | myHs := StrReplace(hotstring, '::') 32 | for it in DATA_LIST 33 | if (it.hs == myHs) { 34 | if (it.type = DataType.web) { 35 | JumpURL(it.path) 36 | Tip('打开 ' . it.title) 37 | } 38 | break 39 | } 40 | } 41 | 42 | ; 打开网址 43 | JumpURL(input) { 44 | if (input ~= '^[^\/]*[一-龥]+.*') { 45 | MsgBox '禁止访问中文域名,谨防诈骗' 46 | return 47 | } 48 | ; https 将逐渐替代 http,这是大势所趋 49 | if NOT InStr(input, 'http') 50 | input := ('https://' . input) 51 | Run input 52 | } 53 | 54 | ; 获取屏幕高度 55 | ;ScreenHeight := A_ScreenHeight 56 | ; 获取任务栏高度 57 | ;WinGetPos , , , &TaskbarHeight, "ahk_class Shell_TrayWnd" 58 | -------------------------------------------------------------------------------- /modules/WindowShading.ahk: -------------------------------------------------------------------------------- 1 | ; Window Shading (based on the v1 script by Rajat) 2 | ; https://www.autohotkey.com 3 | ; This script reduces a window to its title bar and then back to its 4 | ; original size by pressing a single hotkey. Any number of windows 5 | ; can be reduced in this fashion (the script remembers each). If the 6 | ; script exits for any reason, all "rolled up" windows will be 7 | ; automatically restored to their original heights. 8 | 9 | global ShadingWinMap := Map() 10 | 11 | ShadingWindows() { 12 | ; Uncomment this next line if this subroutine is to be converted 13 | ; into a custom menu item rather than a hotkey. The delay allows 14 | ; the active window that was deactivated by the displayed menu to 15 | ; become active again: 16 | ;Sleep 200 17 | ActiveID := WinGetID('A') 18 | Height := ShadingWinMap.Get(ActiveID, false) 19 | if (Height) { 20 | WinMove(,,, Height, ActiveID) 21 | ShadingWinMap.Delete(ActiveID) 22 | } else { 23 | WinGetPos(,,, &Height, 'A') 24 | WinMove(,,, 25, ActiveID) 25 | ShadingWinMap[ActiveID] := Height 26 | } 27 | } 28 | 29 | RestoreWindows() { 30 | for ID, Height in ShadingWinMap 31 | WinMove(,,, Height, ID) 32 | } 33 | -------------------------------------------------------------------------------- /modules/Windows.ahk: -------------------------------------------------------------------------------- 1 | !x::{ 2 | WinGetPos ,, &Width, &Height, 'A' 3 | WinMove(0, 0, A_ScreenWidth/2,A_ScreenHeight/2, 'A') 4 | } 5 | 6 | !y::{ 7 | WinGetPos ,, &Width, &Height, 'A' 8 | WinMove(A_ScreenWidth/2, 0, A_ScreenWidth/2,A_ScreenHeight/2, 'A') 9 | } 10 | 11 | ; 判断当前光标是否在 edit 控件上 12 | !z::{ 13 | FocusedHwnd := ControlGetFocus('A') 14 | FocusedClassNN := ControlGetClassNN(FocusedHwnd) 15 | MsgBox('Control with focus = {Hwnd: ' FocusedHwnd ', ClassNN: "' FocusedClassNN '"}') 16 | } 17 | -------------------------------------------------------------------------------- /modules/debug/CaretGetPos.ahk: -------------------------------------------------------------------------------- 1 | SetTimer WatchCaret, 150 2 | ; 查看光标是否在编辑框上 3 | WatchCaret() { 4 | if CaretGetPos(&x, &y) 5 | ToolTip "X" x " Y" y, x, y - 20 6 | else 7 | ToolTip "No caret" 8 | } 9 | -------------------------------------------------------------------------------- /modules/debug/GetAll.ahk: -------------------------------------------------------------------------------- 1 | CoordMode "Mouse", "Screen" ; 坐标相对于桌面(整个屏幕) 2 | 3 | SetTimer WatchCursor, 150 4 | WatchCursor() { 5 | title := WinGetTitle("A") 6 | class := WinGetClass("A") 7 | pid := WinGetPID("A") 8 | id := WinGetID("A") 9 | processName := WinGetProcessName("A") 10 | 11 | MouseGetPos &x, &y, &id, &control 12 | 13 | ToolTip 14 | ( 15 | "title = " title 16 | "`nahk_class = " class 17 | "`nahk_exe = " processName 18 | "`nahk_pid = " pid 19 | "`nahk_id = " id 20 | 21 | "`n" 22 | 23 | "`nx " x 24 | "`ny " y 25 | "`nid " id 26 | "`ncontrol " control 27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /modules/debug/MouseGetPos.ahk: -------------------------------------------------------------------------------- 1 | CoordMode "Mouse", "Screen" ; 坐标相对于桌面(整个屏幕) 2 | 3 | SetTimer WatchCursor, 150 4 | WatchCursor() { 5 | MouseGetPos &x, &y, &id, &control 6 | ToolTip 7 | ( 8 | "x " x 9 | "`ny " y 10 | "`nid " id 11 | "`ncontrol " control 12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /modules/debug/WinGet.ahk: -------------------------------------------------------------------------------- 1 | CoordMode "Mouse", "Screen" ; 坐标相对于桌面(整个屏幕) 2 | 3 | SetTimer WatchCursor, 150 4 | WatchCursor() { 5 | title := WinGetTitle("A") 6 | class := WinGetClass("A") 7 | pid := WinGetPID("A") 8 | id := WinGetID("A") 9 | processName := WinGetProcessName("A") 10 | ToolTip 11 | ( 12 | "title = " title 13 | "`nahk_class = " class 14 | "`nahk_exe = " processName 15 | "`nahk_pid = " pid 16 | "`nahk_id = " id 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /package/7zr.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/package/7zr.exe -------------------------------------------------------------------------------- /package/JiejianPostExec.ahk: -------------------------------------------------------------------------------- 1 | ; 注意:每次修改版本后记得要重新生成 exe 到根目录 2 | ; 写入版本信息 3 | if (A_Args.Length = 0) { 4 | MsgBox '写入版本信息失败,参数个数为 0' 5 | return 6 | } 7 | 8 | version := A_Args[1] 9 | localIsAlphaOrBeta := InStr(version, "alpha") || InStr(version, "beta") 10 | fileObj := FileOpen(localIsAlphaOrBeta ? 'SNAPSHOT' : 'RELEASE', "w") 11 | fileObj.Write(version) 12 | fileObj.Close() 13 | 14 | ; 拷贝文件 + 并最终生成在 out 目录下生成 jiejian-版本 的文件夹 15 | RunWait("packagePrep.bat jiejian-" . version, , "Hide") 16 | ; 使用 7z 进行压缩 17 | RunWait('..\package\7zr.exe a jiejian-' . version . '.7z jiejian-' . version, 'out', "Hide") 18 | -------------------------------------------------------------------------------- /package/Package.ahk: -------------------------------------------------------------------------------- 1 | #Requires AutoHotkey v2.0 2 | #SingleInstance Force 3 | #NoTrayIcon 4 | 5 | SetWorkingDir(A_ScriptDir . "\..") 6 | 7 | base := "compiler\" 8 | Ahk2Exe := base . "Ahk2Exe.exe" 9 | 10 | jiejian := "jiejian" 11 | jiejianAhk := "Jiejian.ahk" 12 | 13 | aAutoHotkey := "AutoHotkey" 14 | a64exe := "64.exe" 15 | a32exe := "32.exe" 16 | 17 | ; 先构建 64 位 18 | if (A_Is64bitOS) { 19 | RunWait Ahk2Exe 20 | . ' /in ' . jiejianAhk 21 | . ' /out ' . jiejian . a64exe 22 | . ' /base ' . base . aAutoHotkey . a64exe 23 | . ' /compress 2' 24 | } 25 | 26 | ; 再构建 32 位 27 | RunWait Ahk2Exe 28 | . ' /in ' . jiejianAhk 29 | . ' /out ' . jiejian . a32exe 30 | . ' /base ' . base . aAutoHotkey . a32exe 31 | . ' /compress 2' 32 | 33 | ; 由于打包 和 构建不知为何发生在 生成 exe 之前,所以再次构建一次 32 位 34 | RunWait Ahk2Exe 35 | . ' /in ' . jiejianAhk 36 | . ' /out ' . jiejian . a32exe 37 | . ' /base ' . base . aAutoHotkey . a32exe 38 | . ' /compress 2' 39 | 40 | MsgBox '打包完成', '打包程序', 'T3' 41 | -------------------------------------------------------------------------------- /packagePrep.bat: -------------------------------------------------------------------------------- 1 | @REM author: acc8226 2 | @REM updateDate: 2023 3 | @REM 需要一个入参哦,不能直接调用,而是被 jiejianPostExec.ahk 调用 4 | 5 | ECHO %1 6 | RD /S /Q out\%1\ 7 | MKDIR out\%1\extra\ 8 | MKDIR out\%1\custom\ 9 | XCOPY extra out\%1\extra\ /S 10 | XCOPY custom out\%1\custom\ /S 11 | COPY app.csv out\%1\ 12 | COPY template\data.csv out\%1\ 13 | COPY jiejian32.exe out\%1\ 14 | COPY jiejian64.exe out\%1\ 15 | -------------------------------------------------------------------------------- /setting.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/setting.ini -------------------------------------------------------------------------------- /template/data.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/template/data.csv -------------------------------------------------------------------------------- /template/dataTemplate.csv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/template/dataTemplate.csv -------------------------------------------------------------------------------- /tools/MouseSC_Query.bat: -------------------------------------------------------------------------------- 1 | @Echo Off 2 | 3 | Set tApp=MouseSC.exe 4 | If Not %PROCESSOR_ARCHITECTURE% == x86 Set tApp=MouseSC_x64.exe 5 | 6 | CD /D %~dp0\ 7 | If Not Exist "%tApp%" ( 8 | Echo The file %tApp% was not found 9 | pause & exit 10 | ) 11 | 12 | Call :Query /PrimaryButton 13 | Echo Mouse Primary Button: %tRESULT% 14 | 15 | Call :Query /Speed 16 | Echo Mouse Speed: %tRESULT% 17 | 18 | Call :Query /PointerPrecision 19 | Echo Enhance Pointer Precision: %tRESULT% 20 | 21 | Call :Query /VerticalScroll 22 | Echo Mouse Vertical Scroll: %tRESULT% 23 | 24 | Call :Query /HorizontalScroll 25 | Echo Mouse Horizontal Scroll: %tRESULT% 26 | 27 | :Query 28 | for /f "tokens=*" %%q in ('"%tApp%" /Query %1') do set tRESULT=%%q 29 | -------------------------------------------------------------------------------- /tools/MouseSC_x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/tools/MouseSC_x64.exe -------------------------------------------------------------------------------- /tools/ReIconCache_x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/tools/ReIconCache_x64.exe -------------------------------------------------------------------------------- /tools/Rexplorer_x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/tools/Rexplorer_x64.exe -------------------------------------------------------------------------------- /tools/SkipUAC.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/tools/SkipUAC.ini -------------------------------------------------------------------------------- /tools/SkipUAC_x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/tools/SkipUAC_x64.exe -------------------------------------------------------------------------------- /tools/SoundControl.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/tools/SoundControl.exe -------------------------------------------------------------------------------- /tools/W11ClassicMenu.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/tools/W11ClassicMenu.exe -------------------------------------------------------------------------------- /tools/W11ClassicMenu.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/tools/W11ClassicMenu.ini -------------------------------------------------------------------------------- /tools/Wub.ini: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/tools/Wub.ini -------------------------------------------------------------------------------- /tools/Wub_x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/acc8226/jiejian/fc0a4426ce676c1e1e05b7297ad6f694bb8a332f/tools/Wub_x64.exe --------------------------------------------------------------------------------