├── .gitignore
├── LICENSE
├── Makefile
├── MyKeymap.exe
├── bin
├── AlignComment.ahk
├── AutoHotkey64.exe
├── ChangeBrightness.ahk
├── CollectText.ahk
├── Launcher.ahk
├── MiscTools.ahk
├── MyKeymap-CommandInput.exe
├── MyKeymap.ahk
├── RunActivate.ahk
├── SoundControl.exe
├── WindowSpy.ahk
├── font
│ └── font.ttf
├── html
│ ├── AlignText.html
│ ├── logo.ico
│ └── tailwind.css
├── icons
│ ├── logo.ico
│ ├── logo2.ico
│ └── logo3.ico
├── lib
│ ├── Actions.ahk
│ ├── Functions.ahk
│ ├── InputTipWindow.ahk
│ ├── KeymapManager.ahk
│ ├── Monitor.ahk
│ ├── Utils.ahk
│ └── translation.ahk
├── msvcp140.dll
├── sound
│ ├── cancel.wav
│ ├── keydown.wav
│ ├── show.wav
│ └── spaceKey.wav
├── vcruntime140.dll
└── vcruntime140_1.dll
├── build_tools.go
├── config-server
├── .gitignore
├── cmd
│ └── settings
│ │ └── main.go
├── go.mod
├── go.sum
├── internal
│ ├── command
│ │ └── command.go
│ ├── matrix
│ │ └── matrix.go
│ └── script
│ │ ├── abbr.go
│ │ ├── action.go
│ │ ├── config.go
│ │ ├── options.go
│ │ ├── script.go
│ │ └── script_test.go
└── templates
│ ├── CommandInputSkin.tmpl
│ ├── CustomShellMenu.ahk
│ ├── help.html
│ └── mykeymap.tmpl
├── config-ui
├── .browserslistrc
├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── README.md
├── index.html
├── package-lock.json
├── package.json
├── public
│ ├── ProgramPathExample.html
│ ├── config_doc.html
│ ├── config_doc.md
│ ├── exe-path.png
│ ├── favicon.ico
│ ├── font-compare
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ └── 6.png
│ ├── img
│ │ ├── example01.png
│ │ ├── example02.png
│ │ ├── example03.png
│ │ └── example04.png
│ └── uwp-shortcut.png
├── src
│ ├── App.vue
│ ├── assets
│ │ └── logo.png
│ ├── components
│ │ ├── ActionCommentTable.vue
│ │ ├── Code.vue
│ │ ├── HelloWorld.vue
│ │ ├── Key.vue
│ │ ├── NavigationDrawer.vue
│ │ ├── Table.vue
│ │ ├── Tip.vue
│ │ ├── actions
│ │ │ ├── Action.vue
│ │ │ ├── ActivateOrRun.vue
│ │ │ ├── BuiltinFunction.vue
│ │ │ ├── Mouse.vue
│ │ │ ├── MyKeymap.vue
│ │ │ ├── RadioGroup.vue
│ │ │ ├── RemapKey.vue
│ │ │ ├── SendKey.vue
│ │ │ ├── System.vue
│ │ │ ├── Text.vue
│ │ │ └── Window.vue
│ │ └── dialog
│ │ │ ├── InputKeyValueDialog.vue
│ │ │ ├── PathDialog.vue
│ │ │ └── WindowGroupDialog.vue
│ ├── main.ts
│ ├── plugins
│ │ ├── index.ts
│ │ ├── vuetify.ts
│ │ └── webfontloader.ts
│ ├── router
│ │ └── index.ts
│ ├── store
│ │ ├── app.ts
│ │ ├── config.ts
│ │ ├── index.ts
│ │ ├── language-map.ts
│ │ ├── server.ts
│ │ └── shortcut.ts
│ ├── styles
│ │ └── settings.scss
│ ├── types
│ │ └── config.ts
│ ├── views
│ │ ├── Abbr.vue
│ │ ├── CustomHotkey.vue
│ │ ├── Home.vue
│ │ ├── HomeSettings.vue
│ │ ├── Keymap.vue
│ │ └── Settings.vue
│ └── vite-env.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
├── data
├── config.json
└── custom_functions.ahk
├── doc
├── features.png
├── settings.en.png
├── settings.png
└── 夏日大作战.gif
├── lanzou_client.py
├── readme.en.md
├── readme.md
├── tailwind
├── help_page.config.js
├── help_page.css
├── html-tools.config.js
├── index.css
├── package-lock.json
├── package.json
└── tailwind.config.js
├── tools
├── Rexplorer_x64.exe
└── 工具来源.txt
├── 变量方法差别.md
└── 误报病毒时执行这个.bat
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .pnpm-debug.log
3 | MyKeymap
4 | bin/site
5 | bin/html-tools/AlignText.html
6 | bin/html-tools/tailwind.css
7 | bin/templates
8 | bin/mykeymap-settings-server
9 | bin/mykeymap-settings-server.exe
10 | bin/settings.exe
11 | *.7z
12 | todo.md
13 | .idea
14 | MyKeymap-*/
15 | config-server/settings.exe
16 | shortcuts/
17 | lanzou_cookie.txt
18 | CommandInputSkin.txt
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | version = 2.0-beta33
2 | ahkVersion = 2.0.19
3 | folder = MyKeymap-$(version)
4 | zip = $(folder).7z
5 |
6 | buildServer:
7 | go.exe build -C ./config-server -tags=nomsgpack -ldflags "-s -w -X settings/internal/script.MykeymapVersion=$(version)" -o ../bin/settings.exe ./cmd/settings
8 | rm -f -r bin/templates
9 | cp -r config-server/templates bin/templates
10 |
11 | buildClient:
12 | cd config-ui; npm run build
13 | rm -f config-ui/tsconfig.tsbuildinfo
14 | cd config-ui/dist/assets; rm -f *.woff *.eot *.ttf
15 |
16 | copyFiles: CopyAHK
17 | rm -f -r $(folder)
18 | mkdir $(folder)
19 | mkdir $(folder)/shortcuts
20 |
21 | rm -f -r bin/site
22 | cp -r config-ui/dist bin/site
23 |
24 | cp -r data $(folder)/
25 | cp -r bin $(folder)/
26 | cp -r tools $(folder)/
27 | cp MyKeymap.exe $(folder)/
28 | cp 误报病毒时执行这个.bat $(folder)/
29 |
30 | # 如果直接用 wsl 的 cp 命令复制, 复制出的文件会有 read-only 属性, 比较奇怪
31 | CopyAHK:
32 | @echo '@copy /y "C:\\Program Files\\AutoHotkey\\v2\AutoHotkey64.exe" .\\bin\\' > CopyAHK.bat
33 | cmd.exe /c CopyAHK.bat
34 | rm CopyAHK.bat
35 |
36 | build: buildServer buildClient copyFiles
37 | cd bin; ./settings.exe ChangeVersion $(version)
38 | rm -f MyKeymap-*.7z
39 | 7z.exe a $(zip) $(folder)
40 | rm -f -r $(folder)
41 | @echo ------------------------- build ok -------------------------------
42 |
43 | createRelease:
44 | curl -L \
45 | -X POST \
46 | -H "Accept: application/vnd.github+json" \
47 | -H "Authorization: Bearer $$(cat ~/gh_token)" \
48 | -H "X-GitHub-Api-Version: 2022-11-28" \
49 | https://api.github.com/repos/xianyukang/MyKeymap/releases \
50 | -d '{"tag_name":"v$(version)","target_commitish":"main","name":"v$(version)","body":"Description of the release"}' 2>/dev/null | jq -r '.id' > release_id
51 | curl -L \
52 | -X POST \
53 | -H "Accept: application/vnd.github+json" \
54 | -H "Authorization: Bearer $$(cat ~/gh_token)" \
55 | -H "X-GitHub-Api-Version: 2022-11-28" \
56 | -H "Content-Type: application/octet-stream" \
57 | "https://uploads.github.com/repos/xianyukang/MyKeymap/releases/$$(cat release_id)/assets?name=$(zip)" \
58 | --data-binary "@$(zip)" | jq
59 | rm release_id
60 |
61 |
62 | uploadLanZou:
63 | go run build_tools.go checkForAHKUpdate $(ahkVersion)
64 | python3 lanzou_client.py $(zip) 2> share_link.json
65 | go run build_tools.go updateShareLink $(version)
66 | rm -f share_link.json
67 |
68 | upload: uploadLanZou createRelease
69 | @echo ------------------------- upload ok -------------------------------
70 |
71 | # 下面是开发时用到的命令:
72 |
73 | server: buildServer
74 | @cd config-server; ../bin/settings.exe debug
75 |
76 | # https://github.com/facebook/create-react-app/issues/10253#issuecomment-747970009
77 | # 坑啊: WSL2 中的 Chokidar 无法监测 Windows 上的文件修改, 导致 hot reload 不起作用
78 | # 需要把项目移到 WSL2 里面, 或者使用 Polling 模式
79 | client:
80 | @cd config-ui; CHOKIDAR_USEPOLLING=true npm run dev
81 |
82 | ahk: buildServer
83 | @config-server/settings.exe GenerateAHK ./data/config.json ./config-server/templates/mykeymap.tmpl ./bin/MyKeymap.ahk
84 |
85 | .PHONY: server client ahk buildServer buildClient copyFiles upload build
--------------------------------------------------------------------------------
/MyKeymap.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/MyKeymap.exe
--------------------------------------------------------------------------------
/bin/AlignComment.ahk:
--------------------------------------------------------------------------------
1 | #Requires AutoHotkey v2.0
2 | #NoTrayIcon
3 | #SingleInstance Force
4 | SetWorkingDir A_ScriptDir
5 |
6 | AlignText()
7 |
8 | AlignText() {
9 | text := GetSelectedText()
10 | value := URIEncode(text)
11 | Run("msedge.exe --app=" A_WorkingDir "\html\AlignText.html?text=" value)
12 | }
13 |
14 | GetSelectedText() {
15 | temp := A_Clipboard
16 | ; 清空剪贴板
17 | A_Clipboard := ""
18 |
19 | Send("^c")
20 | if not (ClipWait(0.4)) {
21 | Tip("no items selected", -1200)
22 | return
23 | }
24 | text := A_Clipboard
25 |
26 | A_Clipboard := temp
27 | return RTrim(text, "`r`n")
28 | }
29 |
30 | Tip(message, time := -1500) {
31 | ToolTip(message)
32 | SetTimer(() => ToolTip(), time)
33 | }
34 |
35 | URIEncode(uri, encoding := "UTF-8") {
36 | if !uri {
37 | return
38 | }
39 | var := Buffer(StrPut(uri, encoding), 0)
40 | StrPut(uri, var, encoding)
41 | pos := 1
42 | ; 按字节遍历 buffer 中的 utf-8 字符串, 注意字符串有 null-terminator
43 | While pos < var.Size {
44 | code := NumGet(var, pos - 1, "UChar")
45 | if (code >= 0x30 && code <= 0x39) || (code >= 0x41 && code <= 0x5A) || (code >= 0x61 && code <= 0x7A)
46 | res .= Chr(code)
47 | else
48 | res .= "%" . Format("{:02X}", code)
49 | pos++
50 | }
51 | return res
52 | }
--------------------------------------------------------------------------------
/bin/AutoHotkey64.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/AutoHotkey64.exe
--------------------------------------------------------------------------------
/bin/ChangeBrightness.ahk:
--------------------------------------------------------------------------------
1 | #SingleInstance Force
2 | #NoTrayIcon
3 | SetWorkingDir(A_ScriptDir)
4 | TraySetIcon("./icons/logo.ico")
5 |
6 | #Include ./lib/Monitor.ahk
7 | #Include ./lib/Utils.ahk
8 |
9 | class CLayout extends Gui {
10 | monitors := []
11 | currentIndex := 1
12 | monCount := SysGet(80)
13 | monitorCol := Monitor()
14 | defaultGuiWidth := 440
15 | monitorIcoSize := 170
16 |
17 | __New() {
18 | super.__New()
19 | super.Opt("+AlwaysOnTop +Owner +LastFound +Resize -MinimizeBox -MaximizeBox")
20 | super.Title := "显示器亮度调节"
21 | this.InitMonitors()
22 | this.ChangeSelectedMonitor()
23 |
24 | ; 添加下方帮助信息
25 | super.SetFont("s12")
26 | super.Add("text", "x10 y190 w290 h20", "EDSF调节亮度、WR切换显示器、C退出")
27 | super.Add("text", "x10 y210 w" this.defaultGuiWidth " h20", "如果不起作用,可能是因为使用的显示器没有开启DDC/CI功能")
28 | }
29 |
30 | ; 初始化已使用的显示器信息
31 | InitMonitors() {
32 | isWin10 := StrCompare(A_OSVersion, "10.0.22000") < 0
33 |
34 | ; 获取已使用的显示器数量
35 | loop this.monCount {
36 | monIndex := A_Index
37 | ; 获取显示器当前亮度
38 | brightness := this.getBrightness(monIndex)
39 |
40 | ; 计算MonIcoText要显示的位置
41 | ; 当显示器图标无法沾满窗口默认大小时,让显示器图标剧中
42 | padding := 0
43 | if (this.monitorIcoSize * this.monCount < this.defaultGuiWidth) {
44 | padding := (this.defaultGuiWidth - (this.monitorIcoSize * this.monCount)) / 2
45 | }
46 | x := this.monitorIcoSize * (monIndex - 1) + padding
47 | monIco := super.Add("Text", "x" x " y0 w" this.monitorIcoSize " h" this.monitorIcoSize " 0x1", "🖥️")
48 | monIco.SetFont("s128 c000000")
49 |
50 | y := 52
51 | ; 是 Windows 10 则使用不同的 y 坐标
52 | if isWin10 {
53 | y := 66
54 | }
55 |
56 | ; 计算显示器亮度要显示的位置
57 | x := x + 52
58 | monBrightness := super.Add("Text", "x" x " y" y " w65 h40 0x1", brightness)
59 | monBrightness.SetFont("s32 cffffff")
60 | monBrightness.Opt("+BackgroundTrans")
61 |
62 | ; 将当前控件保存起来
63 | m := Map()
64 | m["ico"] := monIco
65 | m["brightness"] := monBrightness
66 | this.monitors.Push(m)
67 | }
68 | }
69 |
70 | ; 修改界面上的当前显示器
71 | ChangeSelectedMonitor(old := 1) {
72 | m := this.monitors.Get(old)
73 | m["ico"].SetFont("c000000")
74 |
75 | m := this.monitors.Get(this.currentIndex)
76 | m["ico"].SetFont("cff6688")
77 | }
78 |
79 | ; 上一个显示屏
80 | Previous() {
81 | if (this.currentIndex > 1) {
82 | this.changeSelectedMonitor(this.currentIndex--)
83 | }
84 | }
85 |
86 | ; 下一个显示屏
87 | Next() {
88 | if (this.currentIndex < this.monCount) {
89 | this.changeSelectedMonitor(this.currentIndex++)
90 | }
91 | }
92 |
93 | ; 加亮度
94 | IncBrightness(mon) {
95 | m := this.monitors.Get(this.currentIndex)
96 | val := m["brightness"].Value + mon
97 | if val > 100 {
98 | val := 100
99 | }
100 | this.SetBrightness(val, this.currentIndex)
101 | m["brightness"].Value := val
102 | }
103 |
104 | ; 减亮度
105 | DecBrightness(mon) {
106 | m := this.monitors.Get(this.currentIndex)
107 | val := m["brightness"].Value - mon
108 | if val < 0 {
109 | val := 0
110 | }
111 | this.SetBrightness(val, this.currentIndex)
112 | m["brightness"].Value := val
113 | }
114 |
115 | ; 显示GUI
116 | ShowGui() {
117 | super.Show()
118 | disableIME(super.Hwnd)
119 | }
120 |
121 | ; 获取屏幕亮度
122 | GetBrightness(monIndex) {
123 | brightness := ""
124 | try {
125 | brightness := this.monitorCol.GetBrightness(monIndex)["Current"]
126 | } catch Error as e {
127 | ; 使用wmi获取亮度
128 | For property in ComObjGet("winmgmts:\\.\root\WMI").ExecQuery("SELECT * FROM WmiMonitorBrightness")
129 | brightness := property.CurrentBrightness
130 | }
131 |
132 | return brightness
133 | }
134 |
135 | ; 设置屏幕亮度
136 | SetBrightness(brightness, monIndex, timeout := 1) {
137 | try {
138 | this.monitorCol.setBrightness(brightness, monIndex)
139 | } catch Error as e {
140 | ; 使用wmi设置亮度
141 | For property in ComObjGet("winmgmts:\\.\root\WMI").ExecQuery("SELECT * FROM WmiMonitorBrightnessMethods")
142 | property.WmiSetBrightness(timeout, brightness)
143 | }
144 | }
145 | }
146 |
147 | layout := CLayout()
148 | layout.ShowGui()
149 |
150 | SetTimer(IfLoseFocusThenExit, 100)
151 | IfLoseFocusThenExit() {
152 | if ( not WinActive("ahk_id" layout.Hwnd)) {
153 | ExitApp
154 | }
155 | }
156 |
157 | OnMessage(0x100, WM_KEYDOWN)
158 | WM_KEYDOWN(wParam, lParam, msg, hwnd) {
159 | switch (GetKeyName(Format("vk{:x}", wparam))) {
160 | case "w": layout.Previous()
161 | case "r": layout.Next()
162 | case "e": layout.IncBrightness(10)
163 | case "f": layout.IncBrightness(5)
164 | case "d": layout.DecBrightness(10)
165 | case "s": layout.DecBrightness(5)
166 | case "c": ExitApp
167 | case "Escape": ExitApp
168 | }
169 |
170 | return 0
171 | }
--------------------------------------------------------------------------------
/bin/CollectText.ahk:
--------------------------------------------------------------------------------
1 | #SingleInstance Force
2 | #NoTrayIcon
3 |
4 | ListLines(false)
5 | SetWorkingDir(A_ScriptDir)
6 | TraySetIcon("./icons/logo.ico")
7 | SendMode("Input")
8 |
9 |
10 | last := ""
11 | layout := CLayout()
12 | layout.showGui()
13 | OnClipboardChange(ClipboardChangeCallbakc)
14 |
15 | class CLayout {
16 | X := 10
17 | Y := 10
18 | W := 700
19 | H := 180
20 | textList := []
21 |
22 | __New() {
23 | this.myGui := Gui("+resize +AlwaysOnTop -SysMenu")
24 | this.hwnd := this.myGUI.hwnd
25 | this.myGUI.SetFont("s12", "Microsoft YaHei Ui")
26 | this.myGUI.Add("text", "y-20", "连续复制后,在本窗口按空格键复制下面的文本")
27 | }
28 |
29 | ShowGui(title := "连续复制后, 在本窗口内按空格或 C 键") {
30 | this.myGui.Title := title
31 | this.myGUI.Show("AutoSize NoActivate W" this.W)
32 | }
33 |
34 | AddItem(text) {
35 | this.textList.Push(text)
36 | space := "y+6"
37 | this.myGUI.Add("text", "y+6 w700 +wrap", text)
38 | }
39 |
40 | Restart() {
41 | this.myGui.Destroy()
42 | this.__New
43 | this.ShowGui("已把收集到的 " this.textList.Length " 行挪入剪切版")
44 | this.textList := []
45 | SetTimer(() => this.ShowGui(), -5000)
46 | }
47 |
48 | }
49 |
50 |
51 | join(sep, params*) {
52 | str := ""
53 |
54 | for index, param in params
55 | str .= sep . param
56 |
57 | return SubStr(str, StrLen(sep) + 1)
58 | }
59 |
60 | OnMessage(0x100, WM_KEYDOWN)
61 | WM_KEYDOWN(wParam, lParam, msg, hwnd) {
62 | switch (GetKeyName(Format("vk{:x}", wparam))) {
63 | case "Space":
64 | JoinCopiedText()
65 | case "Escape":
66 | ExitApp
67 | case "c":
68 | JoinCopiedText()
69 | ExitApp
70 | }
71 | return 0
72 | }
73 |
74 | ClipboardChangeCallbakc(type) {
75 | global last
76 | if (type != 1)
77 | return
78 |
79 | text := RTrim(A_Clipboard, " `t`r`n")
80 | if text == last {
81 | return
82 | }
83 | last := text
84 | layout.AddItem(text)
85 | layout.ShowGui()
86 | }
87 |
88 | JoinCopiedText()
89 | {
90 | ; 临时取消监听
91 | OnClipboardChange(ClipboardChangeCallbakc, 0)
92 | res := join("`n", layout.textList*)
93 | if res {
94 | A_Clipboard := res
95 | }
96 | layout.Restart()
97 | ; 启动监听
98 | OnClipboardChange(ClipboardChangeCallbakc)
99 | }
--------------------------------------------------------------------------------
/bin/Launcher.ahk:
--------------------------------------------------------------------------------
1 | #Requires AutoHotkey v2.0
2 | ; 该脚本只能编译成Exe使用,只用做启动MyKeymap.ahk和AHk v2使用
3 | #SingleInstance Force
4 | #NoTrayIcon
5 | ;@Ahk2Exe-SetMainIcon ./bin/icons/logo3.ico
6 | ;@Ahk2Exe-ExeName MyKeymap
7 | SetWorkingDir(A_ScriptDir)
8 |
9 | ; 以管理员权限运行
10 | full_command_line := DllCall("GetCommandLine", "str")
11 | if not (A_IsAdmin or RegExMatch(full_command_line, " /restart(?!\S)")) {
12 | otherArgs := ""
13 | if (A_Args.Length) {
14 | otherArgs := A_Args.Get(1)
15 | }
16 |
17 | try {
18 | if A_IsCompiled
19 | Run '*RunAs "' A_ScriptFullPath '" ' otherArgs ' /restart'
20 | else
21 | Run '*RunAs "' A_AhkPath '" /restart "' A_ScriptFullPath '"'
22 | ExitApp
23 | } catch Error as e {
24 | hasTip := true
25 | ToolTip("`n MyKeymap is running with normal privileges.`n MyKeymap will not work in a window with admin rights ( e.g., Taskmgr.exe ) `n ")
26 | }
27 | }
28 |
29 | mainAhkFilePath := "./bin/MyKeymap.ahk"
30 |
31 | if (A_Args.Length) {
32 | Run("MyKeymap.exe /script " A_Args.Get(1))
33 | } else {
34 | ; 通过配置文件生成脚本
35 | RunWait("./bin/settings.exe GenerateScripts", "./bin", "Hide")
36 | ; 首次运行则生成快捷方式
37 | if !FileExist(A_WorkingDir "\shortcuts\*.*") {
38 | Run("MyKeymap.exe /script ./bin/MiscTools.ahk GenerateShortcuts")
39 | }
40 | ; 启动脚本
41 | Run("MyKeymap.exe /script " mainAhkFilePath)
42 | }
43 |
44 | if IsSet(hasTip) {
45 | Sleep 7000
46 | }
--------------------------------------------------------------------------------
/bin/MiscTools.ahk:
--------------------------------------------------------------------------------
1 | #Requires AutoHotkey v2.0
2 | #SingleInstance Force
3 | #NoTrayIcon
4 |
5 | SetWorkingDir A_ScriptDir "\.."
6 |
7 | if !A_Args.Length {
8 | return
9 | }
10 |
11 | if A_Args[1] = "GenerateShortcuts" {
12 | ; 由于 windows 系统不允许存在同名文件和文件夹,故预先删除之
13 | try FileDelete("shortcuts")
14 | try DirDelete("shortcuts", true)
15 | ; 休息 0.05 s,防止 delete 操作未完成引起的 shortcuts 目录被占用问题
16 | Sleep(50)
17 | try DirCreate("shortcuts")
18 |
19 | ; 排除特定的快捷方式
20 | useless := "i)(?:uninstall|卸载|help|iSCSI 发起程序|ODBC 数据源|Data Sources \(ODBC\)"
21 | . "|ODBC Data|Windows 内存诊断|恢复驱动器|组件服务|碎片整理和优化驱动器|Office 语言首选项"
22 | . "|手册|更新|帮助|Tools Command Prompt for|license|Website|设置向导|More Games from Microsoft"
23 | . "|细胞词库|意见反馈|输入法管理器|输入法修复器|皮肤下载|官方网站|Microsoft Office 语言设置"
24 | . "|Microsoft Office 文档关联中心|Internet Explorer \(No Add-ons\)"
25 | . "|Windows Easy Transfer Reports|Welcome Center|Microsoft Office 2007 控制中心"
26 |
27 | Loop Files A_StartupCommon . "\*.lnk*"
28 | useless .= "|" . A_LoopFileName
29 | Loop Files A_Startup . "\*.lnk*"
30 | useless .= "|" . A_LoopFileName
31 | useless .= ")"
32 |
33 | ; 把开始菜单中的快捷方式都拷贝到 shortcuts 目录
34 | copyFiles(A_ProgramsCommon "\*.lnk", "shortcuts\", useless)
35 | copyFiles(A_Programs "\*.lnk", "shortcuts\", useless)
36 | ; 然后再生成 UWP 相关的快捷方式
37 | oFolder := ComObject("Shell.Application").NameSpace("shell:AppsFolder")
38 | if Type(oFolder) != 'String' {
39 | for item in oFolder.Items {
40 | if item.Name . ".lnk" ~= useless || FileExist("shortcuts\" item.Name ".lnk") {
41 | continue
42 | }
43 | try FileCreateShortcut("shell:appsfolder\" item.Path, "shortcuts\" item.Name ".lnk")
44 | }
45 | }
46 | return
47 | }
48 |
49 | if A_Args[1] = "RunAtStartup" {
50 | linkFile := A_Startup "\MyKeymap.lnk"
51 | if A_Args[2] = "On" {
52 | FileCreateShortcut(A_WorkingDir "\MyKeymap.exe", linkFile, A_WorkingDir)
53 | } else if (A_Args[2] = "Off") {
54 | if FileExist(linkFile) {
55 | FileDelete(linkFile)
56 | }
57 | }
58 | return
59 | }
60 |
61 | copyFiles(pattern, dest, ignore := "") {
62 | Loop Files pattern, "R" {
63 | if (A_LoopFileName ~= ignore) {
64 | continue
65 | }
66 | try FileCopy(A_LoopFilePath, dest, true)
67 | }
68 | }
--------------------------------------------------------------------------------
/bin/MyKeymap-CommandInput.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/MyKeymap-CommandInput.exe
--------------------------------------------------------------------------------
/bin/RunActivate.ahk:
--------------------------------------------------------------------------------
1 | ; 启动后激活一下窗口
2 | target := A_Args[1]
3 | Run target, , , &pid
4 | exists := WinWait("ahk_pid " pid, , 1)
5 | if exists && !WinActive() {
6 | WinActivate
7 | }
--------------------------------------------------------------------------------
/bin/SoundControl.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/SoundControl.exe
--------------------------------------------------------------------------------
/bin/WindowSpy.ahk:
--------------------------------------------------------------------------------
1 | ;
2 | ; Window Spy for AHKv2
3 | ;
4 |
5 | #Requires AutoHotkey v2.0
6 |
7 | #NoTrayIcon
8 | #SingleInstance force
9 | SetWorkingDir A_ScriptDir
10 | CoordMode "Pixel", "Screen"
11 |
12 | Global oGui
13 |
14 | WinSpyGui()
15 |
16 | WinSpyGui() {
17 | Global oGui
18 |
19 | try TraySetIcon "./icons/logo.ico"
20 | DllCall("shell32\SetCurrentProcessExplicitAppUserModelID", "wstr", "AutoHotkey.WindowSpy")
21 |
22 | oGui := Gui("AlwaysOnTop Resize MinSize +DPIScale", "当前窗口的标识符")
23 | oGui.OnEvent("Close", WinSpyClose)
24 | oGui.OnEvent("Size", WinSpySize)
25 |
26 | oGui.BackColor := "FFFFFF"
27 | oGui.SetFont("s11", "Microsoft YaHei")
28 | oGui.Add("Text", , "程序的窗口标识符有下面三种:")
29 | oGui.Add("Text", , "▷ 窗口名: 无标题 - 记事本")
30 | oGui.Add("Text", , "▷ 进程名: ahk_exe notepad.exe")
31 | oGui.Add("Text", , "▷ 窗口类名: ahk_class Notepad")
32 | oGui.Add("Text", , "➤ 一般选进程名")
33 | oGui.Add("Text", , "➤ 可以只使用部分窗口名: 记事")
34 | oGui.Add("Text", , "➤ 也可以组合两个标识符: 记事 ahk_exe notepad.exe (更精确")
35 | oGui.Add("Text", , "")
36 |
37 | oGui.Add("Text", , "Window Title, Class and Process:")
38 | oGui.Add("Edit", "xm w640 r4 ReadOnly -Wrap vCtrl_Title")
39 | ; oGui.Add("Text",,"当前鼠标位置:")
40 | ; oGui.Add("Edit","w640 r4 ReadOnly vCtrl_MousePos")
41 | ; oGui.Add("Text",,"当前窗口位置:")
42 | ; oGui.Add("Edit","w640 r2 ReadOnly vCtrl_Pos")
43 | oGui.Add("Text", "w640 r1 vCtrl_Freeze", (txtNotFrozen := ""))
44 | oGui.Add("Checkbox", "yp+20 xp+400 h15 w240 Left vCtrl_FollowMouse", "跟随鼠标 (可按 Ctrl 暂停刷新)")
45 |
46 | oGui.Show("NoActivate")
47 | ;WinGetClientPos(&x_temp, &y_temp2, , , "ahk_id " oGui.hwnd)
48 |
49 | ; oGui.horzMargin := x_temp*96//A_ScreenDPI - 320 ; now using oGui.MarginX
50 |
51 | oGui.txtNotFrozen := txtNotFrozen ; create properties for futur use
52 | oGui.txtFrozen := ""
53 |
54 | SetTimer Update, 250
55 | }
56 |
57 | WinSpySize(GuiObj, MinMax, Width, Height) {
58 | Global oGui
59 |
60 | If !oGui.HasProp("txtNotFrozen") ; WinSpyGui() not done yet, return until it is
61 | return
62 |
63 | SetTimer Update, (MinMax = 0) ? 250 : 0 ; suspend updates on minimize
64 |
65 | ctrlW := Width - (oGui.MarginX * 2) ; ctrlW := Width - horzMargin
66 | list := "Title,MousePos,Ctrl,Pos,SBText,VisText,AllText,Freeze"
67 | }
68 |
69 | WinSpyClose(GuiObj) {
70 | ExitApp
71 | }
72 |
73 | Update() { ; timer, no params
74 | Try TryUpdate() ; Try
75 | }
76 |
77 | TryUpdate() {
78 | Global oGui
79 |
80 | If !oGui.HasProp("txtNotFrozen") ; WinSpyGui() not done yet, return until it is
81 | return
82 |
83 | Ctrl_FollowMouse := oGui["Ctrl_FollowMouse"].Value
84 | CoordMode "Mouse", "Screen"
85 | MouseGetPos &msX, &msY, &msWin, &msCtrl, 2 ; get ClassNN and hWindow
86 | actWin := WinExist("A")
87 |
88 | if (Ctrl_FollowMouse) {
89 | curWin := msWin, curCtrl := msCtrl
90 | WinExist("ahk_id " curWin) ; updating LastWindowFound?
91 | } else {
92 | curWin := actWin
93 | curCtrl := ControlGetFocus() ; get focused control hwnd from active win
94 | }
95 | curCtrlClassNN := ""
96 | Try curCtrlClassNN := ControlGetClassNN(curCtrl)
97 |
98 | t1 := WinGetTitle(), t2 := WinGetClass()
99 | if (curWin = oGui.hwnd || t2 = "MultitaskingViewFrame") { ; Our Gui || Alt-tab
100 | UpdateText("Ctrl_Freeze", oGui.txtFrozen)
101 | return
102 | }
103 |
104 | UpdateText("Ctrl_Freeze", oGui.txtNotFrozen)
105 | t3 := WinGetProcessName()
106 |
107 | WinDataText := t1 "`n" ; ZZZ
108 | . "ahk_class " t2 "`n"
109 | . "ahk_exe " t3 "`n"
110 |
111 | UpdateText("Ctrl_Title", WinDataText)
112 | CoordMode "Mouse", "Window"
113 | MouseGetPos &mrX, &mrY
114 | CoordMode "Mouse", "Client"
115 | MouseGetPos &mcX, &mcY
116 | mClr := PixelGetColor(msX, msY, "RGB")
117 | mClr := SubStr(mClr, 3)
118 |
119 | mpText := "Screen:`t" msX ", " msY "`n"
120 | . "Window:`t" mrX ", " mrY "`n"
121 | . "Client:`t" mcX ", " mcY " (default)`n"
122 | . "Color:`t" mClr " (Red=" SubStr(mClr, 1, 2) " Green=" SubStr(mClr, 3, 2) " Blue=" SubStr(mClr, 5) ")"
123 |
124 | UpdateText("Ctrl_MousePos", mpText)
125 |
126 | wX := "", wY := "", wW := "", wH := ""
127 | WinGetPos &wX, &wY, &wW, &wH, "ahk_id " curWin
128 | WinGetClientPos(&wcX, &wcY, &wcW, &wcH, "ahk_id " curWin)
129 |
130 | wText := "Screen:`tx: " wX "`ty: " wY "`tw: " wW "`th: " wH "`n"
131 | . "Client:`tx: " wcX "`ty: " wcY "`tw: " wcW "`th: " wcH
132 |
133 | UpdateText("Ctrl_Pos", wText)
134 | sbTxt := ""
135 |
136 | Loop {
137 | ovi := ""
138 | Try ovi := StatusBarGetText(A_Index)
139 | if (ovi = "")
140 | break
141 | sbTxt .= "(" A_Index "):`t" textMangle(ovi) "`n"
142 | }
143 |
144 | sbTxt := SubStr(sbTxt, 1, -1) ; StringTrimRight, sbTxt, sbTxt, 1
145 | UpdateText("Ctrl_SBText", sbTxt)
146 | bSlow := oGui["Ctrl_IsSlow"].Value ; GuiControlGet, bSlow,, Ctrl_IsSlow
147 |
148 | if (bSlow) {
149 | DetectHiddenText False
150 | ovVisText := WinGetText() ; WinGetText, ovVisText
151 | DetectHiddenText True
152 | ovAllText := WinGetText() ; WinGetText, ovAllText
153 | } else {
154 | ovVisText := WinGetTextFast(false)
155 | ovAllText := WinGetTextFast(true)
156 | }
157 |
158 | UpdateText("Ctrl_VisText", ovVisText)
159 | UpdateText("Ctrl_AllText", ovAllText)
160 | }
161 |
162 | ; ===========================================================================================
163 | ; WinGetText ALWAYS uses the "slow" mode - TitleMatchMode only affects
164 | ; WinText/ExcludeText parameters. In "fast" mode, GetWindowText() is used
165 | ; to retrieve the text of each control.
166 | ; ===========================================================================================
167 | WinGetTextFast(detect_hidden) {
168 | controls := WinGetControlsHwnd()
169 |
170 | static WINDOW_TEXT_SIZE := 32767 ; Defined in AutoHotkey source.
171 |
172 | buf := Buffer(WINDOW_TEXT_SIZE * 2, 0)
173 |
174 | text := ""
175 |
176 | Loop controls.Length {
177 | hCtl := controls[A_Index]
178 | if !detect_hidden && !DllCall("IsWindowVisible", "ptr", hCtl)
179 | continue
180 | if !DllCall("GetWindowText", "ptr", hCtl, "Ptr", buf.ptr, "int", WINDOW_TEXT_SIZE)
181 | continue
182 |
183 | text .= StrGet(buf) "`r`n" ; text .= buf "`r`n"
184 | }
185 | return text
186 | }
187 |
188 | ; ===========================================================================================
189 | ; Unlike using a pure GuiControl, this function causes the text of the
190 | ; controls to be updated only when the text has changed, preventing periodic
191 | ; flickering (especially on older systems).
192 | ; ===========================================================================================
193 | UpdateText(vCtl, NewText) {
194 | Global oGui
195 | static OldText := {}
196 | ctl := oGui[vCtl], hCtl := Integer(ctl.hwnd)
197 |
198 | if (!oldText.HasProp(hCtl) Or OldText.%hCtl% != NewText) {
199 | ctl.Value := NewText
200 | OldText.%hCtl% := NewText
201 | }
202 | }
203 |
204 | textMangle(x) {
205 | elli := false
206 | if (pos := InStr(x, "`n"))
207 | x := SubStr(x, 1, pos - 1), elli := true
208 | else if (StrLen(x) > 40)
209 | x := SubStr(x, 1, 40), elli := true
210 | if elli
211 | x .= " (...)"
212 | return x
213 | }
214 |
215 | suspend_timer() {
216 | Global oGui
217 | SetTimer Update, 0
218 | UpdateText("Ctrl_Freeze", oGui.txtFrozen)
219 | }
220 |
221 | ~*Shift::
222 | ~*Ctrl:: suspend_timer()
223 |
224 | ~*Ctrl up::
225 | ~*Shift up:: SetTimer Update, 250
--------------------------------------------------------------------------------
/bin/font/font.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/font/font.ttf
--------------------------------------------------------------------------------
/bin/html/AlignText.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 对齐代码注释
9 |
10 |
11 |
18 |
36 |
37 |
38 |
39 |
72 |
73 |
152 |
153 |
154 |
--------------------------------------------------------------------------------
/bin/html/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/html/logo.ico
--------------------------------------------------------------------------------
/bin/icons/logo.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/icons/logo.ico
--------------------------------------------------------------------------------
/bin/icons/logo2.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/icons/logo2.ico
--------------------------------------------------------------------------------
/bin/icons/logo3.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/icons/logo3.ico
--------------------------------------------------------------------------------
/bin/lib/InputTipWindow.ahk:
--------------------------------------------------------------------------------
1 | Class InputTipWindow {
2 |
3 | __New(text := " ", fontSize := 12, marginX := 2, marginY := 2, offsetX := 9, offsetY := 7) {
4 | ; 字体颜色
5 | FontColor := "000000"
6 | ; 背景颜色
7 | BackColor := "ffffe0"
8 |
9 | this.gui := Gui("+Owner +ToolWindow +Disabled -SysMenu -Caption +E0x1E +AlwaysOnTop +Border")
10 | this.gui.BackColor := BackColor
11 |
12 | this.gui.MarginX := marginX
13 | this.gui.MarginY := marginY
14 | this.gui.SetFont("c" FontColor " s" fontSize, "Microsoft YaHei UI")
15 |
16 | this.data := text
17 | this.textCon := this.gui.Add("text", "Center", this.data)
18 | this.offsetX := offsetX
19 | this.offsetY := offsetY
20 | FrameShadow(this.gui.Hwnd)
21 | }
22 |
23 | Show(text := "", addition := false) {
24 | ; 注意 ahk 中 "0" == 0 所以 if ("0") 比较危险, 推荐和空字符串 "" 作比较
25 | if (text != "") {
26 | if (addition) {
27 | this.data := this.data text
28 | } else {
29 | this.data := text
30 | }
31 | }
32 |
33 | this.ShowWindow()
34 | }
35 |
36 | ShowWindow() {
37 | this.textCon.Value := this.data
38 | GetPosRelativeScreen(&xpos, &ypos, "Mouse")
39 | xpos += this.offsetX
40 | ypos += this.offsetY
41 | this.gui.Show("AutoSize Center NoActivate x" xpos " y" ypos)
42 | WinSetAlwaysOnTop(true, "ahk_id " this.gui.Hwnd)
43 | }
44 |
45 | Backspace() {
46 | ; 空字符串也不会报错, SubStr 很强
47 | this.data := SubStr(this.textCon.Value, 1, -1)
48 | this.ShowWindow()
49 | }
50 |
51 | Hide() {
52 | this.gui.Hide()
53 | }
54 | }
55 |
56 | ; https://www.autohotkey.com/boards/viewtopic.php?t=29117
57 | FrameShadow(handle) {
58 | DllCall("dwmapi\DwmIsCompositionEnabled", "Int*", &enabled := false) ; Get if DWM Manager is Enabled
59 | if !enabled {
60 | return
61 | }
62 | margin := Buffer(16)
63 | NumPut("UInt", 1
64 | , "UInt", 1
65 | , "UInt", 1
66 | , "UInt", 1
67 | , margin)
68 | DllCall("dwmapi\DwmSetWindowAttribute", "Ptr", handle, "UInt", 2, "Int*", 2, "UInt", 4)
69 | DllCall("dwmapi\DwmExtendFrameIntoClientArea", "Ptr", handle, "Ptr", margin)
70 | }
--------------------------------------------------------------------------------
/bin/lib/Utils.ahk:
--------------------------------------------------------------------------------
1 | /**
2 | * 自动关闭的提示窗口
3 | * @param message 要提示的文本
4 | * @param {number} time 超时后关闭
5 | */
6 | Tip(message, time := -1500) {
7 | ToolTip(message)
8 | SetTimer(() => ToolTip(), time)
9 | }
10 |
11 | /**
12 | * 禁用输入法
13 | * @param hwnd
14 | */
15 | DisableIME(hwnd) {
16 | controlName := ControlGetFocus(hwnd)
17 | controlHwnd := ControlGetHwnd(controlName)
18 | DllCall("Imm32\ImmAssociateContext", "ptr", controlHwnd, "ptr", 0, "ptr")
19 | }
20 |
21 | /**
22 | * 获取光标的位置
23 | * 来源:https://github.com/Ixiko/AHK-libs-and-classes-collection/blob/e5e1666d016c219dc46e7fc97f2bcbf40a9c0da5/AHK_V2/Misc.ahk#L328 GetCaretPos 方法
24 | * @param X 光标相对于屏幕X轴的位置
25 | * @param Y 光标相对于屏幕Y轴的位置
26 | * @param W 光标的宽度
27 | * @param H 光标的高度
28 | * @returns {void}
29 | */
30 | GetCaretPos(&X?, &Y?, &W?, &H?) {
31 | ; UIA2 caret
32 | static IUIA := ComObject("{e22ad333-b25f-460c-83d0-0581107395c9}", "{34723aff-0c9d-49d0-9896-7ab52df8cd8a}")
33 | try {
34 | ComCall(8, IUIA, "ptr*", &FocusedEl := 0) ; GetFocusedElement
35 | rect := Buffer(16)
36 | ComCall(43, FocusedEl, "ptr", rect) ; CurrentBoundingRectangle
37 | left := NumGet(rect, 0, "int")
38 | top := NumGet(rect, 4, "int")
39 | right := NumGet(rect, 8, "int")
40 | bottom := NumGet(rect, 12, "int")
41 | ComCall(16, FocusedEl, "int", 10024, "ptr*", &patternObject := 0), ObjRelease(FocusedEl) ; GetCurrentPattern. TextPatternElement2 = 10024
42 | if patternObject {
43 | ComCall(10, patternObject, "int*", &IsActive := 1, "ptr*", &caretRange := 0), ObjRelease(patternObject) ; GetCaretRange
44 | ComCall(10, caretRange, "ptr*", &boundingRects := 0), ObjRelease(caretRange) ; GetBoundingRectangles
45 | if (Rect := ComValue(0x2005, boundingRects)).MaxIndex() = 3 { ; VT_ARRAY | VT_R8
46 | X := Round(Rect[0]), Y := Round(Rect[1]), W := Round(Rect[2]), H := Round(Rect[3])
47 | return "uia"
48 | }
49 | }
50 | }
51 |
52 | ; Acc caret
53 | static _ := DllCall("LoadLibrary", "Str", "oleacc", "Ptr")
54 | try {
55 | idObject := 0xFFFFFFF8 ; OBJID_CARET
56 | if DllCall("oleacc\AccessibleObjectFromWindow", "ptr", WinExist("A"), "uint", idObject &= 0xFFFFFFFF
57 | , "ptr", -16 + NumPut("int64", idObject == 0xFFFFFFF0 ? 0x46000000000000C0 : 0x719B3800AA000C81, NumPut("int64", idObject == 0xFFFFFFF0 ? 0x0000000000020400 : 0x11CF3C3D618736E0, IID := Buffer(16)))
58 | , "ptr*", oAcc := ComValue(9, 0)) = 0 {
59 | x := Buffer(4), y := Buffer(4), w := Buffer(4), h := Buffer(4)
60 | oAcc.accLocation(ComValue(0x4003, x.ptr, 1), ComValue(0x4003, y.ptr, 1), ComValue(0x4003, w.ptr, 1), ComValue(0x4003, h.ptr, 1), 0)
61 | X := NumGet(x, 0, "int"), Y := NumGet(y, 0, "int"), W := NumGet(w, 0, "int"), H := NumGet(h, 0, "int")
62 | if (X | Y) != 0
63 | return "acc"
64 | }
65 | }
66 |
67 | ; ahk caret
68 | savedCaret := A_CoordModeCaret
69 | CoordMode "Caret", "Screen"
70 | ok := CaretGetPos(&X, &Y)
71 | CoordMode "Caret", savedCaret
72 | if ok {
73 | return "ahk"
74 | }
75 |
76 | ; uia focused element
77 | X := left + (right - left) / 2
78 | Y := top + (bottom - top) / 2
79 | return "uia focused element"
80 | }
81 |
82 | /**
83 | * 获取指定目标相对于屏幕的位置
84 | * @param X 光标相对于屏幕X轴的位置
85 | * @param Y 光标相对于屏幕Y轴的位置
86 | * @param type Caret | Mouse
87 | */
88 | GetPosRelativeScreen(&X, &Y, type) {
89 | saved := type == "Caret" ? A_CoordModeCaret : A_CoordModeMouse
90 | CoordMode type, "Screen"
91 | type == "Caret" ? CaretGetPos(&X, &Y) : MouseGetPos(&X, &Y)
92 | CoordMode type, saved
93 | }
--------------------------------------------------------------------------------
/bin/lib/translation.ahk:
--------------------------------------------------------------------------------
1 | class DefaultTranslation {
2 | mykeymap_on := "🚀 MyKeymap: On "
3 | mykeymap_off := "⏸️ MyKeymap: Off "
4 |
5 | menu_pause := "Pause"
6 | menu_exit := "Exit"
7 | menu_reload := "Reload"
8 | menu_settings := "Settings"
9 | menu_window_spy := "Window Spy"
10 |
11 | no_items_selected := "no items selected"
12 | always_on_top_on := "Always-on-top: On"
13 | always_on_top_off := "Always-on-top: Off"
14 | copy_failed := " Copy: fail "
15 | copy_ok := " Copy: ok "
16 | mute_on := "Mute: On"
17 | mute_off := "Mute: Off"
18 | mute_falied := "Cannot mute this app"
19 | }
20 |
21 | class ChineseTranslation extends DefaultTranslation {
22 | mykeymap_on := "🚀 恢复 MyKeymap "
23 | mykeymap_off := "⏸️ 暂停 MyKeymap "
24 |
25 | menu_pause := "暂停"
26 | menu_exit := "退出"
27 | menu_reload := "重启程序"
28 | menu_settings := "打开设置"
29 | menu_window_spy := "查看窗口标识符"
30 |
31 | no_items_selected := "没有选中的文本或文件"
32 | always_on_top_on := "置顶当前窗口"
33 | always_on_top_off := "取消置顶"
34 | copy_failed := "复制失败"
35 | copy_ok := "复制成功"
36 | mute_on := "静音当前应用"
37 | mute_off := "取消静音"
38 | mute_falied := "无法静音此应用"
39 | }
40 |
41 |
42 | Translation() {
43 | static t := SysLangIsChinese() ? ChineseTranslation() : DefaultTranslation()
44 | return t
45 | }
46 |
47 | SysLangIsChinese()
48 | {
49 | ; https://www.autohotkey.com/docs/v2/misc/Languages.htm
50 | m := Map(
51 | "7804", "Chinese", ; zh
52 | "0004", "Chinese (Simplified)", ; zh-Hans
53 | "0804", "Chinese (Simplified, China)", ; zh-CN
54 | "1004", "Chinese (Simplified, Singapore)", ; zh-SG
55 | "7C04", "Chinese (Traditional)", ; zh-Hant
56 | "0C04", "Chinese (Traditional, Hong Kong SAR)", ; zh-HK
57 | "1404", "Chinese (Traditional, Macao SAR)", ; zh-MO
58 | "0404", "Chinese (Traditional, Taiwan)", ; zh-TW
59 | )
60 | return m.Get(A_Language, false)
61 | }
--------------------------------------------------------------------------------
/bin/msvcp140.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/msvcp140.dll
--------------------------------------------------------------------------------
/bin/sound/cancel.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/sound/cancel.wav
--------------------------------------------------------------------------------
/bin/sound/keydown.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/sound/keydown.wav
--------------------------------------------------------------------------------
/bin/sound/show.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/sound/show.wav
--------------------------------------------------------------------------------
/bin/sound/spaceKey.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/sound/spaceKey.wav
--------------------------------------------------------------------------------
/bin/vcruntime140.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/vcruntime140.dll
--------------------------------------------------------------------------------
/bin/vcruntime140_1.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/bin/vcruntime140_1.dll
--------------------------------------------------------------------------------
/build_tools.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bufio"
5 | "context"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "io"
10 | "log"
11 | "net/http"
12 | "os"
13 | "os/exec"
14 | "strings"
15 | "time"
16 | )
17 |
18 | func main() {
19 | m := map[string]func(args []string){
20 | "checkForAHKUpdate": checkForAHKUpdate,
21 | "updateShareLink": updateShareLink,
22 | }
23 | if f, ok := m[os.Args[1]]; ok {
24 | f(os.Args[2:])
25 | }
26 | }
27 |
28 | func checkForAHKUpdate(args []string) {
29 | stop := StartTimer("check for AHK update:")
30 | defer stop()
31 | currentVersion := args[0]
32 | version, err := getURL("https://www.autohotkey.com/download/2.0/version.txt")
33 | if err != nil {
34 | panic(err)
35 | }
36 | if version != currentVersion {
37 | fmt.Println("error: outdated ahk version")
38 | os.Exit(1)
39 | }
40 | }
41 |
42 | func updateShareLink(args []string) {
43 | f, err := os.Open("share_link.json")
44 | if err != nil {
45 | panic(err)
46 | }
47 | defer f.Close()
48 |
49 | var sl ShareLink
50 | if err = Decode(f, &sl); err != nil {
51 | panic(err)
52 | }
53 |
54 | var format string
55 | replacer := func(line string) string {
56 | if strings.Index(line, "提取码") != -1 {
57 | return fmt.Sprintf(format, args[0], sl.Url, sl.Password)
58 | }
59 | return line
60 | }
61 |
62 | format = "- [MyKeymap %s](%s) ( 提取码 %s )"
63 | if err = ReplaceInFile("./readme.md", replacer); err != nil {
64 | panic(err)
65 | }
66 |
67 | format = "- 下载地址: [MyKeymap %s](%s) ( 提取码 %s )"
68 | _ = ReplaceInFile("/mnt/d/project/my_site/docs/MyKeymap.md", replacer)
69 | }
70 |
71 | func execCmd(exe string, args ...string) {
72 | var c = exec.Command(exe, args...)
73 | c.Stdout = os.Stdout
74 | c.Stderr = os.Stderr
75 | fmt.Printf("\n%s %s\n", exe, strings.Join(args, " "))
76 | err := c.Start()
77 | if err != nil {
78 | panic(err)
79 | }
80 | err = c.Wait()
81 | if err != nil {
82 | panic(err)
83 | }
84 | }
85 |
86 | func getURL(url string) (string, error) {
87 | client := &http.Client{
88 | Timeout: 5 * time.Second,
89 | }
90 |
91 | req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
92 | if err != nil {
93 | return "", err
94 | }
95 |
96 | resp, err := client.Do(req)
97 | if err != nil {
98 | return "", err
99 | }
100 |
101 | defer resp.Body.Close()
102 | body, err := io.ReadAll(resp.Body)
103 | return string(body), err
104 | }
105 |
106 | func CreateTempFile() (*os.File, func(), error) {
107 | f, err := os.CreateTemp(".", "tmp.*.txt")
108 |
109 | cleanup := func() {
110 | _ = os.Remove(f.Name())
111 | }
112 |
113 | return f, cleanup, err
114 | }
115 |
116 | type File struct {
117 | closed bool
118 | *os.File
119 | }
120 |
121 | func (m *File) Close(err *error) {
122 | // 确保只调用一次 Close
123 | if m.closed {
124 | return
125 | }
126 | closeErr := m.File.Close()
127 | m.closed = true
128 |
129 | // 如果之前没有发生错误, 并且 Close() 时发生了错误, 那么修改返回的错误值
130 | if *err == nil {
131 | *err = closeErr
132 | }
133 | }
134 |
135 | func ReplaceInFile(filename string, handler func(string) string) (err error) {
136 | // 打开目标文件
137 | f, err := os.Open(filename)
138 | if err != nil {
139 | return err
140 | }
141 | mf := File{File: f}
142 | defer mf.Close(&err)
143 |
144 | // 创建临时文件, 用于写入
145 | f, cleanup, err := CreateTempFile()
146 | if err != nil {
147 | return err
148 | }
149 | defer cleanup()
150 | wf := File{File: f}
151 | defer wf.Close(&err)
152 |
153 | // 按行读取时, 默认 buffer 大小为 64K, 如果某行会超出 64K, 需要设置更大的 buffer
154 | // 参考此处: https://stackoverflow.com/a/16615559
155 | scanner := bufio.NewScanner(mf)
156 | writer := bufio.NewWriter(wf)
157 | for scanner.Scan() {
158 | line := scanner.Text()
159 | if _, err = writer.WriteString(handler(line) + "\n"); err != nil {
160 | return err
161 | }
162 | }
163 |
164 | // Scan 方法会在遇到错误时返回 false, 所以别忘了检查错误
165 | if err = scanner.Err(); err != nil {
166 | return err
167 | }
168 |
169 | if err = writer.Flush(); err != nil {
170 | return err
171 | }
172 |
173 | // 提前关闭两个文件 ( 因为之后要 Rename )
174 | mf.Close(&err)
175 | if err != nil {
176 | return err
177 | }
178 | wf.Close(&err)
179 | if err != nil {
180 | return err
181 | }
182 | return os.Rename(wf.Name(), mf.Name())
183 | }
184 |
185 | func StartTimer(name string) func() {
186 | t := time.Now()
187 | log.Println(name, "started")
188 | return func() {
189 | d := time.Now().Sub(t)
190 | log.Println(name, "took", d)
191 | }
192 | }
193 |
194 | type Valid interface {
195 | OK() error
196 | }
197 |
198 | type ShareLink struct {
199 | Url string `json:"url"`
200 | Password string `json:"password"`
201 | }
202 |
203 | // OK 实现了 Valid 接口, 这样在 Decode 时顺便校验数据是否合法
204 | func (s ShareLink) OK() error {
205 | if s.Url == "" {
206 | return errors.New("url required")
207 | }
208 | if s.Password == "" {
209 | return errors.New("password required")
210 | }
211 | return nil
212 | }
213 |
214 | func Decode[T any](r io.Reader, v *T) error {
215 | err := json.NewDecoder(r).Decode(v)
216 | if err != nil {
217 | return err
218 | }
219 |
220 | obj, ok := any(v).(Valid) // 注意 *T 是一个具体类型 ( 比如 *int ) 所以不能用 type assertion
221 | if !ok {
222 | return nil // 类型没有 OK 方法, 所以直接返回, 不执行 OK 方法
223 | }
224 | return obj.OK()
225 | }
226 |
--------------------------------------------------------------------------------
/config-server/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | wheels/
23 | share/python-wheels/
24 | *.egg-info/
25 | .installed.cfg
26 | *.egg
27 | MANIFEST
28 |
29 | # PyInstaller
30 | # Usually these files are written by a python script from a template
31 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
32 | *.manifest
33 | *.spec
34 |
35 | # Installer logs
36 | pip-log.txt
37 | pip-delete-this-directory.txt
38 |
39 | # Unit test / coverage reports
40 | htmlcov/
41 | .tox/
42 | .nox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | *.py,cover
50 | .hypothesis/
51 | .pytest_cache/
52 | cover/
53 |
54 | # Translations
55 | *.mo
56 | *.pot
57 |
58 | # Django stuff:
59 | *.log
60 | local_settings.py
61 | db.sqlite3
62 | db.sqlite3-journal
63 |
64 | # Flask stuff:
65 | instance/
66 | .webassets-cache
67 |
68 | # Scrapy stuff:
69 | .scrapy
70 |
71 | # Sphinx documentation
72 | docs/_build/
73 |
74 | # PyBuilder
75 | .pybuilder/
76 | target/
77 |
78 | # Jupyter Notebook
79 | .ipynb_checkpoints
80 |
81 | # IPython
82 | profile_default/
83 | ipython_config.py
84 |
85 | # pyenv
86 | # For a library or package, you might want to ignore these files since the code is
87 | # intended to run in multiple environments; otherwise, check them in:
88 | # .python-version
89 |
90 | # pipenv
91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
94 | # install all needed dependencies.
95 | #Pipfile.lock
96 |
97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
98 | __pypackages__/
99 |
100 | # Celery stuff
101 | celerybeat-schedule
102 | celerybeat.pid
103 |
104 | # SageMath parsed files
105 | *.sage.py
106 |
107 | # Environments
108 | .env
109 | .venv
110 | env/
111 | venv/
112 | ENV/
113 | env.bak/
114 | venv.bak/
115 |
116 | # Spyder project settings
117 | .spyderproject
118 | .spyproject
119 |
120 | # Rope project settings
121 | .ropeproject
122 |
123 | # mkdocs documentation
124 | /site
125 |
126 | # mypy
127 | .mypy_cache/
128 | .dmypy.json
129 | dmypy.json
130 |
131 | # Pyre type checker
132 | .pyre/
133 |
134 | # pytype static type analyzer
135 | .pytype/
136 |
137 | # Cython debug symbols
138 | cython_debug/
--------------------------------------------------------------------------------
/config-server/cmd/settings/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "github.com/gin-contrib/cors"
7 | "github.com/gin-contrib/static"
8 | "github.com/gin-gonic/gin"
9 | "io"
10 | "log"
11 | "net"
12 | "net/http"
13 | "os"
14 | "os/exec"
15 | "path/filepath"
16 | "settings/internal/command"
17 | "settings/internal/matrix"
18 | "settings/internal/script"
19 | "text/template"
20 | "time"
21 | )
22 |
23 | func main() {
24 |
25 | if len(os.Args) >= 2 {
26 | if handler, ok := command.Map[os.Args[1]]; ok {
27 | handler(os.Args[2:]...)
28 | return
29 | }
30 | }
31 |
32 | hasError := make(chan struct{})
33 | rainDone := make(chan struct{})
34 | debug := len(os.Args) == 2 && os.Args[1] == "debug"
35 |
36 | if !debug {
37 | go matrix.DigitalRain(hasError, rainDone)
38 | }
39 | if debug {
40 | hasError = nil
41 | }
42 |
43 | execCmd("./MyKeymap.exe", "/script", "./bin/MiscTools.ahk", "GenerateShortcuts")
44 | server(hasError, rainDone, debug)
45 | }
46 |
47 | func server(hasError chan<- struct{}, rainDone <-chan struct{}, debug bool) {
48 | if !debug {
49 | gin.SetMode(gin.ReleaseMode)
50 | gin.DefaultWriter = io.Discard
51 | }
52 |
53 | router := gin.Default()
54 | router.Use(PanicHandler(hasError, rainDone))
55 | if debug {
56 | router.Use(cors.Default())
57 | }
58 | router.NoRoute(static.Serve("/", static.LocalFile("./site", false)), indexHandler)
59 |
60 | router.GET("/", indexHandler)
61 | router.GET("/config", GetConfigHandler)
62 | router.PUT("/config", SaveConfigHandler(debug))
63 | router.POST("/server/command/:id", ServerCommandHandler)
64 | router.GET("/shortcuts", GetShortcutsHandler)
65 |
66 | // 先尝试 12333 端口, 失败了则用随机端口. 因为 12333 端口可能已被占用, 或者被禁:
67 | // An attempt was made to access a socket in a way forbidden by its access permissions.
68 | ln, err := net.Listen("tcp", "localhost:12333")
69 | if err != nil {
70 | ln, err = net.Listen("tcp", "localhost:0")
71 | if err != nil {
72 | close(hasError)
73 | <-rainDone
74 | fmt.Println("Error:", err.Error())
75 | _, _ = fmt.Scanln()
76 | os.Exit(1)
77 | }
78 | }
79 |
80 | if !debug {
81 | go func() {
82 | err := openBrowser(ln.Addr())
83 | if err != nil {
84 | hasError <- struct{}{}
85 | <-rainDone
86 | fmt.Println("Error:", err.Error())
87 | }
88 | }()
89 | }
90 |
91 | err = router.RunListener(ln)
92 | if err != nil {
93 | close(hasError)
94 | <-rainDone
95 | log.Fatal(err)
96 | }
97 | }
98 |
99 | //goland:noinspection HttpUrlsUsage
100 | func openBrowser(addr net.Addr) error {
101 | time.Sleep(600 * time.Millisecond)
102 | if addr, ok := addr.(*net.TCPAddr); ok {
103 | // _ = exec.Command("cmd", "/c", "start", fmt.Sprintf("http://localhost:%d", addr.Port)).Start()
104 | return exec.Command("C:\\Windows\\System32\\rundll32.exe", "url.dll,FileProtocolHandler", fmt.Sprintf("http://localhost:%d", addr.Port)).Start()
105 | }
106 | return errors.New("addr is not tcp")
107 | }
108 |
109 | func indexHandler(c *gin.Context) {
110 | data, err := os.ReadFile("./site/index.html")
111 | if err != nil {
112 | panic(err)
113 | }
114 | // 设置 Cache-Control: no-store 禁用缓存
115 | c.Header("Cache-Control", "no-store")
116 | c.Data(http.StatusOK, "text/html; charset=utf-8", data)
117 | }
118 |
119 | func GetConfigHandler(c *gin.Context) {
120 | config, err := script.ParseConfig("../data/config.json")
121 | if err != nil {
122 | panic(err)
123 | }
124 | c.JSON(http.StatusOK, config)
125 | }
126 |
127 | func GetShortcutsHandler(c *gin.Context) {
128 | type shortcut struct {
129 | Path string `json:"path"`
130 | }
131 | exe, err := os.Executable()
132 | if err != nil {
133 | panic(err)
134 | }
135 | root := filepath.Dir(filepath.Dir(exe))
136 | pattern := filepath.Join(root, "shortcuts", "*.lnk")
137 |
138 | files, err := filepath.Glob(pattern)
139 | if err != nil {
140 | panic(err)
141 | }
142 | var data []shortcut
143 | for _, f := range files {
144 | data = append(data, shortcut{
145 | Path: f[len(root)+1:],
146 | })
147 | }
148 | c.JSON(http.StatusOK, data)
149 | }
150 |
151 | func PanicHandler(hasError chan<- struct{}, rainDone <-chan struct{}) gin.HandlerFunc {
152 | return func(c *gin.Context) {
153 | defer func() {
154 | if err := recover(); err != nil {
155 | // 不允许重复 close channel
156 | if hasError != nil {
157 | close(hasError)
158 | <-rainDone
159 | hasError = nil
160 | }
161 | panic(err)
162 | }
163 | }()
164 |
165 | c.Next()
166 | }
167 | }
168 |
169 | func ServerCommandHandler(c *gin.Context) {
170 | m := map[string]struct {
171 | exe string
172 | args []string
173 | }{
174 | "2": {
175 | exe: "./MyKeymap.exe",
176 | args: []string{"/script", "bin/WindowSpy.ahk"},
177 | },
178 | "3": {
179 | exe: "./MyKeymap.exe",
180 | args: []string{"/script", "./bin/MiscTools.ahk", "RunAtStartup", "On"},
181 | },
182 | "4": {
183 | exe: "./MyKeymap.exe",
184 | args: []string{"/script", "./bin/MiscTools.ahk", "RunAtStartup", "Off"},
185 | },
186 | }
187 | if c, ok := m[c.Param("id")]; ok {
188 | execCmd(c.exe, c.args...)
189 | }
190 |
191 | c.JSON(http.StatusOK, gin.H{})
192 | }
193 |
194 | func execCmd(exe string, args ...string) {
195 | // 切换到 parent 文件夹, 执行完 command 后再回来
196 | // 程序工作目录算全局共享状态, 所以会影响到其他 goroutine
197 | wd, err := os.Getwd()
198 | if err == nil {
199 | base := filepath.Base(wd)
200 | err := os.Chdir("../")
201 | if err == nil {
202 | defer func() {
203 | _ = os.Chdir(base)
204 | }()
205 | }
206 | }
207 |
208 | var c = exec.Command(exe, args...)
209 | err = c.Start()
210 | if err != nil {
211 | }
212 | }
213 |
214 | func SaveConfigHandler(debug bool) gin.HandlerFunc {
215 | return func(c *gin.Context) {
216 | var config script.Config
217 | if err := c.ShouldBindJSON(&config); err != nil {
218 | panic(err)
219 | }
220 |
221 | // 生成帮助文件
222 | // helpPageHtml := config["helpPageHtml"].(string)
223 | // delete(config, "helpPageHtml")
224 | // saveHelpPageHtml(helpPageHtml)
225 |
226 | script.SaveConfigFile(&config) // 保存配置文件
227 |
228 | if debug {
229 | script.GenerateScripts(&config) // 生成脚本文件
230 | execCmd("./MyKeymap.exe") // 重启程序, 此时 launcher 会重新生成脚本
231 | // execCmd("./MyKeymap.exe", "./bin/MyKeymap.ahk") // 重启程序且跳过 ahk 脚本生成
232 | } else {
233 | execCmd("./MyKeymap.exe") // 重启程序, 此时 launcher 会重新生成脚本
234 | }
235 |
236 | c.JSON(http.StatusOK, gin.H{"message": "ok"})
237 | }
238 | }
239 |
240 | func saveHelpPageHtml(html string) {
241 |
242 | f, err := os.Create("../bin/site/help.html")
243 | if err != nil {
244 | panic(err)
245 | }
246 | defer func(f *os.File) {
247 | _ = f.Close()
248 | }(f)
249 |
250 | files := []string{
251 | "./templates/help.html",
252 | }
253 |
254 | ts, err := template.ParseFiles(files...)
255 | if err != nil {
256 | panic(err)
257 | }
258 | err = ts.Execute(f, map[string]string{"helpPageHtml": html})
259 | if err != nil {
260 | panic(err)
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/config-server/go.mod:
--------------------------------------------------------------------------------
1 | module settings
2 |
3 | go 1.23
4 |
5 | require (
6 | github.com/gdamore/tcell/v2 v2.8.1
7 | github.com/gin-contrib/cors v1.7.3
8 | github.com/gin-contrib/static v1.1.3
9 | github.com/gin-gonic/gin v1.10.0
10 | )
11 |
12 | require (
13 | github.com/bytedance/sonic v1.12.8 // indirect
14 | github.com/bytedance/sonic/loader v0.2.3 // indirect
15 | github.com/cloudwego/base64x v0.1.5 // indirect
16 | github.com/gabriel-vasile/mimetype v1.4.8 // indirect
17 | github.com/gdamore/encoding v1.0.1 // indirect
18 | github.com/gin-contrib/sse v1.0.0 // indirect
19 | github.com/go-playground/locales v0.14.1 // indirect
20 | github.com/go-playground/universal-translator v0.18.1 // indirect
21 | github.com/go-playground/validator/v10 v10.24.0 // indirect
22 | github.com/goccy/go-json v0.10.5 // indirect
23 | github.com/json-iterator/go v1.1.12 // indirect
24 | github.com/klauspost/cpuid/v2 v2.2.9 // indirect
25 | github.com/kr/text v0.2.0 // indirect
26 | github.com/leodido/go-urn v1.4.0 // indirect
27 | github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
28 | github.com/mattn/go-isatty v0.0.20 // indirect
29 | github.com/mattn/go-runewidth v0.0.16 // indirect
30 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
31 | github.com/modern-go/reflect2 v1.0.2 // indirect
32 | github.com/pelletier/go-toml/v2 v2.2.3 // indirect
33 | github.com/rivo/uniseg v0.4.7 // indirect
34 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
35 | github.com/ugorji/go/codec v1.2.12 // indirect
36 | golang.org/x/arch v0.14.0 // indirect
37 | golang.org/x/crypto v0.33.0 // indirect
38 | golang.org/x/net v0.34.0 // indirect
39 | golang.org/x/sys v0.30.0 // indirect
40 | golang.org/x/term v0.29.0 // indirect
41 | golang.org/x/text v0.22.0 // indirect
42 | google.golang.org/protobuf v1.36.5 // indirect
43 | gopkg.in/yaml.v3 v3.0.1 // indirect
44 | )
45 |
--------------------------------------------------------------------------------
/config-server/internal/command/command.go:
--------------------------------------------------------------------------------
1 | package command
2 |
3 | import (
4 | "errors"
5 | "fmt"
6 | "io/ioutil"
7 | "log"
8 | "os"
9 | "os/exec"
10 | "path/filepath"
11 | "settings/internal/script"
12 | "strings"
13 | "text/template"
14 | )
15 |
16 | var Map = map[string]func(args ...string){
17 | "AlignText": AlignText,
18 | "GenerateAHK": GenerateAHK,
19 | "ChangeVersion": ChangeVersion,
20 | "GenerateScripts": GenerateScripts,
21 | "UseOriginalAHK": UseOriginalAHK,
22 | }
23 |
24 | var logger = log.New(os.Stderr, "", 0)
25 |
26 | func GenerateAHK(args ...string) {
27 | if len(os.Args) < 5 {
28 | logger.Fatal("GenerateAHK requires 3 arguments, for example: GenerateAHK ./config.json ./templates/script.ahk ./output.ahk")
29 | }
30 | configFile := os.Args[2]
31 | templateFile := os.Args[3]
32 | outputFile := os.Args[4]
33 |
34 | config, err := script.ParseConfig(configFile)
35 | if err != nil {
36 | logger.Fatal(err)
37 | }
38 |
39 | if err := script.SaveAHK(config, templateFile, outputFile); err != nil {
40 | logger.Fatal(err)
41 | }
42 | }
43 |
44 | func ChangeVersion(args ...string) {
45 | config, err := script.ParseConfig("../data/config.json")
46 | if err != nil {
47 | panic(err)
48 | }
49 | config.Options.MykeymapVersion = args[0]
50 | config.Options.Language = "" // 重置语言
51 | script.SaveConfigFile(config)
52 | script.GenerateScripts(config)
53 | }
54 |
55 | func GenerateScripts(args ...string) {
56 | config, err := script.ParseConfig("../data/config.json")
57 | if err != nil {
58 | panic(err)
59 | }
60 | script.GenerateScripts(config)
61 | }
62 |
63 | func UseOriginalAHK(args ...string) {
64 | defer func() {
65 | fmt.Println()
66 | }()
67 |
68 | exe := "bin\\AutoHotkey64.exe"
69 | if _, err := os.Stat(exe); errors.Is(err, os.ErrNotExist) {
70 | fmt.Println("Error: file", exe, "does not exist")
71 | return
72 | }
73 | if err := execCmd("cmd.exe", "/c", "copy /y "+exe+" MyKeymap.exe"); err != nil {
74 | fmt.Println("\nPlease close MyKeymap and retry")
75 | return
76 | }
77 | if err := execCmd("cmd.exe", "/c", "copy /y bin\\Launcher.ahk MyKeymap.ahk"); err != nil {
78 | panic(err)
79 | return
80 | }
81 | fmt.Println("\ndone!")
82 | }
83 |
84 | func execCmd(exe string, args ...string) error {
85 | cmd := exec.Command(exe, args...)
86 | cmd.Stdout = os.Stdout
87 | cmd.Stderr = os.Stderr
88 | if err := cmd.Start(); err != nil {
89 | return err
90 | }
91 | if err := cmd.Wait(); err != nil {
92 | return err
93 | }
94 | return nil
95 | }
96 |
97 | func AlignText(args ...string) {
98 | text, err := readStdin()
99 | if err != nil {
100 | fmt.Println(err)
101 | return
102 | }
103 | r := strings.NewReplacer("`", "\\`", "\\", "\\\\")
104 | data := map[string]interface{}{
105 | "Text": r.Replace(text),
106 | }
107 | executeTemplate(data, "./html-tools/AlignText.tmpl.html", "./html-tools/AlignText.html")
108 | }
109 |
110 | func readStdin() (string, error) {
111 | fi, _ := os.Stdin.Stat()
112 | if (fi.Mode() & os.ModeCharDevice) == 0 {
113 | // 这个分支表示 stdin 的数据来自管道
114 | } else {
115 | return "", errors.New("需要从管道读取数据")
116 | }
117 |
118 | bytes, err := ioutil.ReadAll(os.Stdin)
119 | if err != nil {
120 | return "", err
121 | }
122 | // CRLF 转换
123 | r := string(bytes)
124 | if strings.Index(r, "\r\n") != -1 {
125 | r = strings.ReplaceAll(r, "\r\n", "\n")
126 | }
127 | return r, nil
128 | }
129 |
130 | func executeTemplate(data map[string]interface{}, templateFile, outputFile string) {
131 |
132 | f, err := os.Create(outputFile)
133 | if err != nil {
134 | panic(err)
135 | }
136 | defer func(f *os.File) {
137 | _ = f.Close()
138 | }(f)
139 |
140 | files := []string{
141 | templateFile,
142 | }
143 |
144 | ts, err := template.New(filepath.Base(templateFile)).
145 | Funcs(script.TemplateFuncMap).Delims("{%", "%}").ParseFiles(files...)
146 | if err != nil {
147 | panic(err)
148 | }
149 |
150 | err = ts.Execute(f, data)
151 | if err != nil {
152 | panic(err)
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/config-server/internal/matrix/matrix.go:
--------------------------------------------------------------------------------
1 | package matrix
2 |
3 | import (
4 | "github.com/gdamore/tcell/v2"
5 | "math/rand"
6 | "os"
7 | "strings"
8 | "time"
9 | )
10 |
11 | func DigitalRain(hasError <-chan struct{}, rainDone chan<- struct{}) {
12 | defer close(rainDone)
13 |
14 | // init screen
15 | s, _ := tcell.NewScreen()
16 | _ = s.Init()
17 | defStyle := tcell.StyleDefault.Background(tcell.ColorReset).Foreground(tcell.ColorGreen)
18 | s.SetStyle(defStyle)
19 | s.Clear()
20 |
21 | quit := func() {
22 | s.Fini()
23 | os.Exit(0)
24 | }
25 |
26 | nodes := make([]node, 0, 100)
27 | width, height := s.Size()
28 | columns := initColumns(width, height)
29 | startTime := time.Now()
30 |
31 | outer:
32 | for {
33 | select {
34 | case <-hasError:
35 | s.Fini()
36 | break outer
37 | default:
38 | // 刷新率
39 | time.Sleep(34 * time.Millisecond)
40 | }
41 |
42 | for _, col := range columns {
43 | // 让计时器减一, 计时器为 0 时产生新节点, 并重置计时器
44 | if col.Timer == 0 {
45 | n := col.spawnNode(col.X, height)
46 | nodes = append(nodes, n)
47 | }
48 | col.Timer -= 1
49 | }
50 |
51 | // 绘制节点, 并把节点下移一行
52 | for i := range nodes {
53 | drawNode(&nodes[i], s, defStyle)
54 | nodes[i].Y++
55 | }
56 |
57 | nodes = filter(nodes, func(n node) bool {
58 | return n.Y < height+1
59 | })
60 |
61 | // 打印横幅
62 | banner := []string{
63 | " ------------------------------------------------------------------ ",
64 | " ",
65 | " MyKeymap config server is running... ",
66 | " ",
67 | " ------------------------------------------------------------------ ",
68 | }
69 | style := defStyle.Foreground(tcell.ColorWhite)
70 | for index, line := range banner {
71 | // 按一定速度打印第三行文字
72 | if index == 2 {
73 | text := strings.TrimLeft(line, " ")
74 | prefix := line[:len(line)-len(text)]
75 | distance := time.Since(startTime).Milliseconds() / 50
76 | text2 := text[:distance%int64(len(text))]
77 | suffix := strings.Repeat(" ", len(line)-len(prefix)-len(text2))
78 | line = prefix + text2 + suffix
79 | }
80 | drawText(s, 24, 5+index, 100, 100, style, line)
81 | }
82 |
83 | // Update screen
84 | s.Show()
85 |
86 | // Poll event
87 | if s.HasPendingEvent() {
88 | ev := s.PollEvent()
89 |
90 | // Process event
91 | switch ev := ev.(type) {
92 | case *tcell.EventResize:
93 | s.Sync()
94 | width, height = s.Size()
95 | columns = initColumns(width, height)
96 | case *tcell.EventKey:
97 | if ev.Key() == tcell.KeyEscape || ev.Key() == tcell.KeyCtrlC {
98 | quit()
99 | }
100 | }
101 | }
102 | }
103 | }
104 |
105 | func drawText(s tcell.Screen, x1, y1, x2, y2 int, style tcell.Style, text string) {
106 | // To draw text more easily, define a render function.
107 | row := y1
108 | col := x1
109 | for _, r := range []rune(text) {
110 | s.SetContent(col, row, r, nil, style)
111 | col++
112 | // col 在 x2 处换行
113 | if col >= x2 {
114 | row++
115 | col = x1
116 | }
117 | // 超出的文本不展示
118 | if row > y2 {
119 | break
120 | }
121 | }
122 | }
123 |
124 | func filter(nodes []node, f func(node) bool) []node {
125 | var result []node
126 | for i := range nodes {
127 | if f(nodes[i]) {
128 | result = append(result, nodes[i])
129 | }
130 | }
131 | return result
132 | }
133 |
134 | func drawNode(n *node, s tcell.Screen, style tcell.Style) {
135 |
136 | character := ' '
137 | color := tcell.ColorGreen
138 |
139 | // 1/2 节点是纯绿色, 1/2 节点带彩色,
140 | if n.Type == writer {
141 | character = getChar()
142 | if n.Colored {
143 | color = tcell.ColorYellow
144 | }
145 | }
146 |
147 | s.SetContent(n.X, n.Y, character, nil, style.Foreground(color))
148 |
149 | if n.Colored {
150 | if n.LastChar != 0 {
151 | // 三分之一的概率是亮绿色
152 | if rand.Intn(100) < 33 {
153 | color = tcell.Color46
154 | } else {
155 | color = tcell.ColorGreen
156 | }
157 | // 彩色节点的上一个格子需要从彩色改回绿色
158 | s.SetContent(n.X, n.Y-1, character, nil, style.Foreground(color))
159 | }
160 | n.LastChar = character
161 | }
162 | }
163 |
164 | func getChar() rune {
165 | const a = `ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン1234567890`
166 | const b = `1234567890-=*_+|:<>"-=*_+|:<>"-=*_+|:<>"-=*_+|:<>"`
167 | var charSet = []rune(a + b)
168 | return charSet[rand.Intn(len(charSet))]
169 | }
170 |
171 | func initColumns(width, height int) []*column {
172 | columns := make([]*column, 0, 100)
173 | for i := 0; i < width; i += 5 {
174 | col := column{
175 | Timer: 1 + rand.Intn(height-1),
176 | X: i,
177 | }
178 | columns = append(columns, &col)
179 | }
180 | return columns
181 | }
182 |
183 | func (c *column) spawnNode(x, rowCount int) node {
184 |
185 | nt := eraser
186 | colored := false
187 | c.Timer = 1 + rand.Intn(rowCount-1)
188 |
189 | if c.NextNodeType == writer {
190 | nt = writer
191 | if rand.Intn(2) == 0 {
192 | colored = true
193 | }
194 | c.Timer = 4 + rand.Intn(rowCount-7)
195 | c.NextNodeType = eraser
196 | } else {
197 | c.NextNodeType = writer
198 | }
199 |
200 | return node{
201 | X: x,
202 | Type: nt,
203 | Colored: colored,
204 | }
205 | }
206 |
207 | type nodeType int
208 |
209 | const (
210 | writer nodeType = iota
211 | eraser
212 | )
213 |
214 | type node struct {
215 | X int
216 | Y int
217 | Type nodeType
218 | Colored bool
219 | LastChar rune
220 | }
221 |
222 | type column struct {
223 | X int
224 | Timer int
225 | NextNodeType nodeType
226 | }
227 |
--------------------------------------------------------------------------------
/config-server/internal/script/abbr.go:
--------------------------------------------------------------------------------
1 | package script
2 |
3 | import (
4 | "sort"
5 | "strings"
6 | )
7 |
8 | func (c *Config) CapslockAbbr() map[string][]Action {
9 | for _, km := range c.Keymaps {
10 | if km.Hotkey == "capslockAbbr" {
11 | return km.Hotkeys
12 | }
13 | }
14 | return make(map[string][]Action)
15 | }
16 |
17 | func (c *Config) SemicolonAbbr() map[string][]Action {
18 | for _, km := range c.Keymaps {
19 | if km.Hotkey == "semicolonAbbr" {
20 | return km.Hotkeys
21 | }
22 | }
23 | return make(map[string][]Action)
24 | }
25 |
26 | func (c *Config) CapslockAbbrEnabled() bool {
27 | for _, km := range c.Keymaps {
28 | if !km.Enable {
29 | continue
30 | }
31 | for _, actions := range km.Hotkeys {
32 | for _, a := range actions {
33 | if a.TypeID == 9 && a.ValueID == 6 {
34 | return true
35 | }
36 | }
37 | }
38 | }
39 | return false
40 | }
41 |
42 | func (c *Config) CapslockAbbrKeys() string {
43 | var keys []string
44 | for key := range c.CapslockAbbr() {
45 | keys = append(keys, strings.ReplaceAll(key, ",", ",,"))
46 | }
47 | sort.Slice(keys, func(i, j int) bool {
48 | return keys[i] < keys[j]
49 | })
50 | return strings.Join(keys, ",")
51 | }
52 |
53 | func (c *Config) SemicolonAbbrEnabled() bool {
54 | for _, km := range c.Keymaps {
55 | if !km.Enable {
56 | continue
57 | }
58 | for _, actions := range km.Hotkeys {
59 | for _, a := range actions {
60 | if a.TypeID == 9 && a.ValueID == 5 {
61 | return true
62 | }
63 | }
64 | }
65 | }
66 | return false
67 | }
68 |
69 | func (c *Config) SemicolonAbbrKeys() string {
70 | var keys []string
71 | for key := range c.SemicolonAbbr() {
72 | keys = append(keys, strings.ReplaceAll(key, ",", ",,"))
73 | }
74 | sort.Slice(keys, func(i, j int) bool {
75 | return keys[i] < keys[j]
76 | })
77 | return strings.Join(keys, ",")
78 | }
79 |
--------------------------------------------------------------------------------
/config-server/internal/script/action.go:
--------------------------------------------------------------------------------
1 | package script
2 |
3 | import (
4 | "fmt"
5 | "sort"
6 | "strings"
7 | )
8 |
9 | // Cfg 在 SaveAHK 中初始化, 会被下面的转换函数用到
10 | var Cfg *Config
11 |
12 | const remapKey = 5
13 |
14 | var actionMap = map[int]func(Action, bool) string{
15 | 1: activateOrRun1,
16 | 2: systemActions2,
17 | 3: windowActions3,
18 | 4: mouseActions4,
19 | remapKey: remapKey5,
20 | 6: sendKeys6,
21 | 7: textFeatures7,
22 | 8: builtinFunctions8,
23 | 9: mykeymapActions9,
24 | }
25 |
26 | func actionToHotkey(action Action) string {
27 | if f, ok := actionMap[action.TypeID]; ok {
28 | return f(action, false)
29 | }
30 | return ""
31 | }
32 |
33 | // TODO 缩写功能中不支持的操作:
34 | // 重映射按键, 锁定当前模式, 暂停重启锁定, 鼠标操作, 窗口操作, ....
35 | // 基本上只支持发送按键, 启动程序, 执行内置函数, 系统控制
36 | func abbrToCode(abbrMap map[string][]Action) string {
37 | // map 结构转成 list, 然后进行排序
38 | type Abbr struct {
39 | abbr string
40 | actions []Action
41 | }
42 | var abbrList []Abbr
43 | for abbr, actions := range abbrMap {
44 | actions = sortActions(actions)
45 | abbrList = append(abbrList, Abbr{abbr, actions})
46 | }
47 | sort.Slice(abbrList, func(i, j int) bool {
48 | return abbrList[i].abbr < abbrList[j].abbr
49 | })
50 |
51 | var s strings.Builder
52 | for _, abbr := range abbrList {
53 | s.WriteString(fmt.Sprintf(" case \"%s\":\n", abbr.abbr))
54 | for _, a := range abbr.actions {
55 | if _, ok := actionMap[a.TypeID]; !ok {
56 | continue
57 | }
58 |
59 | call := actionMap[a.TypeID](a, true)
60 | winTitle, conditionType := Cfg.GetWinTitle(a)
61 | if conditionType == 5 {
62 | winTitle = strings.Trim(winTitle, "'")
63 | }
64 | if a.WindowGroupID == 0 {
65 | s.WriteString(fmt.Sprintf(" %s\n", call))
66 | continue
67 | }
68 | s.WriteString(fmt.Sprintf(" if matchWinTitleCondition(%s, %d) {\n", winTitle, conditionType))
69 | s.WriteString(fmt.Sprintf(" %s\n", call))
70 | s.WriteString(fmt.Sprintf(" return\n"))
71 | s.WriteString(fmt.Sprintf(" }\n"))
72 | }
73 | }
74 | return s.String()
75 | }
76 |
77 | func sortActions(actions []Action) []Action {
78 | // 把 a.WindowGroupID 为 0 的放在最后面, 因为这是默认生效的动作, 优先级最低
79 | res := make([]Action, 0, len(actions))
80 | var suffix []Action
81 | for _, a := range actions {
82 | if a.WindowGroupID == 0 {
83 | suffix = append(suffix, a)
84 | } else {
85 | res = append(res, a)
86 | }
87 | }
88 | res = append(res, suffix...)
89 | return res
90 | }
91 |
92 | func toAHKFuncArg(val string) string {
93 | prefix := "ahk-expression:"
94 | if strings.HasPrefix(val, prefix) {
95 | val = val[len(prefix):]
96 | return strings.TrimSpace(val)
97 | }
98 | return ahkString(val)
99 | }
100 |
101 | func remapKey5(a Action, inAbbrContext bool) string {
102 | if strings.ToLower(a.Hotkey) == "singlepress" {
103 | a.KeysToSend = "{blind}{" + a.RemapToKey + "}"
104 | return sendKeys6(a, inAbbrContext)
105 | }
106 | key := strings.TrimLeft(a.Hotkey, "*")
107 | ctx := Cfg.GetHotkeyContext(a)
108 | if ctx != "" {
109 | ctx = ctx[2:]
110 | }
111 | f := "RemapKey"
112 | if a.RemapInHotIf {
113 | f = "RemapInHotIf"
114 | }
115 | return fmt.Sprintf(`km.%s("%s", %s%s)`, f, key, toAHKFuncArg(a.RemapToKey), ctx)
116 | }
117 |
118 | func sendKeys6(a Action, inAbbrContext bool) string {
119 | // 有多行, 每行转成一个 send 调用, 然后用逗号 , 连起来
120 | var res []string
121 | lines := strings.Split(a.KeysToSend, "\n")
122 | for _, line := range lines {
123 | if len(strings.TrimSpace(line)) == 0 {
124 | continue
125 | }
126 | if strings.HasPrefix(line, "ahk:") {
127 | res = append(res, strings.TrimSpace(line[4:]))
128 | continue
129 | }
130 | if strings.HasPrefix(line, "sleep ") || strings.HasPrefix(line, "Sleep ") {
131 | res = append(res, fmt.Sprintf(`Sleep(%s)`, line[6:]))
132 | continue
133 | }
134 | line = toAHKFuncArg(line)
135 | res = append(res, fmt.Sprintf(`Send(%s)`, line))
136 | }
137 | if len(res) == 0 {
138 | return ""
139 | }
140 |
141 | call := strings.Join(res, ", ")
142 |
143 | if inAbbrContext {
144 | return call
145 | }
146 | return fmt.Sprintf(`km.Map("%[1]s", _ => (%s)%s)`, a.Hotkey, call, Cfg.GetHotkeyContext(a))
147 | }
148 |
149 | func mykeymapActions9(a Action, inAbbrContext bool) string {
150 | ctx := Cfg.GetHotkeyContext(a)
151 | callMap := map[int]string{
152 | 1: `MyKeymapToggleSuspend()`,
153 | 2: `MyKeymapReload()`,
154 | 3: `MyKeymapExit()`,
155 | 4: `MyKeymapOpenSettings()`,
156 | 5: `EnterSemicolonAbbr(semiHook, semiHookAbbrWindow)`,
157 | 6: `EnterCapslockAbbr(capsHook)`,
158 | 7: `ToggleCapslock()`,
159 | 8: `km.ToggleLock`,
160 | }
161 |
162 | call, ok := callMap[a.ValueID]
163 | if !ok {
164 | return ""
165 | }
166 |
167 | if inAbbrContext {
168 | return call
169 | }
170 |
171 | if a.ValueID == 1 || a.ValueID == 2 {
172 | if ctx == "" {
173 | ctx += ", , , "
174 | }
175 | return fmt.Sprintf(`km.Map("%[1]s", _ => %s%s, "S")`, a.Hotkey, call, ctx)
176 | }
177 | if a.ValueID == 8 {
178 | return fmt.Sprintf(`km.Map("%[1]s", %s%s)`, a.Hotkey, call, ctx)
179 | }
180 |
181 | return fmt.Sprintf(`km.Map("%[1]s", _ => %s%s)`, a.Hotkey, call, ctx)
182 | }
183 |
184 | func mouseActions4(a Action, inAbbrContext bool) string {
185 | winTitle, conditionType := Cfg.GetWinTitle(a)
186 | ctx := Cfg.GetHotkeyContext(a)
187 | valueMap := map[int]string{
188 | 1: `km.Map("%[1]s", fast.MoveMouseUp, slow%[2]s), slow.Map("%[1]s", slow.MoveMouseUp%[3]s)`,
189 | 2: `km.Map("%[1]s", fast.MoveMouseDown, slow%[2]s), slow.Map("%[1]s", slow.MoveMouseDown%[3]s)`,
190 | 3: `km.Map("%[1]s", fast.MoveMouseLeft, slow%[2]s), slow.Map("%[1]s", slow.MoveMouseLeft%[3]s)`,
191 | 4: `km.Map("%[1]s", fast.MoveMouseRight, slow%[2]s), slow.Map("%[1]s", slow.MoveMouseRight%[3]s)`,
192 |
193 | 5: `km.Map("%[1]s", fast.ScrollWheelUp%s), slow.Map("%[1]s", slow.ScrollWheelUp%s)`,
194 | 6: `km.Map("%[1]s", fast.ScrollWheelDown%s), slow.Map("%[1]s", slow.ScrollWheelDown%s)`,
195 | 7: `km.Map("%[1]s", fast.ScrollWheelLeft%s), slow.Map("%[1]s", slow.ScrollWheelLeft%s)`,
196 | 8: `km.Map("%[1]s", fast.ScrollWheelRight%s), slow.Map("%[1]s", slow.ScrollWheelRight%s)`,
197 |
198 | 9: `km.Map("%[1]s", fast.LButton()%s), slow.Map("%[1]s", slow.LButton()%s)`,
199 | 10: `km.Map("%[1]s", fast.RButton()%s), slow.Map("%[1]s", slow.RButton()%s)`,
200 | 11: `km.Map("%[1]s", fast.MButton()%s), slow.Map("%[1]s", slow.MButton()%s)`,
201 | 12: `km.Map("%[1]s", fast.LButtonDown()%s), slow.Map("%[1]s", slow.LButtonDown()%s)`,
202 | 13: `km.Map("%[1]s", _ => MoveMouseToCaret()%s), slow.Map("%[1]s", _ => MoveMouseToCaret()%s)`,
203 | }
204 | if format, ok := valueMap[a.ValueID]; ok {
205 | if a.ValueID >= 5 {
206 | return fmt.Sprintf(format, a.Hotkey, ctx)
207 | }
208 | fastSuffix := fmt.Sprintf(", %s, %d", winTitle, conditionType)
209 | slowSuffix := fmt.Sprintf(", , %s, %d", winTitle, conditionType)
210 | if winTitle == `""` && conditionType == 0 {
211 | fastSuffix = ""
212 | slowSuffix = ""
213 | }
214 | return fmt.Sprintf(format, a.Hotkey, fastSuffix, slowSuffix)
215 | }
216 | return ""
217 | }
218 |
219 | func windowActions3(a Action, inAbbrContext bool) string {
220 | if a.ValueID == 4 {
221 | return fmt.Sprintf(`km.Map("%[1]s", _ => Send("^!{tab}"), taskSwitch%s)`, a.Hotkey, Cfg.GetHotkeyContext(a))
222 | }
223 | if a.ValueID == 14 {
224 | return fmt.Sprintf(`km.Map("%[1]s", BindWindow()%s)`, a.Hotkey, Cfg.GetHotkeyContext(a))
225 | }
226 | callMap := map[int]string{
227 | 1: `SmartCloseWindow()`,
228 | 2: `GoToLastWindow()`,
229 | 3: `LoopRelatedWindows()`,
230 | 5: `GoToPreviousVirtualDesktop()`,
231 | 6: `GoToNextVirtualDesktop()`,
232 | 7: `MoveWindowToNextMonitor()`,
233 | 8: `MinimizeWindow()`,
234 | 9: `MaximizeWindow()`,
235 | 10: `CenterAndResizeWindow(1200, 800)`,
236 | 11: `CenterAndResizeWindow(1370, 930)`,
237 | 12: `ToggleWindowTopMost()`,
238 | 13: `MakeWindowDraggable()`,
239 | 15: `CloseWindowProcesses()`,
240 | 16: `CloseSameClassWindows()`,
241 | }
242 | if call, ok := callMap[a.ValueID]; ok {
243 | if inAbbrContext {
244 | return call
245 | }
246 | return fmt.Sprintf(`km.Map("%[1]s", _ => %s%s)`, a.Hotkey, call, Cfg.GetHotkeyContext(a))
247 | }
248 | return ""
249 | }
250 |
251 | func systemActions2(a Action, inAbbrContext bool) string {
252 | callMap := map[int]string{
253 | 1: `SystemLockScreen()`,
254 | 2: `SystemSleep()`,
255 | 3: `SystemShutdown()`,
256 | 4: `SystemReboot()`,
257 | 5: `SoundControl()`,
258 | 6: `BrightnessControl()`,
259 | 7: `SystemRestartExplorer()`,
260 | 8: `CopySelectedAsPlainText()`,
261 | 9: `MuteActiveApp()`,
262 | 10: `ShowActiveProcessInFolder()`,
263 | }
264 | if call, ok := callMap[a.ValueID]; ok {
265 | if inAbbrContext {
266 | return call
267 | }
268 | return fmt.Sprintf(`km.Map("%[1]s", _ => %s%s)`, a.Hotkey, call, Cfg.GetHotkeyContext(a))
269 | }
270 | return ""
271 | }
272 |
273 | func activateOrRun1(a Action, inAbbrContext bool) string {
274 | ctx := Cfg.GetHotkeyContext(a)
275 | winTitle := toAHKFuncArg(a.WinTitle)
276 | target := toAHKFuncArg(a.Target)
277 | args := toAHKFuncArg(a.Args)
278 | workingDir := toAHKFuncArg(a.WorkingDir)
279 |
280 | call := fmt.Sprintf(`ActivateOrRun(%s, %s, %s, %s, %t, %t, %t)`, winTitle, target, args, workingDir, a.RunAsAdmin, a.DetectHiddenWindow, a.RunInBackground)
281 | if args == `""` && workingDir == `""` && !a.RunAsAdmin && !a.DetectHiddenWindow && !a.RunInBackground {
282 | call = fmt.Sprintf(`ActivateOrRun(%s, %s)`, winTitle, target)
283 | }
284 |
285 | if inAbbrContext {
286 | return call
287 | }
288 |
289 | return fmt.Sprintf(`km.Map("%[1]s", _ => %s%s)`, a.Hotkey, call, ctx)
290 | }
291 |
292 | func builtinFunctions8(a Action, inAbbrContext bool) string {
293 | if inAbbrContext {
294 | return a.AHKCode
295 | }
296 | return fmt.Sprintf(`km.Map("%[1]s", _ => %s%s)`, a.Hotkey, a.AHKCode, Cfg.GetHotkeyContext(a))
297 | }
298 |
299 | func textFeatures7(a Action, inAbbrContext bool) string {
300 |
301 | m := map[int]struct {
302 | Type string
303 | Value string
304 | }{
305 | 1: {"remap", "up"},
306 | 2: {"remap", "down"},
307 | 3: {"remap", "left"},
308 | 4: {"remap", "right"},
309 | 5: {"remap", "home"},
310 | 6: {"remap", "end"},
311 | 17: {"remap", "appskey"},
312 | 20: {"remap", "esc"},
313 | 21: {"remap", "backspace"},
314 | 23: {"remap", "delete"},
315 | 24: {"remap", "insert"},
316 | 25: {"remap", "tab"},
317 |
318 | 7: {"send", "{blind}^{left}"},
319 | 8: {"send", "{blind}^{right}"},
320 | 9: {"send", "{blind}+{up}"},
321 | 10: {"send", "{blind}+{down}"},
322 | 11: {"send", "{blind}+{left}"},
323 | 12: {"send", "{blind}+{right}"},
324 | 13: {"send", "{blind}+{home}"},
325 | 14: {"send", "{blind}+{end}"},
326 | 15: {"send", "^+{left}"},
327 | 16: {"send", "^+{right}"},
328 | 18: {"send", "^{backspace}"},
329 | 33: {"send", "{home}+{end}{backspace}"},
330 | 22: {"send", "{blind}{enter}"},
331 | 26: {"send", "^{tab}"},
332 | 27: {"send", "{blind}+{tab}"},
333 | 28: {"send", "^+{tab}"},
334 | }
335 |
336 | if item, ok := m[a.ValueID]; ok {
337 | if item.Type == "remap" {
338 | a.RemapToKey = item.Value
339 | return remapKey5(a, inAbbrContext)
340 | }
341 | if item.Type == "send" {
342 | a.KeysToSend = item.Value
343 | return sendKeys6(a, inAbbrContext)
344 | }
345 | }
346 |
347 | callMap := map[int]string{
348 | 19: `HoldDownModifierKey("LShift")`,
349 | 29: `InsertSpaceBetweenZHAndEn()`,
350 | 30: `HoldDownModifierKey("LCtrl")`,
351 | 31: `HoldDownModifierKey("LAlt")`,
352 | 32: `HoldDownModifierKey("LWin")`,
353 | }
354 | if call, ok := callMap[a.ValueID]; ok {
355 | if inAbbrContext {
356 | return call
357 | }
358 | return fmt.Sprintf(`km.Map("%[1]s", _ => %s%s)`, a.Hotkey, call, Cfg.GetHotkeyContext(a))
359 | }
360 | return ""
361 | }
362 |
--------------------------------------------------------------------------------
/config-server/internal/script/config.go:
--------------------------------------------------------------------------------
1 | package script
2 |
3 | import (
4 | "bytes"
5 | "encoding/json"
6 | "fmt"
7 | "os"
8 | "sort"
9 | "strings"
10 | )
11 |
12 | // 刚刚发现 GoLand 可以直接把 JSON 字符串粘贴为「 结构体定义 」 一下省掉了好多工作
13 |
14 | var MykeymapVersion string
15 |
16 | type Config struct {
17 | Keymaps []Keymap `json:"keymaps,omitempty"`
18 | Options Options `json:"options,omitempty"`
19 | KeyMapping string `json:"-"`
20 | }
21 |
22 | type Keymap struct {
23 | ID int `json:"id"`
24 | Name string `json:"name"`
25 | Enable bool `json:"enable"`
26 | Hotkey string `json:"hotkey"`
27 | ParentID int `json:"parentID"`
28 | Delay int `json:"delay"`
29 | DisableAt string `json:"disableAt"`
30 | Hotkeys map[string][]Action `json:"hotkeys"`
31 | }
32 |
33 | type Action struct {
34 | WindowGroupID int `json:"windowGroupID"`
35 | TypeID int `json:"actionTypeID"`
36 | Comment string `json:"comment,omitempty"`
37 | Hotkey string `json:"hotkey,omitempty"`
38 | // 下面的字段因动作类型而异
39 | KeysToSend string `json:"keysToSend,omitempty"`
40 | RemapToKey string `json:"remapToKey,omitempty"`
41 | ValueID int `json:"actionValueID,omitempty"`
42 | WinTitle string `json:"winTitle,omitempty"`
43 | Target string `json:"target,omitempty"`
44 | Args string `json:"args,omitempty"`
45 | WorkingDir string `json:"workingDir,omitempty"`
46 | RunAsAdmin bool `json:"runAsAdmin,omitempty"`
47 | RunInBackground bool `json:"runInBackground,omitempty"`
48 | DetectHiddenWindow bool `json:"detectHiddenWindow,omitempty"`
49 | AHKCode string `json:"ahkCode,omitempty"`
50 |
51 | RemapInHotIf bool `json:"-"`
52 | }
53 |
54 | func ParseConfig(file string) (*Config, error) {
55 | data, err := os.ReadFile(file)
56 | if err != nil {
57 | return nil, fmt.Errorf("cannot read file %s: %v", file, err)
58 | }
59 |
60 | var config Config
61 | err = json.Unmarshal(data, &config)
62 | if err != nil {
63 | return nil, fmt.Errorf("cannot parse config: %v", err)
64 | }
65 |
66 | config.Options.MykeymapVersion = MykeymapVersion
67 | if config.Options.Mouse.TipSymbol == "" {
68 | config.Options.Mouse.TipSymbol = "🐶"
69 | }
70 | if config.Options.CommandInputSkin == (CommandInputSkin{}) {
71 | config.Options.CommandInputSkin = CommandInputSkin{
72 | BackgroundColor: "#FFFFFF",
73 | BackgroundOpacity: "0.9",
74 | BorderWidth: "3",
75 | BorderColor: "#FFFFFF",
76 | BorderOpacity: "1.0",
77 | BorderRadius: "10",
78 | CornerColor: "#000000",
79 | CornerOpacity: "0.0",
80 | GridlineColor: "#2843AD",
81 | GridlineOpacity: "0.04",
82 | KeyColor: "#000000",
83 | KeyOpacity: "1.0",
84 | HideAnimationDuration: "0.34",
85 | WindowYPos: "0.25",
86 | WindowWidth: "700",
87 | WindowShadowColor: "#000000",
88 | WindowShadowOpacity: "0.5",
89 | WindowShadowSize: "3.0",
90 | }
91 | }
92 |
93 | return &config, nil
94 | }
95 |
96 | func SaveConfigFile(config *Config) {
97 | // 先写到缓冲区, 如果直接写文件的话, 当编码过程遇到错误时, 会导致文件损坏
98 | buf := new(bytes.Buffer)
99 | encoder := json.NewEncoder(buf)
100 | encoder.SetIndent("", " ")
101 | encoder.SetEscapeHTML(false)
102 | if err := encoder.Encode(config); err != nil {
103 | panic(err)
104 | }
105 |
106 | if err := os.WriteFile("../data/config.json", buf.Bytes(), 0644); err != nil {
107 | panic(err)
108 | }
109 | }
110 |
111 | func groupName(id int, prefix ...string) string {
112 | p := "MY_WINDOW_GROUP_"
113 | if len(prefix) > 0 {
114 | p = prefix[0]
115 | }
116 | if id < 0 {
117 | return fmt.Sprintf(p+"_%d", -id)
118 | }
119 | return fmt.Sprintf(p+"%d", id)
120 | }
121 |
122 | func groupToWinTile(g WindowGroup) string {
123 | if lines := notBlankLines(g.Value); len(lines) > 1 {
124 | return fmt.Sprintf(`"ahk_group %s"`, groupName(g.ID))
125 | } else {
126 | return fmt.Sprintf(`"%s"`, strings.TrimSpace(g.Value))
127 | }
128 | }
129 |
130 | func (c *Config) GetWinTitle(a Action) (winTitle string, conditionType int) {
131 | if a.WindowGroupID == 0 {
132 | return `""`, 0
133 | }
134 |
135 | for _, g := range c.Options.WindowGroups {
136 | if g.ID == a.WindowGroupID {
137 | // 5 表示自定义的 HotIf 表达式
138 | if g.ConditionType == 5 {
139 | return fmt.Sprintf(`'%s'`, g.Value), 5
140 | }
141 |
142 | return groupToWinTile(g), g.ConditionType
143 | }
144 | }
145 | return `""`, 0
146 | }
147 |
148 | func (c *Config) GetHotkeyContext(a Action) string {
149 | winTitle, conditionType := c.GetWinTitle(a)
150 | if winTitle == `""` && conditionType == 0 {
151 | return ""
152 | }
153 | return fmt.Sprintf(", , %s, %d", winTitle, conditionType)
154 | }
155 |
156 | func (c *Config) EnabledKeymaps() []Keymap {
157 | var enabled []Keymap
158 | for _, km := range c.Keymaps {
159 | if km.ID == 1 && km.Enable {
160 | c.handleKeyRemapping(km)
161 | enabled = append(enabled, km)
162 | }
163 | if km.ID >= 5 && km.Enable {
164 | enabled = append(enabled, km)
165 | }
166 | }
167 |
168 | // 对模式进行分组和排序
169 | groups := make(map[int][]Keymap)
170 | for _, km := range enabled {
171 | if km.ParentID != 0 {
172 | groups[km.ParentID] = append(groups[km.ParentID], km)
173 | }
174 | }
175 | var res []Keymap
176 | for _, km := range enabled {
177 | if km.ParentID == 0 {
178 | res = append(res, km)
179 | if subKeymaps, ok := groups[km.ID]; ok {
180 | res = append(res, subKeymaps...)
181 | }
182 | }
183 | }
184 | return res
185 | }
186 |
187 | func (c *Config) handleKeyRemapping(custom Keymap) {
188 | // 把自定义热键中的按键重映射取出来, 这部分需要单独渲染
189 | var list []Action
190 | for hk, actions := range custom.Hotkeys {
191 | for i, a := range actions {
192 | if a.TypeID == remapKey {
193 | a.Hotkey = hk
194 | list = append(list, a)
195 | a.RemapInHotIf = true
196 | actions[i] = a
197 | }
198 | }
199 | }
200 |
201 | // 按照 windowGroupID 进行排序
202 | sort.SliceStable(list, func(i, j int) bool {
203 | return list[i].WindowGroupID < list[j].WindowGroupID
204 | })
205 |
206 | var s strings.Builder
207 | lastGroup := -2233
208 | for _, a := range list {
209 | if lastGroup != a.WindowGroupID {
210 | s.WriteString("\n")
211 | s.WriteString(hotifHeader(c, a))
212 | s.WriteString("\n")
213 | lastGroup = a.WindowGroupID
214 | }
215 | s.WriteString(fmt.Sprintf("%s::%s\n", strings.TrimLeft(a.Hotkey, "*"), a.RemapToKey))
216 | }
217 | s.WriteString("\n#HotIf")
218 | c.KeyMapping = s.String()
219 | }
220 |
221 | func hotifHeader(c *Config, a Action) string {
222 | winTitle, conditionType := c.GetWinTitle(a)
223 | if winTitle == `""` && conditionType == 0 {
224 | return "#HotIf"
225 | }
226 | switch conditionType {
227 | case 1:
228 | return fmt.Sprintf("#HotIf WinActive(%s)", winTitle)
229 | case 2:
230 | return fmt.Sprintf("#HotIf WinExist(%s)", winTitle)
231 | case 3:
232 | return fmt.Sprintf("#HotIf !WinActive(%s)", winTitle)
233 | case 4:
234 | return fmt.Sprintf("#HotIf !WinExist(%s)", winTitle)
235 | case 5:
236 | return fmt.Sprintf("#HotIf %s ", winTitle)
237 | }
238 | return ""
239 | }
240 |
--------------------------------------------------------------------------------
/config-server/internal/script/options.go:
--------------------------------------------------------------------------------
1 | package script
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 | )
7 |
8 | type Options struct {
9 | MykeymapVersion string `json:"mykeymapVersion"`
10 | WindowGroups []WindowGroup `json:"windowGroups"`
11 | Mouse Mouse `json:"mouse"`
12 | Scroll Scroll `json:"scroll"`
13 | CommandInputSkin CommandInputSkin `json:"commandInputSkin"`
14 | PathVariables []PathVariable `json:"pathVariables"`
15 | Startup bool `json:"startup"`
16 | Language string `json:"language"`
17 | KeyMapping string `json:"keyMapping"`
18 | KeyboardLayout string `json:"keyboardLayout"`
19 | }
20 |
21 | type WindowGroup struct {
22 | ID int `json:"id"`
23 | Name string `json:"name"`
24 | Value string `json:"value,omitempty"`
25 | ConditionType int `json:"conditionType,omitempty"`
26 | }
27 |
28 | type Mouse struct {
29 | KeepMouseMode bool `json:"keepMouseMode"`
30 | ShowTip bool `json:"showTip"`
31 | TipSymbol string `json:"tipSymbol"`
32 | Delay1 string `json:"delay1"`
33 | Delay2 string `json:"delay2"`
34 | FastSingle string `json:"fastSingle"`
35 | FastRepeat string `json:"fastRepeat"`
36 | SlowSingle string `json:"slowSingle"`
37 | SlowRepeat string `json:"slowRepeat"`
38 | }
39 | type Scroll struct {
40 | Delay1 string `json:"delay1"`
41 | Delay2 string `json:"delay2"`
42 | OnceLineCount string `json:"onceLineCount"`
43 | }
44 | type PathVariable struct {
45 | Name string `json:"name"`
46 | Value string `json:"value"`
47 | }
48 |
49 | type CommandInputSkin struct {
50 | BackgroundColor string `json:"backgroundColor"`
51 | BackgroundOpacity string `json:"backgroundOpacity"`
52 | BorderWidth string `json:"borderWidth"`
53 | BorderColor string `json:"borderColor"`
54 | BorderOpacity string `json:"borderOpacity"`
55 | BorderRadius string `json:"borderRadius"`
56 | CornerColor string `json:"cornerColor"`
57 | CornerOpacity string `json:"cornerOpacity"`
58 | GridlineColor string `json:"gridlineColor"`
59 | GridlineOpacity string `json:"gridlineOpacity"`
60 | KeyColor string `json:"keyColor"`
61 | KeyOpacity string `json:"keyOpacity"`
62 | HideAnimationDuration string `json:"hideAnimationDuration"`
63 | WindowYPos string `json:"windowYPos"`
64 | WindowWidth string `json:"windowWidth"`
65 | WindowShadowColor string `json:"windowShadowColor"`
66 | WindowShadowOpacity string `json:"windowShadowOpacity"`
67 | WindowShadowSize string `json:"windowShadowSize"`
68 | }
69 |
70 | func (c *Config) PathVariables() string {
71 | var s strings.Builder
72 | for _, v := range c.Options.PathVariables {
73 | if strings.TrimSpace(v.Name) == "" {
74 | continue
75 | }
76 | s.WriteString(" ")
77 | s.WriteString(v.Name)
78 | s.WriteString(" := ")
79 | s.WriteString(toAHKFuncArg(v.Value))
80 | s.WriteString("\n")
81 | }
82 | return s.String()
83 | }
84 |
85 | func notBlankLines(str string) []string {
86 | var res []string
87 | for _, line := range strings.Split(str, "\n") {
88 | line = strings.TrimSpace(line)
89 | if line != "" {
90 | res = append(res, line)
91 | }
92 | }
93 | return res
94 | }
95 |
96 | func (c *Config) WindowGroups() string {
97 | var s strings.Builder
98 | for _, g := range c.Options.WindowGroups {
99 | addGroup(&s, g.Value, g.ID)
100 | }
101 | for _, km := range c.Keymaps {
102 | addGroup(&s, km.DisableAt, km.ID, "GROUP_DISABLE_KEYMAP_")
103 | }
104 | return s.String()
105 | }
106 |
107 | func (c *Config) getKeymapDisableAt(kmID int) string {
108 | for _, km := range c.Keymaps {
109 | if km.ID == kmID {
110 | lines := notBlankLines(km.DisableAt)
111 | switch {
112 | case len(lines) == 0:
113 | return ""
114 | case len(lines) == 1:
115 | return lines[0]
116 | case len(lines) > 1:
117 | return fmt.Sprintf("ahk_group GROUP_DISABLE_KEYMAP_%d", kmID)
118 | }
119 | }
120 | }
121 | return ""
122 | }
123 |
124 | func addGroup(s *strings.Builder, value string, id int, prefix ...string) {
125 | if lines := notBlankLines(value); len(lines) > 1 {
126 | for _, v := range lines {
127 | arg := toAHKFuncArg(v)
128 | if arg != `""` {
129 | s.WriteString(fmt.Sprintf(" GroupAdd(\"%s\", %s)\n", groupName(id, prefix...), arg))
130 | }
131 | }
132 |
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/config-server/internal/script/script.go:
--------------------------------------------------------------------------------
1 | package script
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path/filepath"
7 | "sort"
8 | "strings"
9 | "text/template"
10 | )
11 |
12 | func GenerateScripts(config *Config) {
13 | preprocess(config)
14 |
15 | if err := SaveAHK(config, "./templates/MyKeymap.tmpl", "../bin/MyKeymap.ahk"); err != nil {
16 | panic(err)
17 | }
18 | if err := SaveAHK(config, "./templates/CommandInputSkin.tmpl", "../bin/CommandInputSkin.txt"); err != nil {
19 | panic(err)
20 | }
21 | // if err := SaveAHK(config, "./templates/CustomShellMenu.ahk", "../bin/CustomShellMenu.ahk"); err != nil {
22 | // panic(err)
23 | // }
24 | }
25 |
26 | func preprocess(cfg *Config) {
27 | // 添加一个隐藏的全局热键, 且免疫 suspend, 否则 ahk 的 suspend 会把键盘钩子临时移除
28 | for _, km := range cfg.Keymaps {
29 | if km.ID == 1 {
30 | km.Hotkeys["!f17"] = []Action{{TypeID: 9, ValueID: 2}}
31 | return
32 | }
33 | }
34 | }
35 |
36 | func SaveAHK(data *Config, templateFile, outputFile string) error {
37 | Cfg = data
38 | files := []string{
39 | templateFile,
40 | }
41 | ts, err := template.New(filepath.Base(templateFile)).Funcs(TemplateFuncMap).ParseFiles(files...)
42 | if err != nil {
43 | return err
44 | }
45 |
46 | // 用 Go 代码生成 AHK 脚本时会使用 \n 导致换行符不统一
47 | // 先输出到一个字符串, 然后对换行符进行统一, 把 \n 改成 \r\n
48 | builder := new(strings.Builder)
49 | err = ts.Execute(builder, data)
50 | if err != nil {
51 | return err
52 | }
53 | res := builder.String()
54 | res = strings.ReplaceAll(res, "\r\n", "\n")
55 | res = strings.ReplaceAll(res, "\n", "\r\n")
56 |
57 | f, err := os.Create(outputFile)
58 | if err != nil {
59 | return err
60 | }
61 | //goland:noinspection GoUnhandledErrorResult
62 | defer f.Close()
63 |
64 | // 因为模板文件就是 UTF-8 with BOM, 所以输出文件也是 UTF-8 with BOM
65 | // _, _ = f.Write([]byte{0xef, 0xbb, 0xbf}) // 写入 utf-8 的 BOM (0xefbbbf)
66 | _, err = f.Write([]byte(res))
67 | return err
68 | }
69 |
70 | var TemplateFuncMap = template.FuncMap{
71 | "contains": strings.Contains,
72 | "concat": concat,
73 | "join": join,
74 | "ahkString": ahkString,
75 | "escapeAhkHotkey": escapeAhkHotkey,
76 | "actionToHotkey": actionToHotkey,
77 | "abbrToCode": abbrToCode,
78 | "sortHotkeys": sortHotkeys,
79 | "divide": divide,
80 | "renderKeymap": renderKeymap,
81 | "GroupDisableMyKeymap": GroupDisableMyKeymap,
82 | }
83 |
84 | func divide(a, b int) string {
85 | res := float64(a) / float64(b)
86 | if res <= 0 {
87 | return ""
88 | }
89 | return fmt.Sprintf("%.3f", res)
90 | }
91 |
92 | func join(sep string, elems []interface{}) string {
93 | elems2 := make([]string, 0)
94 | for _, val := range elems {
95 | elems2 = append(elems2, val.(string))
96 | }
97 | return strings.Join(elems2, sep)
98 | }
99 |
100 | func ahkString(s string) string {
101 | s = strings.ReplaceAll(s, "`", "``")
102 | s = strings.ReplaceAll(s, "\"", "`\"")
103 | s = strings.ReplaceAll(s, " ;", " `;") // 空格后的分号会被 ahk 解释为注释
104 | return `"` + s + `"`
105 | }
106 |
107 | func escapeAhkHotkey(key string) string {
108 | if key == ";" {
109 | return "`;"
110 | }
111 | return key
112 | }
113 |
114 | func concat(a, b string) string {
115 | return a + b
116 | }
117 |
118 | func sortHotkeys(hotkeyMap map[string][]Action) []Action {
119 | res := make([]Action, 0, len(hotkeyMap))
120 | for hotkey, actions := range hotkeyMap {
121 | for _, action := range actions {
122 | // 去掉首末的 " 字符, 虽然此处按字节取子切片也行, 但写法不够通用
123 | action.Hotkey = substr(toAHKFuncArg(hotkey), 1, -1)
124 | res = append(res, action)
125 | }
126 | }
127 |
128 | sort.Slice(res, func(i, j int) bool {
129 | // 先根据 action type 排, 然后根据 hotkey 的长度和字典序来排序
130 | if res[i].TypeID != res[j].TypeID {
131 | return res[i].TypeID < res[j].TypeID
132 | }
133 |
134 | if len(res[i].Hotkey) != len(res[j].Hotkey) {
135 | return len(res[i].Hotkey) < len(res[j].Hotkey)
136 | }
137 |
138 | return res[i].Hotkey < res[j].Hotkey
139 | })
140 |
141 | return res
142 | }
143 |
144 | // 取子字符串,并不想看起来那么简单: https://stackoverflow.com/a/56129336
145 | // NOTE: this isn't multi-Unicode-codepoint aware, like specifying skintone or
146 | // gender of an emoji: https://unicode.org/emoji/charts/full-emoji-modifiers.html
147 | func substr(input string, start int, length int) string {
148 | asRunes := []rune(input)
149 |
150 | if start >= len(asRunes) {
151 | return ""
152 | }
153 |
154 | if start+length > len(asRunes) {
155 | length = len(asRunes) - start
156 | } else if length < 0 {
157 | length = len(asRunes) - start + length
158 | }
159 |
160 | return string(asRunes[start : start+length])
161 | }
162 |
163 | func renderKeymap(km Keymap) string {
164 | if "" == strings.TrimSpace(km.Hotkey) {
165 | return ""
166 | }
167 | var buf strings.Builder
168 |
169 | // ; Capslock + F
170 | buf.WriteString(fmt.Sprintf("\n ; %s\n", km.Name))
171 |
172 | // km6 := KeymapManager.AddSubKeymap(km5, "*f", "Capslock + F")
173 | line := fmt.Sprintf(" km%d := KeymapManager.", km.ID)
174 | hotkey := km.Hotkey
175 | if containsOnlyModifier(km.Hotkey) {
176 | hotkey = "customHotkeys"
177 | }
178 | s := ahkString
179 | if km.ParentID == 0 {
180 | line += fmt.Sprintf("NewKeymap(%s, %s, %s, %s)\n", s(hotkey), s(km.Name), s(divide(km.Delay, 1000)), s(Cfg.getKeymapDisableAt(km.ID)))
181 | } else {
182 | line += fmt.Sprintf("AddSubKeymap(km%d, %s, %s, %s)\n", km.ParentID, s(hotkey), s(km.Name), s(divide(km.Delay, 1000)))
183 | }
184 | buf.WriteString(line)
185 |
186 | // km := km6
187 | buf.WriteString(fmt.Sprintf(" km := km%d\n", km.ID))
188 |
189 | for _, action := range sortHotkeys(km.Hotkeys) {
190 | if containsOnlyModifier(km.Hotkey) {
191 | if action.Hotkey == "singlePress" {
192 | continue
193 | }
194 | action.Hotkey = km.Hotkey + action.Hotkey // 把触发键 # 和热键 *q 拼起来
195 | }
196 | buf.WriteString(fmt.Sprintf(" %s\n", actionToHotkey(action)))
197 | }
198 |
199 | // 替换换行符为 \r\n
200 | res := buf.String()
201 | res = strings.ReplaceAll(res, "\r\n", "\n")
202 | res = strings.ReplaceAll(res, "\n", "\r\n")
203 | return res
204 | }
205 |
206 | func containsOnlyModifier(hotkey string) bool {
207 | hotkey = strings.TrimSpace(hotkey)
208 | return hotkey != "" && strings.Trim(hotkey, "#!^+<>*~$") == ""
209 | }
210 |
211 | func GroupDisableMyKeymap(groups []WindowGroup) string {
212 | for _, g := range groups {
213 | if g.ID == -1 {
214 | return groupToWinTile(g)
215 | }
216 | }
217 | return ahkString("")
218 | }
219 |
--------------------------------------------------------------------------------
/config-server/internal/script/script_test.go:
--------------------------------------------------------------------------------
1 | package script
2 |
3 | import "testing"
4 |
5 | func TestSortActions(t *testing.T) {
6 | a := Action{WindowGroupID: 0, Comment: "item1"}
7 | b := Action{WindowGroupID: 1, Comment: "item2"}
8 | c := Action{WindowGroupID: 0, Comment: "item3"}
9 | s := []Action{a, b, c}
10 | ss := sortActions(s)
11 | assertEqual(t, ss[0], b)
12 | assertEqual(t, ss[1], a)
13 | assertEqual(t, ss[2], c)
14 | }
15 |
16 | func assertEqual[T comparable](t *testing.T, a, b T) {
17 | t.Helper()
18 | if a != b {
19 | t.Errorf("%v != %v", a, b)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/config-server/templates/CommandInputSkin.tmpl:
--------------------------------------------------------------------------------
1 | backgroundColor = {{ if .Options.CommandInputSkin.BackgroundColor }}{{ .Options.CommandInputSkin.BackgroundColor }}{{ else }}#FFFFFF{{ end }}
2 | backgroundOpacity = {{ if .Options.CommandInputSkin.BackgroundOpacity }}{{ .Options.CommandInputSkin.BackgroundOpacity }}{{ else }}0.9{{ end }}
3 | borderWidth = {{ if .Options.CommandInputSkin.BorderWidth }}{{ .Options.CommandInputSkin.BorderWidth }}{{ else }}3{{ end }}
4 | borderColor = {{ if .Options.CommandInputSkin.BorderColor }}{{ .Options.CommandInputSkin.BorderColor }}{{ else }}#FFFFFF{{ end }}
5 | borderOpacity = {{ if .Options.CommandInputSkin.BorderOpacity }}{{ .Options.CommandInputSkin.BorderOpacity }}{{ else }}1.0{{ end }}
6 | borderRadius = {{ if .Options.CommandInputSkin.BorderRadius }}{{ .Options.CommandInputSkin.BorderRadius }}{{ else }}10{{ end }}
7 | cornerColor = {{ if .Options.CommandInputSkin.CornerColor }}{{ .Options.CommandInputSkin.CornerColor }}{{ else }}#000000{{ end }}
8 | cornerOpacity = {{ if .Options.CommandInputSkin.CornerOpacity }}{{ .Options.CommandInputSkin.CornerOpacity }}{{ else }}0.0{{ end }}
9 | gridlineColor = {{ if .Options.CommandInputSkin.GridlineColor }}{{ .Options.CommandInputSkin.GridlineColor }}{{ else }}#2843AD{{ end }}
10 | gridlineOpacity = {{ if .Options.CommandInputSkin.GridlineOpacity }}{{ .Options.CommandInputSkin.GridlineOpacity }}{{ else }}0.04{{ end }}
11 | keyColor = {{ if .Options.CommandInputSkin.KeyColor }}{{ .Options.CommandInputSkin.KeyColor }}{{ else }}#000000{{ end }}
12 | keyOpacity = {{ if .Options.CommandInputSkin.KeyOpacity }}{{ .Options.CommandInputSkin.KeyOpacity }}{{ else }}1.0{{ end }}
13 | hideAnimationDuration = {{ if .Options.CommandInputSkin.HideAnimationDuration }}{{ .Options.CommandInputSkin.HideAnimationDuration }}{{ else }}0.34{{ end }}
14 | windowYPos = {{ if .Options.CommandInputSkin.WindowYPos }}{{ .Options.CommandInputSkin.WindowYPos }}{{ else }}0.25{{ end }}
15 | windowWidth = {{ if .Options.CommandInputSkin.WindowWidth }}{{ .Options.CommandInputSkin.WindowWidth }}{{ else }}700{{ end }}
16 | windowShadowColor = {{ if .Options.CommandInputSkin.WindowShadowColor }}{{ .Options.CommandInputSkin.WindowShadowColor }}{{ else }}#000000{{ end }}
17 | windowShadowOpacity = {{ if .Options.CommandInputSkin.WindowShadowOpacity }}{{ .Options.CommandInputSkin.WindowShadowOpacity }}{{ else }}0.5{{ end }}
18 | windowShadowSize = {{ if .Options.CommandInputSkin.WindowShadowSize }}{{ .Options.CommandInputSkin.WindowShadowSize }}{{ else }}3.0{{ end }}
--------------------------------------------------------------------------------
/config-server/templates/CustomShellMenu.ahk:
--------------------------------------------------------------------------------
1 | #NoEnv
2 | #SingleInstance, force
3 | #NoTrayIcon
4 | SetWorkingDir %A_ScriptDir%
5 | CoordMode, Mouse, Screen
6 | SendMode Input
7 |
8 | menuItems := []
9 | filePath := getSelectedFilePath()
10 | SplitPath, filePath , filename, fileDir, fileExt, filenameNoExt
11 |
12 | ; 固定的
13 | firstItemName := filename
14 | if InStr(filePath, "`n") {
15 | firstItemName := "选中了多个文件"
16 | }
17 | Menu, MyMenu, Add, %firstItemName%, MenuHandler
18 | Menu, MyMenu, Disable, %firstItemName%
19 |
20 | name := "(&F) 复制路径"
21 | Menu, MyMenu, Add, %name%, MenuHandler
22 | Menu, MyMenu, Icon, %name%, Shell32.dll, 135
23 |
24 | ; 自定义的
25 | {{ .Settings.CustomShellMenu }}
26 |
27 |
28 | WinGetPos, , , Width, Height, A
29 | offsetX := Width/2
30 | offsetY := Height/2
31 | Menu, MyMenu, Show, %offsetX%, %offsetY%
32 | return
33 |
34 | MenuHandler:
35 | ; MsgBox file: %filePath%`nitem: %A_ThisMenuItem%. `n x: %x% `n y: %y%
36 | if (A_ThisMenuItem == "(&F) 复制路径") {
37 | Clipboard := filePath
38 | ToolTip, 复制了 %filePath%
39 | sleep 600
40 | return
41 | }
42 | for index, item in menuItems {
43 | if (A_ThisMenuItem == item.name) {
44 | exe := item.exe
45 | args := item.args
46 | args := StrReplace(args, """{file}""", """" filePath """")
47 | args := StrReplace(args, "{file}", """" filePath """")
48 | args := StrReplace(args, """{dir}""", """" fileDir """")
49 | args := StrReplace(args, "{dir}", """" fileDir """")
50 | args := StrReplace(args, """{filename}""", """" filename """")
51 | args := StrReplace(args, "{filename}", """" filename """")
52 | args := StrReplace(args, """{filenameNoExt}""", """" filenameNoExt """")
53 | args := StrReplace(args, "{filenameNoExt}", """" filenameNoExt """")
54 |
55 | if (item.args == "smartExtractZipFile") {
56 | smartExtractZipFile(item.exe, filePath)
57 | break
58 | }
59 | if (item.args == "smartArchiveZipFile") {
60 | smartArchiveZipFile(item.exe, filePath)
61 | break
62 | }
63 |
64 | if (item.workingDir) {
65 | wr := item.workingDir
66 | wr := StrReplace(wr, "{file}", filePath)
67 | wr := StrReplace(wr, "{dir}", fileDir)
68 | run, %exe% %args%, %wr%
69 | break
70 | }
71 |
72 | run, %exe% %args%
73 | break
74 | }
75 | }
76 | return
77 |
78 | add_menu_item(key, name, icon := "", exe := "", args := "", workingDir := "")
79 | {
80 | global menuItems
81 |
82 | name := "(&" key ") " name
83 | Menu, MyMenu, Add, %name%, MenuHandler
84 | if (icon && icon != "NoIcon") {
85 | ; 如果是 .lnk 并且它指向 exe 文件, 那么用这个 exe 作为 icon
86 | if (SubStr(icon, -3) == ".lnk") {
87 | FileGetShortcut, %exe%, lnkTarget
88 | if (SubStr(lnkTarget, -3) == ".exe") {
89 | icon := lnkTarget
90 | }
91 | }
92 | Menu, MyMenu, Icon, %name%, %icon%
93 | }
94 |
95 | obj := {}
96 | obj.exe := exe
97 | obj.args := args
98 | obj.workingDir := workingDir
99 | obj.name := name
100 | menuItems.Push(obj)
101 | }
102 |
103 | getSelectedFilePath()
104 | {
105 | tmp := Clipboardall
106 | Clipboard := ""
107 | Send, ^c
108 | ClipWait, 0.6
109 | path := Clipboard
110 | Clipboard := tmp
111 | if ErrorLevel {
112 | tooltip, 没有获取到路径
113 | sleep, 1000
114 | ExitApp
115 | }
116 | return path
117 | }
118 |
119 |
120 | smartExtractZipFile(exe, filepath)
121 | {
122 | info := GetZipInfo(exe, filepath)
123 | showDialog := info.encrypted ? "-ad" : ""
124 |
125 | SplitPath, exe, OutFileName, OutDir, OutExtension, OutNameNoExt
126 | exe := OutDir . "\7zg.exe"
127 |
128 | SplitPath, filepath, OutFileName, OutDir, OutExtension, OutNameNoExt
129 | if (info.containsOnlyOneFile) {
130 | run, %exe% x %showDialog% "%filepath%" -o"%OutDir%"
131 | } else {
132 | run, %exe% x %showDialog% "%filepath%" -o"%OutDir%\%OutNameNoExt%"
133 | }
134 | }
135 |
136 | GetZipInfo(exe,filepath)
137 | {
138 | cmdLine := """" exe """ l -p123 -ba -x!*\* " """" filepath """"
139 | stdout := RunCMD(cmdLine)
140 | lines := StrSplit(stdout, "`n")
141 | zipInfo := {}
142 | zipInfo.containsOnlyOneFile := lines.Length() == 2
143 | zipInfo.encrypted := InStr(stdout, "Cannot open encrypted archive")
144 | return zipInfo
145 | }
146 |
147 |
148 | smartArchiveZipFile(exe, filepath)
149 | {
150 | SplitPath, exe, OutFileName, OutDir, OutExtension, OutNameNoExt
151 | exe := OutDir . "\7zg.exe"
152 |
153 | ; 选中一个文件/文件夹
154 | if !InStr(filepath, "`n") {
155 | SplitPath, filepath, OutFileName, OutDir, OutExtension, OutNameNoExt
156 | if InStr(FileExist(filepath), "D") {
157 | run, %exe% a "%filepath%.7z" "%filepath%"
158 | } else {
159 | run, %exe% a "%OutDir%\%OutNameNoExt%.7z" "%filepath%"
160 | }
161 | return
162 | }
163 |
164 | ; 选中多个文件, 注意换行符是 `r`n
165 | files := StrSplit(filepath, "`r`n")
166 |
167 | ; 最后会多出一个空格, 但在命令行中无所谓
168 | result := ""
169 | for i,f in files {
170 | result := result . """" f """ "
171 | }
172 |
173 | firstFile := files[1]
174 | SplitPath, firstFile, , OutDir
175 | SplitPath, OutDir, OutFileName
176 | run, %exe% a "%OutDir%\%OutFileName%.7z" %result%
177 | }
178 |
179 |
180 | RunCMD(CmdLine, WorkingDir:="", Codepage:="CP0", Fn:="RunCMD_Output") { ; RunCMD v0.94
181 | Local ; RunCMD v0.94 by SKAN on D34E/D37C @ autohotkey.com/boards/viewtopic.php?t=74647
182 | Global A_Args ; Based on StdOutToVar.ahk by Sean @ autohotkey.com/board/topic/15455-stdouttovar
183 |
184 | Fn := IsFunc(Fn) ? Func(Fn) : 0
185 | , DllCall("CreatePipe", "PtrP",hPipeR:=0, "PtrP",hPipeW:=0, "Ptr",0, "Int",0)
186 | , DllCall("SetHandleInformation", "Ptr",hPipeW, "Int",1, "Int",1)
187 | , DllCall("SetNamedPipeHandleState","Ptr",hPipeR, "UIntP",PIPE_NOWAIT:=1, "Ptr",0, "Ptr",0)
188 |
189 | , P8 := (A_PtrSize=8)
190 | , VarSetCapacity(SI, P8 ? 104 : 68, 0) ; STARTUPINFO structure
191 | , NumPut(P8 ? 104 : 68, SI) ; size of STARTUPINFO
192 | , NumPut(STARTF_USESTDHANDLES:=0x100, SI, P8 ? 60 : 44,"UInt") ; dwFlags
193 | , NumPut(hPipeW, SI, P8 ? 88 : 60) ; hStdOutput
194 | , NumPut(hPipeW, SI, P8 ? 96 : 64) ; hStdError
195 | , VarSetCapacity(PI, P8 ? 24 : 16) ; PROCESS_INFORMATION structure
196 |
197 | If not DllCall("CreateProcess", "Ptr",0, "Str",CmdLine, "Ptr",0, "Int",0, "Int",True
198 | ,"Int",0x08000000 | DllCall("GetPriorityClass", "Ptr",-1, "UInt"), "Int",0
199 | ,"Ptr",WorkingDir ? &WorkingDir : 0, "Ptr",&SI, "Ptr",&PI)
200 | Return Format("{1:}", "", ErrorLevel := -1
201 | ,DllCall("CloseHandle", "Ptr",hPipeW), DllCall("CloseHandle", "Ptr",hPipeR))
202 |
203 | DllCall("CloseHandle", "Ptr",hPipeW)
204 | , A_Args.RunCMD := { "PID": NumGet(PI, P8? 16 : 8, "UInt") }
205 | , File := FileOpen(hPipeR, "h", Codepage)
206 |
207 | , LineNum := 1, sOutput := ""
208 | While (A_Args.RunCMD.PID + DllCall("Sleep", "Int",0))
209 | and DllCall("PeekNamedPipe", "Ptr",hPipeR, "Ptr",0, "Int",0, "Ptr",0, "Ptr",0, "Ptr",0)
210 | While A_Args.RunCMD.PID and (Line := File.ReadLine())
211 | sOutput .= Fn ? Fn.Call(Line, LineNum++) : Line
212 |
213 | A_Args.RunCMD.PID := 0
214 | , hProcess := NumGet(PI, 0)
215 | , hThread := NumGet(PI, A_PtrSize)
216 |
217 | , DllCall("GetExitCodeProcess", "Ptr",hProcess, "PtrP",ExitCode:=0)
218 | , DllCall("CloseHandle", "Ptr",hProcess)
219 | , DllCall("CloseHandle", "Ptr",hThread)
220 | , DllCall("CloseHandle", "Ptr",hPipeR)
221 |
222 | , ErrorLevel := ExitCode
223 |
224 | Return sOutput
225 | }
--------------------------------------------------------------------------------
/config-server/templates/help.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 所有键位映射
7 |
8 |
9 |
14 |
15 |
16 | {{ .helpPageHtml }}
17 |
18 |
--------------------------------------------------------------------------------
/config-server/templates/mykeymap.tmpl:
--------------------------------------------------------------------------------
1 | #Requires AutoHotkey v2.0
2 | #SingleInstance Force
3 | #UseHook true
4 |
5 | #include lib/translation.ahk
6 | #Include lib/Functions.ahk
7 | #Include lib/Actions.ahk
8 | #Include lib/KeymapManager.ahk
9 | #Include lib/InputTipWindow.ahk
10 | #Include lib/Utils.ahk
11 |
12 | ; #WinActivateForce ; 先关了遇到相关问题再打开试试
13 | ; InstallKeybdHook ; 这个可以重装 keyboard hook, 提高自己的 hook 优先级, 以后可能会用到
14 | ; ListLines False ; 也许能提升一点点性能 ( 别抱期待 ), 当有这个需求时再打开试试
15 | ; #Warn All, Off ; 也许能提升一点点性能 ( 别抱期待 ), 当有这个需求时再打开试试
16 |
17 | DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr") ; 多显示器不同缩放比例会导致问题: https://www.autohotkey.com/boards/viewtopic.php?f=14&t=13810
18 | SetMouseDelay 0 ; SendInput 可能会降级为 SendEvent, 此时会有 10ms 的默认 delay
19 | SetWinDelay 0 ; 默认会在 activate, maximize, move 等窗口操作后睡眠 100ms
20 | A_MaxHotkeysPerInterval := 256 ; 默认 70 可能有点低, 即使没有热键死循环也触发警告
21 | SendMode "Event" ; 执行 SendInput 的期间会短暂卸载 Hook, 这时候松开引导键会丢失 up 事件, 所以 Event 模式更适合 MyKeymap
22 | SetKeyDelay 0 ; 默认 10 太慢了, https://www.reddit.com/r/AutoHotkey/comments/gd3z4o/possible_unreliable_detection_of_the_keyup_event/
23 | ProcessSetPriority "High"
24 | SetWorkingDir("../")
25 | InitTrayMenu()
26 | InitKeymap()
27 | OnExit(MyKeymapExit)
28 | #include ../data/custom_functions.ahk
29 |
30 | InitKeymap()
31 | {
32 | taskSwitch := TaskSwitchKeymap("e", "d", "s", "f", "c", "space")
33 | mouseTip := {{ if .Options.Mouse.ShowTip }}InputTipWindow("{{ .Options.Mouse.TipSymbol }}",,,, 20, 16){{ else }}false{{ end }}
34 | slow := MouseKeymap("slow mouse", {{ .Options.Mouse.KeepMouseMode }}, mouseTip, {{ .Options.Mouse.SlowSingle }}, {{ .Options.Mouse.SlowRepeat }}, "T{{ .Options.Mouse.Delay1 }}", "T{{ .Options.Mouse.Delay2 }}", {{ .Options.Scroll.OnceLineCount }}, "T{{ .Options.Scroll.Delay1 }}", "T{{ .Options.Scroll.Delay2 }}")
35 | fast := MouseKeymap("fast mouse", {{ .Options.Mouse.KeepMouseMode }}, mouseTip, {{ .Options.Mouse.FastSingle }}, {{ .Options.Mouse.FastRepeat }}, "T{{ .Options.Mouse.Delay1 }}", "T{{ .Options.Mouse.Delay2 }}", {{ .Options.Scroll.OnceLineCount }}, "T{{ .Options.Scroll.Delay1 }}", "T{{ .Options.Scroll.Delay2 }}", slow)
36 | slow.Map("*space", slow.LButtonUp())
37 | {{ if .CapslockAbbrEnabled }}
38 | capsHook := InputHook("", "{CapsLock}{Esc}", "{{ .CapslockAbbrKeys }}")
39 | capsHook.KeyOpt("{CapsLock}", "S")
40 | capsHook.KeyOpt("{Backspace}", "N")
41 | capsHook.OnChar := PostCharToCaspAbbr
42 | capsHook.OnKeyDown := PostBackspaceToCaspAbbr
43 | Run("bin\MyKeymap-CommandInput.exe")
44 | {{ end }}
45 | {{- if .SemicolonAbbrEnabled }}
46 | semiHook := InputHook("", "{CapsLock}{Esc}{;}", "{{ .SemicolonAbbrKeys }}")
47 | semiHook.KeyOpt("{CapsLock}", "S")
48 | semiHook.KeyOpt("{Backspace}", "N")
49 | semiHook.OnChar := (ih, char) => semiHookAbbrWindow.Show(char, true)
50 | semiHook.OnKeyDown := (ih, vk, sc) => semiHookAbbrWindow.Backspace()
51 | semiHookAbbrWindow := InputTipWindow()
52 | {{ end }}
53 |
54 | ; 路径变量
55 | {{ .PathVariables }}
56 | ; 窗口组
57 | {{ .WindowGroups }}
58 | KeymapManager.GlobalKeymap.DisabledAt := {{ GroupDisableMyKeymap .Options.WindowGroups }}
59 | {{range .EnabledKeymaps}}{{renderKeymap .}}{{end}}
60 |
61 | KeymapManager.GlobalKeymap.Enable()
62 | }
63 |
64 | {{ if .CapslockAbbrEnabled -}}
65 | ExecCapslockAbbr(command) {
66 | ; 路径变量
67 | {{ .PathVariables }}
68 | switch command {
69 | {{ abbrToCode .CapslockAbbr }} }
70 | }
71 | {{- else -}}
72 | ExecCapslockAbbr(command) {
73 | }
74 | {{- end }}
75 |
76 | {{ if .SemicolonAbbrEnabled -}}
77 | ExecSemicolonAbbr(command) {
78 | ; 路径变量
79 | {{ .PathVariables }}
80 | switch command {
81 | {{ abbrToCode .SemicolonAbbr }} }
82 | }
83 | {{- else -}}
84 | ExecSemicolonAbbr(command) {
85 | }
86 | {{- end }}
87 |
88 | InitTrayMenu() {
89 | A_TrayMenu.Delete()
90 | A_TrayMenu.Add(Translation().menu_pause, TrayMenuHandler)
91 | A_TrayMenu.Add(Translation().menu_exit, TrayMenuHandler)
92 | A_TrayMenu.Add(Translation().menu_reload, TrayMenuHandler)
93 | A_TrayMenu.Add(Translation().menu_settings, TrayMenuHandler)
94 | A_TrayMenu.Add(Translation().menu_window_spy, TrayMenuHandler)
95 | A_TrayMenu.Default := Translation().menu_pause
96 | A_TrayMenu.ClickCount := 1
97 |
98 | A_IconTip := "MyKeymap {{ .Options.MykeymapVersion }} created by 咸鱼阿康"
99 | TraySetIcon("./bin/icons/logo.ico", , true)
100 | }
101 |
102 | {{ .KeyMapping }}
--------------------------------------------------------------------------------
/config-ui/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 | not dead
4 | not ie 11
5 |
--------------------------------------------------------------------------------
/config-ui/.editorconfig:
--------------------------------------------------------------------------------
1 | [*.{js,jsx,ts,tsx,vue}]
2 | indent_style = space
3 | indent_size = 2
4 | trim_trailing_whitespace = true
5 | insert_final_newline = true
6 | end_of_line = lf
--------------------------------------------------------------------------------
/config-ui/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true,
5 | },
6 | extends: [
7 | 'plugin:vue/vue3-essential',
8 | 'eslint:recommended',
9 | '@vue/eslint-config-typescript',
10 | ],
11 | rules: {
12 | 'vue/multi-word-component-names': 'off',
13 | },
14 | }
15 |
--------------------------------------------------------------------------------
/config-ui/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/config-ui/README.md:
--------------------------------------------------------------------------------
1 | # essentials
2 |
3 | ## Project setup
4 |
5 | ```
6 | # yarn
7 | yarn
8 |
9 | # npm
10 | npm install
11 |
12 | # pnpm
13 | pnpm install
14 | ```
15 |
16 | ### Compiles and hot-reloads for development
17 |
18 | ```
19 | # yarn
20 | yarn dev
21 |
22 | # npm
23 | npm run dev
24 |
25 | # pnpm
26 | pnpm dev
27 | ```
28 |
29 | ### Compiles and minifies for production
30 |
31 | ```
32 | # yarn
33 | yarn build
34 |
35 | # npm
36 | npm run build
37 |
38 | # pnpm
39 | pnpm build
40 | ```
41 |
42 | ### Lints and fixes files
43 |
44 | ```
45 | # yarn
46 | yarn lint
47 |
48 | # npm
49 | npm run lint
50 |
51 | # pnpm
52 | pnpm lint
53 | ```
54 |
55 | ### Customize configuration
56 |
57 | See [Configuration Reference](https://vitejs.dev/config/).
58 |
--------------------------------------------------------------------------------
/config-ui/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | MyKeymap Setting
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/config-ui/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "config-ui",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "vite",
7 | "build": "vite build",
8 | "preview": "vite preview",
9 | "lint": "eslint . --fix --ignore-path .gitignore"
10 | },
11 | "dependencies": {
12 | "@mdi/font": "7.0.96",
13 | "@vueuse/core": "^10.3.0",
14 | "core-js": "^3.29.0",
15 | "lodash-es": "^4.17.21",
16 | "pinia": "^2.0.0",
17 | "roboto-fontface": "*",
18 | "vue": "^3.2.0",
19 | "vue-router": "^4.0.0",
20 | "vuetify": "^3.0.0",
21 | "webfontloader": "^1.0.0"
22 | },
23 | "devDependencies": {
24 | "@babel/types": "^7.21.4",
25 | "@types/lodash-es": "^4.17.8",
26 | "@types/node": "^18.15.0",
27 | "@types/webfontloader": "^1.6.35",
28 | "@vitejs/plugin-vue": "^4.0.0",
29 | "@vue/eslint-config-typescript": "^11.0.0",
30 | "eslint": "^8.37.0",
31 | "eslint-plugin-vue": "^9.3.0",
32 | "sass": "^1.60.0",
33 | "typescript": "^5.0.0",
34 | "vite": "^4.2.0",
35 | "vite-plugin-vuetify": "^1.0.0",
36 | "vue-tsc": "^1.2.0"
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/config-ui/public/config_doc.md:
--------------------------------------------------------------------------------
1 | ## 😀 欢迎
2 |
3 | 1. [项目 GitHub](https://github.com/xianyukang/MyKeymap)
4 | 2. [视频介绍](https://www.bilibili.com/video/BV1Sf4y1c7p8/)
5 | 3. [快速入门](https://xianyukang.com/MyKeymap.html#mykeymap-%E7%AE%80%E4%BB%8B) ( 推荐看看,能了解 MyKeymap 的功能,和注意事项 )
6 |
7 |
8 |
9 | ## ✨ 浏览器字体
10 |
11 | 1. 可以美化浏览器字体,因为字体文件占几十 M 所以不打包在 MyKeymap 里面
12 | 2. 效果对比: 「 [修改前](/font-compare/1.png) / [修改后](/font-compare/2.png) 」, 「 [修改前](/font-compare/3.png) / [修改后](/font-compare/4.png) 」, 「 [修改前](/font-compare/5.png) / [修改后](/font-compare/6.png) 」
13 | 3. 如何修改: ①[去这下载工具](https://www.bilibili.com/video/BV1pP4y187bR/) ②把其中的常用字体全部装上 ③[从这跟着视频](https://www.bilibili.com/video/BV1pP4y187bR?t=190.0)一步一步操作
14 |
15 |
16 |
17 |
18 |
19 | ## 🚀 启动程序或激活窗口
20 |
21 | ### 概述
22 |
23 | - 此功能用来启动程序或激活窗口,其中「 激活窗口 」是亮点,因为程序往往只启动一次,却要激活它的窗口几十上百次
24 | - 基于模糊搜索的启动器 ( 比如开始菜单 ) 确实很方便,但在面对「 常用软件 」时,它们还不够方便:
25 | 1. 模糊搜索具有模糊性,需要担心和处理不匹配的情况,而快捷键是精准确定的,无脑按就行
26 | 2. 模糊搜索消耗的按键次数更多,至少要按 5 个键才能启动,而快捷键只需 1/2/3 个键
27 | 3. 缺乏激活窗口的能力,而激活窗口的频率要比启动频率高得多
28 |
29 | ### 如何配置
30 |
31 | 1. 先填第 2 个输入框,可以填「 程序路径 / 文件夹路径 / 网页链接 / PATH 中的命令 / 魔法链接 (参见后文) 」
32 | - 在文件管理器中,可用 `Capslock + Z` 复制选中文件的路径
33 | 2. 填完第 2 个输入框点左下角的「 保存 」,然后按一下当前热键,试试看能否成功启动
34 | 3. 程序启动后点一下「 查看窗口标识符 」,然后把程序的窗口标识符 ( 比如 `ahk_exe msedge.exe` ) 填到第 1 个输入框
35 |
36 | 
37 |
38 | ### 用官方热键进行激活
39 |
40 | - 像 QQ 和微信这些挂在后台的程序,需要用它们的官方热键进行激活
41 | - 比如 QQ 要按 Ctrl + Alt + Z 进行激活,微信要按 Ctrl + Alt + W 激活
42 | - 可以使用图中的函数:
43 | - ProcessExistSendKeyOrRun("WeChat.exe", "^!w", "shortcuts\微信.lnk")
44 | - 它的含义是: 如果 `WeChat.exe` 进程存在,那么输入 `^!w` (表示 Ctrl+Alt+W 热键) 激活微信,否则启动微信
45 |
46 | 
47 |
48 | ### 魔法链接
49 |
50 | - 在上上图的第 2 个输入框中填 https://example.com/ 能让浏览器打开指定的网页
51 | - 这个 `https:` 是一种 URI Scheme,可以让相关程序执行特定动作,除了 `https:` 还有其他格式的链接,比如:
52 | - 用 `shell:Downloads` 让文件资源管理器打开「 下载 」文件夹
53 | - 用 `shell:RecycleBinFolder` 让文件资源管理器打开「 回收站 」文件夹
54 | - 用 `ms-settings:bluetooth` 让 Windows 设置打开「 蓝牙 」页面
55 | - 用 `ms-settings:startupapps` 让 Windows 设置打开「 自启 」页面
56 | - 用 `steam://rungameid/1687950` 让 Steam 启动「 女神异闻录5 」游戏
57 | - 其中打开「 特殊文件夹 」和「 特定设置页 」比较常用,可以参考:
58 | - [文件资源管理器 ( shell: )](https://www.elevenforum.com/t/list-of-windows-11-shell-commands-for-shell-folder-shortcuts.1080/)
59 | - [Windows 设置 ( ms-settings: )](https://learn.microsoft.com/en-us/windows/uwp/launch-resume/launch-settings-app#ms-settings-uri-scheme-reference)
60 |
61 |
62 |
63 |
64 |
65 | ## 💎 输入文本或按键
66 |
67 | ### 概述
68 |
69 | - 此功能用来输入一串按键或文本,比较好用建议掌握👍,常用于:
70 | 1. 重映射不好按的快捷键: 比如用 Capslock+X 来触发 Alt+F4,能让快捷键变得更好按
71 | 2. 通过输入一串按键实现各种各样的目的:
72 | - 比如用 Capslock+D 来输入 Home、Shift+End、Backspace,能删除一行文本
73 | - 比如用 Capslock+R 来输入 Win+X、U 、R,能重启电脑
74 |
75 | ### 如何配置
76 |
77 | - 首先要把输入法切换到英文状态,因为接下来用到的标点符号,全都必须是「 英文标点符号 」!
78 | - 下图取自 Capslock 模式的 0 键,逐行解释如下:
79 |
80 | 
81 |
82 | #### ➤ 第一行
83 |
84 | - 含义为: 先用 home 键把光标移动到行首,然后用 shift + end 选中一整行,最后按 backspace 删除
85 |
86 | - `{home}` 表示 home 键,因为键名超出 1 个字母,所以要用大括号 `{}` 括起来 ( 特殊符号也一样 )
87 | - `+{end}` 表示 shift + end,请记住 `^ ! + #` 这四个特殊符号,它们常用于输入组合键,举个例子:
88 | - 用 `^a` 表示 ctrl + a
89 | - 用 `!f` 表示 alt + f
90 | - 用 `#s` 表示 win + s
91 | - 用 `!{f4}` 表示 alt + f4
92 | - 用 `^+{esc}` 表示 ctrl + shift + esc,能启动任务管理器
93 | - 用 `^!{tab}` 表示 ctrl + alt + tab,能打开窗口切换器
94 |
95 | #### ➤ 第二行
96 |
97 | - `{text}i love homura` 中的 `{text}` 表示以文本模式输入后面的内容,区别如下:
98 | - 使用 `love` 时,会依次输入 l、o、v、e 等 4 个按键
99 | - 使用 `{text}love` 时,会输入 `love` 这样一段文本,不受输入法中英文状态的影响
100 |
101 | #### ➤ 第三行
102 |
103 | - `sleep 1000` 表示等待 1000 毫秒,有时候要等程序处理完一组按键,再输入下一组按键,比如:
104 |
105 | 
106 |
107 | #### ➤ 补充
108 |
109 | 1. 这里有完整的「 [键名列表](https://wyagd001.github.io/v2/docs/lib/Send.htm#keynames) 」
110 | 2. 假设用 Capslock + X 输入 `F4` 键:
111 | - 如果想让 Capslock + Alt + X 变成 `Alt + F4`,那么得把 Capslock 模式的 X 键配成 `{blind}{f4}`
112 | - 通过 `{blind}` 盲从模式输入的键,会被已经按下的修饰键 ( Ctrl / Shift / Alt ) 影响
113 | - 比如已经按下了 Alt 键,那么输入的 `{blind}{f4}` 会变成 `Alt + F4`
114 |
115 |
116 |
117 |
118 |
119 | ## ⚙️ 添加自定义模式
120 |
121 | ### 星号的作用
122 |
123 | 点开 Settings 页会发现有的触发键以星号开头 ( 例如 3 模式的的 `*3` ),而有的没有星号 ( 例如 `Tab` )
124 |
125 | `*3` 中星号的作用是,在按住 Ctrl, Alt, Win, Shift 等键时,再按 3 也能触发 3 模式,所以 `Win+3+K` = `Win+2`
126 |
127 | 如果不加星号,那么按下 `Win+3` 就执行它原本的功能,仿佛 3 模式不存在
128 |
129 | 然后 Tab 模式的触发键不加星号是为了不影响 `Alt+Tab` 热键
130 |
--------------------------------------------------------------------------------
/config-ui/public/exe-path.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/exe-path.png
--------------------------------------------------------------------------------
/config-ui/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/favicon.ico
--------------------------------------------------------------------------------
/config-ui/public/font-compare/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/font-compare/1.png
--------------------------------------------------------------------------------
/config-ui/public/font-compare/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/font-compare/2.png
--------------------------------------------------------------------------------
/config-ui/public/font-compare/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/font-compare/3.png
--------------------------------------------------------------------------------
/config-ui/public/font-compare/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/font-compare/4.png
--------------------------------------------------------------------------------
/config-ui/public/font-compare/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/font-compare/5.png
--------------------------------------------------------------------------------
/config-ui/public/font-compare/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/font-compare/6.png
--------------------------------------------------------------------------------
/config-ui/public/img/example01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/img/example01.png
--------------------------------------------------------------------------------
/config-ui/public/img/example02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/img/example02.png
--------------------------------------------------------------------------------
/config-ui/public/img/example03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/img/example03.png
--------------------------------------------------------------------------------
/config-ui/public/img/example04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/img/example04.png
--------------------------------------------------------------------------------
/config-ui/public/uwp-shortcut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/public/uwp-shortcut.png
--------------------------------------------------------------------------------
/config-ui/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
36 |
--------------------------------------------------------------------------------
/config-ui/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/config-ui/src/assets/logo.png
--------------------------------------------------------------------------------
/config-ui/src/components/ActionCommentTable.vue:
--------------------------------------------------------------------------------
1 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 | |
53 |
54 | {{ item.comment }}
55 | |
56 |
57 |
58 |
59 |
60 |
61 |
71 |
--------------------------------------------------------------------------------
/config-ui/src/components/Code.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
19 |
--------------------------------------------------------------------------------
/config-ui/src/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Welcome to
7 |
8 | Vuetify
9 |
10 |
11 |
12 |
13 |
14 |
21 |
26 |
27 | Components
28 |
29 |
30 |
31 |
32 |
41 |
46 |
47 | Get Started
48 |
49 |
50 |
51 |
52 |
59 |
64 |
65 | Community
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
76 |
--------------------------------------------------------------------------------
/config-ui/src/components/Key.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
46 | {{ label }}
47 |
48 |
49 |
50 |
51 |
70 |
--------------------------------------------------------------------------------
/config-ui/src/components/NavigationDrawer.vue:
--------------------------------------------------------------------------------
1 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | MyKeymap
93 | {{ options.mykeymapVersion }}
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
103 |
104 |
105 |
106 | {{ keymap.name }}
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | {{ translate('label:507') }}
115 |
116 |
117 |
118 |
119 |
124 |
--------------------------------------------------------------------------------
/config-ui/src/components/Table.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | {{title}} |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
65 |
--------------------------------------------------------------------------------
/config-ui/src/components/Tip.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
25 |
--------------------------------------------------------------------------------
/config-ui/src/components/actions/Action.vue:
--------------------------------------------------------------------------------
1 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
85 |
86 |
87 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
123 |
--------------------------------------------------------------------------------
/config-ui/src/components/actions/ActivateOrRun.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {{ translate('label:309') }}
37 |
38 |
39 |
40 |
55 |
--------------------------------------------------------------------------------
/config-ui/src/components/actions/BuiltinFunction.vue:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Tips:
25 | (1) 自定义函数可放到 data\custom_functions.ahk,然后在此处调用
26 | (2) 复杂的脚本推荐做成独立的 ahk 文件,然后用 MyKeymap 启动那个 ahk 文件
27 |
28 |
29 |
34 |
--------------------------------------------------------------------------------
/config-ui/src/components/actions/Mouse.vue:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/config-ui/src/components/actions/MyKeymap.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/config-ui/src/components/actions/RadioGroup.vue:
--------------------------------------------------------------------------------
1 |
40 |
41 |
42 |
43 |
44 |
45 |
51 |
52 |
53 |
54 |
55 |
56 |
71 |
--------------------------------------------------------------------------------
/config-ui/src/components/actions/RemapKey.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
40 |
41 |
42 |
43 |
48 |
--------------------------------------------------------------------------------
/config-ui/src/components/actions/SendKey.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/config-ui/src/components/actions/System.vue:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/config-ui/src/components/actions/Text.vue:
--------------------------------------------------------------------------------
1 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/config-ui/src/components/actions/Window.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/config-ui/src/components/dialog/InputKeyValueDialog.vue:
--------------------------------------------------------------------------------
1 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | {{ props.title }}
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | {{ translate('label:609') }}
55 | {{ translate('label:610') }}
56 | {{ translate('label:611') }}
57 |
58 |
59 |
60 |
61 |
62 |
63 |
68 |
--------------------------------------------------------------------------------
/config-ui/src/components/dialog/PathDialog.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
26 |
27 | 先定义一个 programs
变量,值为 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\
,然后就能在「 程序路径 」中
28 | 使用 ahk-expression: programs "Microsoft Edge.lnk"
表示 C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Microsoft Edge.lnk
29 |
30 |
31 |
32 | 编辑路径变量
33 |
34 |
35 |
36 |
37 | 变量名
38 | 路径
39 |
40 |
41 |
42 |
43 |
45 |
46 |
47 |
49 |
50 |
51 |
52 |
53 |
54 |
60 |
--------------------------------------------------------------------------------
/config-ui/src/components/dialog/WindowGroupDialog.vue:
--------------------------------------------------------------------------------
1 |
31 |
32 |
33 |
35 |
36 | {{ translate('label:601') }}
37 |
38 |
39 | {{ translate('label:612') }}
40 |
41 |
42 | {{ translate('label:602') }}
43 | {{ translate('label:603') }}
44 | {{ translate('label:604') }}
45 |
46 |
47 |
48 |
50 |
51 |
52 |
54 |
55 |
56 |
59 |
60 |
61 |
62 |
63 |
64 | {{ translate('label:309') }}
65 |
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/config-ui/src/main.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * main.ts
3 | *
4 | * Bootstraps Vuetify and other plugins then mounts the App`
5 | */
6 |
7 | // Components
8 | import App from './App.vue'
9 |
10 | // Composables
11 | import { createApp } from 'vue'
12 |
13 | // Plugins
14 | import { registerPlugins } from '@/plugins'
15 |
16 | const app = createApp(App)
17 |
18 | registerPlugins(app)
19 |
20 | app.mount('#app')
21 |
--------------------------------------------------------------------------------
/config-ui/src/plugins/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * plugins/index.ts
3 | *
4 | * Automatically included in `./src/main.ts`
5 | */
6 |
7 | // Plugins
8 | import { loadFonts } from './webfontloader'
9 | import vuetify from './vuetify'
10 | import pinia from '../store'
11 | import router from '../router'
12 |
13 | // Types
14 | import type { App } from 'vue'
15 |
16 | export function registerPlugins (app: App) {
17 | loadFonts()
18 | app
19 | .use(vuetify)
20 | .use(router)
21 | .use(pinia)
22 | }
23 |
--------------------------------------------------------------------------------
/config-ui/src/plugins/vuetify.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * plugins/vuetify.ts
3 | *
4 | * Framework documentation: https://vuetifyjs.com`
5 | */
6 |
7 | // Styles
8 | import '@mdi/font/css/materialdesignicons.css'
9 | import 'vuetify/styles'
10 |
11 | // Composables
12 | import { createVuetify } from 'vuetify'
13 |
14 | // https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
15 | export default createVuetify({
16 | theme: {
17 | themes: {
18 | light: {
19 | colors: {
20 | primary: '#1867C0',
21 | secondary: '#5CBBF6',
22 | },
23 | },
24 | },
25 | },
26 | })
27 |
--------------------------------------------------------------------------------
/config-ui/src/plugins/webfontloader.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * plugins/webfontloader.ts
3 | *
4 | * webfontloader documentation: https://github.com/typekit/webfontloader
5 | */
6 |
7 | export async function loadFonts () {
8 | const webFontLoader = await import(/* webpackChunkName: "webfontloader" */'webfontloader')
9 |
10 | webFontLoader.load({
11 | // google: {
12 | // families: ['Roboto:100,300,400,500,700,900&display=swap'],
13 | // },
14 | })
15 | }
16 |
--------------------------------------------------------------------------------
/config-ui/src/router/index.ts:
--------------------------------------------------------------------------------
1 | // Composables
2 | import { createRouter, createWebHistory } from 'vue-router'
3 | import CustomHotkey from "@/views/CustomHotkey.vue";
4 | import Abbr from "@/views/Abbr.vue";
5 | import HomeSettings from '@/views/HomeSettings.vue';
6 | import Keymap from "@/views/Keymap.vue";
7 |
8 | const routes = [
9 | {
10 | path: "/",
11 | component: HomeSettings
12 | },
13 | {
14 | path: "/settings",
15 | component: HomeSettings
16 | },
17 | {
18 | path: "/keymap",
19 | children: [
20 | {
21 | path: ':id(1)',
22 | component: CustomHotkey
23 | },
24 | {
25 | path: ':id(2|3)',
26 | component: Abbr
27 | },
28 | {
29 | path: ':id',
30 | component: Keymap
31 | },
32 | ]
33 | },
34 | ]
35 |
36 | const router = createRouter({
37 | history: createWebHistory(process.env.BASE_URL),
38 | routes,
39 | })
40 |
41 | export default router
42 |
--------------------------------------------------------------------------------
/config-ui/src/store/app.ts:
--------------------------------------------------------------------------------
1 | // Utilities
2 | import { defineStore } from 'pinia'
3 |
4 | export const useAppStore = defineStore('app', {
5 | state: () => ({
6 | //
7 | }),
8 | })
9 |
--------------------------------------------------------------------------------
/config-ui/src/store/config.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from "pinia"
2 | import { computed, ref, watch } from "vue"
3 | import { useRoute } from "vue-router"
4 | import { Action, Config, Keymap } from "@/types/config";
5 | import { useMyFetch } from "./server";
6 | import trimStart from "lodash-es/trimStart";
7 | import { languageMap } from "./language-map";
8 | import { useThrottleFn } from "@vueuse/core";
9 |
10 |
11 | const defaultKeyboardLayout = "1 2 3 4 5 6 7 8 9 0\nq w e r t y u i o p\na s d f g h j k l ;\nz x c v b n m , . /\nspace enter backspace - [ ' singlePress"
12 | const keyboardLayout74 = "esc f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12\n` 1 2 3 4 5 6 7 8 9 0 - = backspace\ntab q w e r t y u i o p [ ] \\\ncapslock a s d f g h j k l ; ' enter\nLShift z x c v b n m , . / RShift\nLCtrl LWin LAlt space RAlt RWin RCtrl singlePress"
13 | const keyboardLayout104 = "esc f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12\n` 1 2 3 4 5 6 7 8 9 0 - = backspace\ntab q w e r t y u i o p [ ] \\\ncapslock a s d f g h j k l ; ' enter\nLShift z x c v b n m , . / RShift\nLCtrl LWin LAlt space RAlt RWin RCtrl singlePress\nPrintScreen ScrollLock Pause insert home pgup delete end pgdn up down left right\nnumpad0 numpad1 numpad2 numpad3 numpad4 numpad5 numpad6 numpad7 numpad8 numpad9\nNumpadDot NumpadEnter NumpadAdd NumpadSub NumpadMult NumpadDiv NumLock"
14 | const mouseButtons = "LButton RButton MButton XButton1 XButton2 WheelUp WheelDown WheelLeft WheelRight"
15 |
16 | export const useConfigStore = defineStore('config', () => {
17 | // 根据 url 返回对应的 keymap
18 | const config = fetchConfig()
19 | const route = useRoute()
20 | const keymap = ref()
21 | watch(
22 | () => config.value?.keymaps.find(x => x.id + '' === route.params.id),
23 | (newValue) => {
24 | keymap.value = newValue
25 | hotkey.value = "" // 防止串键, 会导致当前选择的键带到缩写模式中
26 | }
27 | )
28 |
29 | // 根据选中的 hotkey 和 windowGroupID, 返回对应的 action
30 | const hotkey = ref("")
31 | const windowGroupID = ref(0)
32 | const action = ref({ ...emptyAction })
33 | watch(
34 | () => _getAction(keymap.value, hotkey.value, windowGroupID.value),
35 | (newValue) => action.value = newValue
36 | )
37 |
38 | watch(() => [action.value.actionTypeID, action.value.actionValueID],
39 | ([newTypeId, newValueId], [oldTypeid, oldValueId]) => {
40 | if ((newTypeId == 9 || oldTypeid == 9) && (newValueId == 5 || oldValueId == 5 || newValueId == 6 || oldValueId == 6)) {
41 | changeAbbrEnable()
42 | }
43 | }
44 | )
45 |
46 | function changeAbbrEnable() {
47 | // 获取缩写Keymap
48 | const capsAbbr = config.value!.keymaps[config.value!.keymaps.length - 3]
49 | const seemAbbr = config.value!.keymaps[config.value!.keymaps.length - 2]
50 | // 默认为关闭状态
51 | let capsAbbrEnable = false
52 | let seemAbbrEnable = false
53 |
54 | for (let km: Keymap of enabledKeymaps.value) {
55 | // 不遍历缩写、设置
56 | if (km.id >= 2 && km.id <= 4) {
57 | console.log(km.id)
58 | continue;
59 | }
60 |
61 | // 当两个缩写状态都为开启时不再遍历
62 | if (capsAbbrEnable && seemAbbrEnable) {
63 | break
64 | }
65 |
66 | for (let key: string in km.hotkeys) {
67 | for (let act: Action of km.hotkeys[key]) {
68 | // 有选择caps命令将命令状态设置为开启
69 | if (act.actionTypeID == 9 && act.actionValueID == 6) {
70 | capsAbbrEnable = true
71 | continue
72 | }
73 |
74 | // 有选择缩写将缩写状态设置为开启
75 | if (act.actionTypeID == 9 && act.actionValueID == 5) {
76 | seemAbbrEnable = true
77 | }
78 | }
79 | }
80 | }
81 |
82 | // 设置缩写的状态
83 | capsAbbr.enable = capsAbbrEnable
84 | seemAbbr.enable = seemAbbrEnable
85 | }
86 |
87 | const changeActionComment = (label: string) => {
88 | action.value.comment = label
89 | }
90 |
91 | const keymaps = computed(() => config.value!.keymaps)
92 | const options = computed(() => config.value!.options)
93 |
94 | const enabledKeymaps = computed(() => keymaps.value.filter(x => x.enable))
95 | const customKeymaps = computed(() => keymaps.value.filter(x => x.id > 4))
96 | const customSonKeymaps = computed(() => customKeymaps.value.filter(x => x.parentID != 0))
97 | const customParentKeymaps = computed(() => {
98 | const arr = customKeymaps.value.filter(x => x.parentID == 0)
99 | arr.unshift({ id: 0, name: "-", hotkey: "", parentID: 0, enable: true, hotkeys: {}, delay: 0 })
100 | return arr;
101 | })
102 |
103 |
104 | const hotkeys = computed(() => keymap.value?.hotkeys!)
105 |
106 | function changeHotkey(oldHotkey: string, newHotkey: string) {
107 | // 如果热键已存在且当前键的action.actionTypeID 为0删除当前热键,不为0删除之前的热键
108 | if (newHotkey in hotkeys.value) {
109 | if (hotkeys.value[oldHotkey][0].actionTypeID == 0) {
110 | removeHotkey(oldHotkey)
111 | return newHotkey
112 | } else {
113 | removeHotkey(newHotkey)
114 | }
115 | }
116 |
117 | // 如果直接替换会导致hotkey的位置在最下方
118 | keymap.value!.hotkeys = Object.keys(hotkeys.value).reduce((result: { [key: string]: Array }, key) => {
119 | if (key == oldHotkey) {
120 | result[newHotkey] = hotkeys.value[key];
121 | } else {
122 | result[key] = hotkeys.value[key];
123 | }
124 | return result;
125 | }, {})
126 | }
127 |
128 | function removeHotkey(hotkey: string) {
129 | delete hotkeys.value[hotkey]
130 | }
131 |
132 | function addHotKey(key: string = "") {
133 | keymap.value!.hotkeys[key] = [{ ...emptyAction }]
134 | }
135 |
136 | function resetKeyboardLayout(num: number) {
137 | if (num == 0) {
138 | options.value.keyboardLayout = defaultKeyboardLayout
139 | } else if (num == 74) {
140 | options.value.keyboardLayout = keyboardLayout74
141 | } else if (num == 104) {
142 | options.value.keyboardLayout = keyboardLayout104
143 | } else if (num == 1) {
144 | options.value.keyboardLayout += '\n' + mouseButtons
145 | }
146 | }
147 |
148 | function translate(comment: string) {
149 | const m = languageMap as any
150 | if (comment && comment.startsWith('label:')) {
151 | const id = comment.substring(6)
152 | if (m[id]) {
153 | return m[id][config.value!.options.language] ?? m[id]['en']
154 | }
155 | }
156 | return comment
157 | }
158 |
159 | return {
160 | config, keymap, hotkey, windowGroupID, action, enabledKeymaps, customKeymaps, options, hotkeys,
161 | customParentKeymaps, customSonKeymaps, keymaps, changeActionComment, changeAbbrEnable, resetKeyboardLayout,
162 | changeHotkey, removeHotkey, addHotKey, translate,
163 | disabledKeys: computed(() => _disabledKeys(enabledKeymaps.value)),
164 | getAction: (hotkey: string) => _getAction(keymap.value, hotkey, windowGroupID.value),
165 | saveConfig: useThrottleFn(() => _saveConfig(config.value), 1000),
166 | }
167 | })
168 |
169 | const emptyAction: Action = {
170 | windowGroupID: 0,
171 | actionTypeID: 0,
172 | isEmpty: true,
173 | }
174 |
175 | function _getAction(keymap: Keymap | undefined, hotkey: string, windowGroupID: number): Action {
176 | if (!keymap || !hotkey) {
177 | return { ...emptyAction }
178 | }
179 |
180 | // keymap 中此热键还不存在, 那么初始化一下
181 | let actions = keymap.hotkeys[hotkey]
182 | if (!actions) {
183 | actions = []
184 | keymap.hotkeys[hotkey] = actions
185 | // 比如新增了 2 模式, 让它的 singlePress 默认为输入 2 键
186 | if (keymap.isNew && hotkey == 'singlePress') {
187 | console.log('singlePress')
188 | const key = trimStart(keymap.hotkey, ' #!^+<>*~$')
189 | actions.push({
190 | windowGroupID: 0,
191 | actionTypeID: 6,
192 | isEmpty: false,
193 | keysToSend: '{blind}{' + key + '}',
194 | comment: '输入 ' + key + ' 键'
195 | })
196 | }
197 | }
198 |
199 | // 选择的 windowGroupID 还没有对应的 action, 那么初始化一下
200 | let found = actions.find(x => x.windowGroupID === windowGroupID)
201 | if (!found) {
202 | found = { ...emptyAction, windowGroupID }
203 | actions.push(found)
204 | actions.sort((a, b) => a.windowGroupID - b.windowGroupID)
205 | }
206 | return found
207 | }
208 |
209 | function _saveConfig(config: Config | undefined) {
210 | if (!config) {
211 | return
212 | }
213 |
214 | // 克隆一下, 然后删掉空的 action
215 | config = JSON.parse(JSON.stringify(config))
216 | for (const km of config!.keymaps) {
217 | const keySet = new Set(parseKeyboardLayout(config!.options.keyboardLayout, km.hotkey).flatMap(x => x))
218 | for (const [hk, actions] of Object.entries(km.hotkeys)) {
219 | const filterd = actions.filter(x => !x.isEmpty)
220 | const normalKeymap = km.id > 4
221 | if (filterd.length > 0 && (!normalKeymap || (normalKeymap && keySet.has(hk)))) {
222 | km.hotkeys[hk] = filterd
223 | } else {
224 | delete km.hotkeys[hk]
225 | }
226 | }
227 | }
228 | useMyFetch("/config", { timeout: 1500, })
229 | .put(config)
230 | .onFetchError(err => {
231 | console.error(err)
232 | alert(`保存失败,可能设置程序被关了, ${err.name}:${err.code}`)
233 | }
234 | )
235 | }
236 |
237 | function _disabledKeys(keymaps: Keymap[]) {
238 | // 返回值为各个 keymap 该禁用的热键, 比如 {1: {"*3": true} }
239 | const m = {} as any
240 | for (const km of keymaps) {
241 | if (!m[km.id]) {
242 | m[km.id] = {}
243 | }
244 | m[km.id][km.hotkey.toLowerCase()] = true
245 | m[km.id]['*' + km.hotkey.toLowerCase()] = true
246 |
247 | if (!m[km.parentID]) {
248 | m[km.parentID] = {}
249 | }
250 | m[km.parentID][km.hotkey.toLowerCase()] = true
251 | m[km.parentID]['*' + km.hotkey.toLowerCase()] = true
252 | }
253 | return m
254 | }
255 |
256 | function fetchConfig() {
257 | const config = ref()
258 | const { data, error } = useMyFetch("/config").json()
259 | watch(data, (val) => {
260 | val = val!
261 | // 这个字段是后加的, 旧的 config.json 肯定没有此字段, 所以要初始化
262 | if (!val.options.keyboardLayout) {
263 | val.options.keyboardLayout = defaultKeyboardLayout
264 | }
265 | // 初始化语言
266 | if (!val.options.language) {
267 | if (navigator.language.includes('zh-')) {
268 | val.options.language = 'zh'
269 | } else {
270 | val.options.language = 'en'
271 | }
272 | }
273 | // 初始化排除列表
274 | if (val.options.windowGroups[0].id != -1) {
275 | val.options.windowGroups.unshift({
276 | id: -1,
277 | name: "🚫 Exclude",
278 | value: "",
279 | })
280 | }
281 | config.value = val
282 | })
283 | return config
284 | }
285 |
286 | export const parseKeyboardLayout = (layout: string, keymapHotkey: string) => {
287 | const rows = layout
288 | .split('\n')
289 | .filter(x => x.trim())
290 | .map(
291 | line => {
292 | const keys = line.split(/\s+/).filter(x => x.trim())
293 | return keys.map(key => {
294 | if (key == 'singlePress') {
295 | return key
296 | }
297 | return '*' + key
298 | })
299 | }
300 | )
301 |
302 | if (keymapHotkey.toLowerCase().includes("button")) {
303 | const list = rows.flatMap(x => x)
304 | const res = []
305 | if (!list.includes("*LButton")) { res.push("*LButton") }
306 | if (!list.includes("*MButton")) { res.push("*MButton") }
307 | if (!list.includes("*RButton")) { res.push("*RButton") }
308 | if (!list.includes("*WheelUp")) { res.push("*WheelUp") }
309 | if (!list.includes("*WheelDown")) { res.push("*WheelDown") }
310 | if (!list.includes("*XButton1")) { res.push("*XButton1") }
311 | if (!list.includes("*XButton2")) { res.push("*XButton2") }
312 |
313 | if (res.length > 0) {
314 | rows.push(res)
315 | }
316 | }
317 | return rows
318 | }
319 |
--------------------------------------------------------------------------------
/config-ui/src/store/index.ts:
--------------------------------------------------------------------------------
1 | // Utilities
2 | import { createPinia } from 'pinia'
3 |
4 | export default createPinia()
5 |
--------------------------------------------------------------------------------
/config-ui/src/store/language-map.ts:
--------------------------------------------------------------------------------
1 | export const languageList = [
2 | { title: '简体中文', value: 'zh' },
3 | { title: 'English', value: 'en' },
4 | ]
5 |
6 | export const languageMap = {
7 | // window
8 | 1: { zh: "关闭窗口", en: "Close", },
9 | 2: { zh: "切换到上一个窗口", en: "Previous window", },
10 | 3: { zh: "在当前程序的窗口间轮换", en: "Cycle through app windows", },
11 | 4: { zh: "窗口管理 (EDSF切换X关闭,空格选择)", en: "Task switcher (EDSF=↑↓←→,X=Delete", },
12 | 5: { zh: "上一个虚拟桌面", en: "Previous virtual desktop", },
13 | 6: { zh: "下一个虚拟桌面", en: "Next virtual desktop", },
14 | 7: { zh: "移动窗口到下一个显示器", en: "Move window to next monitor", },
15 | 8: { zh: "关闭窗口 (杀进程)", en: "Kill the active process", },
16 | 9: { zh: "关闭同类窗口", en: "Close all app windows", },
17 | 10: { zh: "窗口最小化", en: "Minimize", },
18 | 11: { zh: "窗口最大化或还原", en: "Maximize", },
19 | 12: { zh: "窗口居中 (1200x800)", en: "Center (1200x800)", },
20 | 13: { zh: "窗口居中 (1370x930)", en: "Center (1370x930)", },
21 | 14: { zh: "切换窗口置顶状态", en: "Always on top", },
22 | 15: { zh: "让窗口随鼠标拖动", en: "Drag window", },
23 | 16: { zh: "绑定当前窗口 (长按绑定,短按激活)", en: "Bind window by long press", },
24 |
25 | // system
26 | 17: { zh: "锁屏", en: "Lock the screen", },
27 | 18: { zh: "睡眠", en: "Sleep", },
28 | 19: { zh: "关机", en: "Shutdown", },
29 | 20: { zh: "重启", en: "Reboot", },
30 | 21: { zh: "音量调节", en: "Volume control", },
31 | 22: { zh: "显示器亮度调节", en: "Monitor brightness control", },
32 | 23: { zh: "重启文件资源管理器", en: "Restart explorer.exe", },
33 | 24: { zh: "复制文件路径或纯文本", en: "Copy full path of a file ", },
34 | 2401: { zh: "静音当前应用", en: "Mute active app", },
35 | 2402: { zh: "打开当前程序所在目录", en: "Show active process in folder", },
36 |
37 | // mouse
38 | 25: { zh: "鼠标上移", en: "Mouse up" },
39 | 26: { zh: "鼠标下移", en: "Mouse down" },
40 | 27: { zh: "鼠标左移", en: "Mouse left" },
41 | 28: { zh: "鼠标右移", en: "Mouse right" },
42 | 29: { zh: "滚轮上滑", en: "Wheel up" },
43 | 30: { zh: "滚轮下滑", en: "Wheel down" },
44 | 31: { zh: "滚轮左滑", en: "Wheel left" },
45 | 32: { zh: "滚轮右滑", en: "Wheel right" },
46 | 33: { zh: "鼠标左键", en: "Left button" },
47 | 34: { zh: "鼠标右键", en: "Right button" },
48 | 35: { zh: "鼠标中键", en: "Middle button" },
49 | 36: { zh: "鼠标左键按下 (之后按空格松开)", en: "Left button down ( Space=Release )", },
50 | 37: { zh: "移动鼠标到活动窗口", en: "Move mouse to active window", },
51 |
52 | // text
53 | 38: { zh: "光标 - 上移", en: "Up" },
54 | 39: { zh: "光标 - 下移", en: "Down" },
55 | 40: { zh: "光标 - 左移", en: "Left" },
56 | 41: { zh: "光标 - 右移", en: "Right" },
57 | 42: { zh: "光标 - 跳到行首", en: "Home" },
58 | 43: { zh: "光标 - 跳到行尾", en: "End" },
59 | 44: { zh: "光标 - 上一单词", en: "Ctrl + Left" },
60 | 45: { zh: "光标 - 下一单词", en: "Ctrl + Right" },
61 |
62 | 46: { zh: "选择 - 往上", en: "Shift + Up" },
63 | 47: { zh: "选择 - 往下", en: "Shift + Down" },
64 | 48: { zh: "选择 - 往左", en: "Shift + Left" },
65 | 49: { zh: "选择 - 往右", en: "Shift + Right" },
66 | 50: { zh: "选择 - 选到行首", en: "Shift + Home" },
67 | 51: { zh: "选择 - 选到行尾", en: "Shift + End" },
68 | 52: { zh: "选择 - 上一单词", en: "Ctrl+Shift+Left" },
69 | 53: { zh: "选择 - 下一单词", en: "Ctrl+Shift+Right" },
70 |
71 | 54: { zh: "右键菜单", en: "Menu key" },
72 | 55: { zh: "删除一行文本", en: "Delete a line" },
73 | 56: { zh: "删除一个单词", en: "Delete a word" },
74 | 57: { zh: "Shift 键", en: "Shift" },
75 | 58: { zh: "Ctrl 键", en: "Ctrl" },
76 | 59: { zh: "Alt 键", en: "Alt" },
77 | 60: { zh: "Win 键", en: "Win" },
78 | 61: { zh: "中英文之间加空格", en: "中英文间加空格" },
79 |
80 | 62: { en: "Esc" },
81 | 63: { en: "Backspace" },
82 | 64: { en: "Enter" },
83 | 65: { en: "Delete" },
84 | 66: { en: "Insert" },
85 | 67: { en: "Tab" },
86 | 68: { en: "Ctrl + Tab" },
87 | 69: { en: "Shift + Tab" },
88 | 70: { en: "Ctrl+Shift+Tab" },
89 |
90 | // MyKeymap
91 | 71: { zh: "暂停 MyKeymap", en: "Pause" },
92 | 72: { zh: "重启 MyKeymap", en: "Reload" },
93 | 73: { zh: "退出 MyKeymap", en: "Exit" },
94 | 74: { zh: "打开 MyKeymap 设置", en: "Settings" },
95 |
96 | 75: { zh: "触发 Abbreviation", en: "Abbreviation" },
97 | 76: { zh: "触发 Command", en: "Command" },
98 | 77: { zh: "切换 CapsLock 大小写", en: "CapsLock" },
99 | 78: { zh: "锁定当前模式 (免去按住触发键)", en: "Hold down/release the modifier key" },
100 |
101 | // Action types
102 | 200: { zh: "⛔ 未配置", en: "⛔ Undefined" },
103 | 201: { zh: "🚀 启动程序或激活窗口", en: "🚀 App Launcher" },
104 | 202: { zh: "🖥️ 系统控制", en: "🖥️ System" },
105 | 203: { zh: "🏠 窗口操作", en: "🏠 Window" },
106 | 204: { zh: "🖱️ 鼠标操作", en: "🖱️ Mouse" },
107 | 205: { zh: "🅰️ 重映射按键", en: "🅰️ Remap Keys" },
108 | 206: { zh: "🅰️ 输入按键或文本", en: "🅰️ Send Keys" },
109 | 207: { zh: "📚 文字编辑相关", en: "📚 Edit" },
110 | 208: { zh: "⚛️ 自定义函数", en: "⚛️ Custom Functions" },
111 | 209: { zh: "⚙️ MyKeymap 相关", en: "⚙️ MyKeymap" },
112 |
113 | // App Launcher
114 | 301: { zh: "要激活的窗口 (窗口标识符)", en: "The window to activate" },
115 | 302: { zh: "当窗口不存在时要启动的: 程序 / 文件夹 / URL", en: "Target" },
116 | 303: { zh: "命令行参数", en: "Arguments" },
117 | 304: { zh: "工作目录", en: "Working directory" },
118 | 305: { zh: "备注", en: "Comment" },
119 | 306: { zh: "以管理员运行", en: "Admin" },
120 | 307: { zh: "后台运行", en: "Hide" },
121 | 308: { zh: "检测隐藏窗口", en: "Detect hidden windows" },
122 | 309: { zh: "🔍 查看窗口标识符", en: "🔍 Window Spy" },
123 |
124 | // Other
125 | 401: { zh: "重映射为", en: "Remap to" },
126 | 402: { zh: "要输入的按键或文本", en: "Keys" },
127 | 403: { zh: "代码", en: "Code" },
128 | 404: { zh: "热键", en: "Hotkey" },
129 | 405: { zh: "新增一个", en: "Add" },
130 | 406: { zh: "输入ab按回车添加/切换到ab, del ab删除ab, rn cd重命名当前为cd", en: "Input 'ab' and Enter to add a new item. Delete => del ab. Rename => rn cd."},
131 |
132 | // Settings
133 | 501: { zh: "名称", en: "Name" },
134 | 502: { zh: "触发键", en: "Modifer" },
135 | 503: { zh: "上层", en: "Parent" },
136 | 504: { zh: "开关", en: "" },
137 | 505: { zh: "其他设置", en: "Other" },
138 | 506: { zh: "开机自启", en: "Run on system startup" },
139 | 507: { zh: "保存配置(CTRL+S)", en: "Save(CTRL+S)" },
140 |
141 |
142 | // Window Groups
143 | 601: { zh: "😺 编辑程序分组", en: "😺 Window Groups" },
144 | 602: { zh: "组名", en: "Group Name" },
145 | 603: { zh: "窗口标识符", en: "Window List" },
146 | 604: { zh: "条件", en: "Condition" },
147 | 605: { zh: "是前台窗口", en: "is active" },
148 | 606: { zh: "这些窗口存在", en: "exist" },
149 | 607: { zh: "不是前台窗口", en: "not active" },
150 | 608: { zh: "这些窗口不存在", en: "not exist" },
151 | 609: { zh: "添加一行", en: "Add" },
152 | 610: { zh: "保存", en: "Save" },
153 | 611: { zh: "取消", en: "Cancel" },
154 | 612: { zh: "注意: MyKeyamp 的所有热键在 Exclude 组中会被禁用", en: "Note: MyKeymap will be disabled in the \"Exclude\" group." },
155 |
156 | // Mouse Options
157 | 701: { zh: "🖱️ 修改鼠标参数", en: "🖱️ Mouse Options" },
158 | 702: { zh: "鼠标移动相关参数", en: "Mouse Options" },
159 | 703: { zh: "进入连续移动前的延时(秒)", en: "Delay 1" },
160 | 704: { zh: "两次移动的间隔时间(秒)", en: "Delay 2" },
161 | 705: { zh: "快速模式步长(像素)", en: "Length 1" },
162 | 706: { zh: "快速模式首步长(像素)", en: "Length 2" },
163 | 707: { zh: "慢速模式步长(像素)", en: "Length 3" },
164 | 708: { zh: "慢速模式首步长(像素)", en: "Length 4" },
165 | 709: { zh: "鼠标模式的提示符", en: "Prompt" },
166 | 710: { zh: "提示进入了鼠标模式", en: "Show prompt" },
167 | 711: { zh: "点击鼠标后不退出鼠标模式", en: "Use only the space key to exit" },
168 | 712: { zh: "滚轮相关参数", en: "Scroll Wheel Options" },
169 | 713: { zh: "进入连续滚动前的延时 (秒)", en: "Delay 1" },
170 | 714: { zh: "两次滚动的间隔时间 (越小滚动速度越快)", en: "Delay 2" },
171 | 715: { zh: "一次滚动的行数", en: "Lines to scroll at a time" },
172 |
173 | // Keyboard Layout
174 | 721: { zh: "⌨️ 修改键盘布局", en: "⌨️ Keyboard Layout" },
175 | 722: { zh: "键盘布局", en: "Keyboard Layout" },
176 | 723: { zh: "重置为默认值", en: "Default" },
177 | 724: { zh: "重置为 74 键", en: "74 Key" },
178 | 725: { zh: "重置为 104 键", en: "104 Key" },
179 | 726: { zh: "添加鼠标按钮", en: "Add Mouse Buttons" },
180 |
181 | // Command Window
182 | 741: { zh: "✨ 命令框皮肤", en: "✨ Command Window" },
183 | 742: { zh: "命令框皮肤", en: "Command Window" },
184 | 743: { zh: "窗口宽度", en: "Width" },
185 | 744: { zh: "窗口 Y 轴位置 (百分比)", en: "Y pos" },
186 | 745: { zh: "窗口圆角大小", en: "Border radius" },
187 | 746: { zh: "窗口动画持续时间", en: "Animation duration" },
188 | 747: { zh: "窗口背景色", en: "Background color" },
189 | 748: { zh: "透明度", en: "Opacity" },
190 | 749: { zh: "网格线颜色", en: "Gridline color" },
191 | 750: { zh: "边框宽度", en: "Border width" },
192 | 751: { zh: "边框颜色", en: "Border color" },
193 | 752: { zh: "按键颜色", en: "Key color" },
194 | 753: { zh: "四角颜色", en: "Corner color" },
195 | 754: { zh: "窗口阴影大小", en: "Drop shadow size" },
196 | 755: { zh: "窗口阴影颜色", en: "Drop shadow color" },
197 |
198 |
199 | 761: { zh: "🕗 设置触发延时", en: "🕗 Modifer Delay" },
200 | 762: { zh: "触发延时 (毫秒)", en: "Modifer Delay (ms)" },
201 | 763: { zh: "一般推荐设为 0,让模式立刻生效。", en: "If delay > 0, you need long press to use the keymap." },
202 | 764: { zh: "如果设置大于零的值,即通过长按触发模式,也许能减少打字误触。", en: "" },
203 | 765: { zh: "但会有另一种形式的误触,比如想输入热键,但长按时间不够,所以触发热键失败。", en: "" },
204 |
205 | 781: { zh: "🌐 切换语言", en: "🌐 Change Language" },
206 |
207 |
208 |
209 | };
210 |
--------------------------------------------------------------------------------
/config-ui/src/store/server.ts:
--------------------------------------------------------------------------------
1 | import { createFetch } from "@vueuse/core"
2 |
3 | export const useMyFetch = createFetch({
4 | baseUrl: import.meta.env.MODE == 'development' ? 'http://localhost:12333' : '',
5 | options: {
6 | }
7 | })
8 |
9 |
10 | export const server = {
11 | runWindowSpy: () => useMyFetch('/server/command/2').post(),
12 | enableRunAtStartup: () => useMyFetch('/server/command/3').post(),
13 | disableRunAtStartup: () => useMyFetch('/server/command/4').post(),
14 | }
15 |
--------------------------------------------------------------------------------
/config-ui/src/store/shortcut.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia'
2 | import { ref, watch } from 'vue'
3 | import { useMyFetch } from './server'
4 |
5 | interface Shortcut {
6 | path: string
7 | }
8 |
9 | export const useShortcutStore = defineStore('shortcut', () => {
10 | const shortcuts = fetchShortcuts()
11 | return { shortcuts }
12 | })
13 |
14 | const fetchShortcuts = () => {
15 | const shortcuts = ref()
16 | const { data, error } = useMyFetch('/shortcuts').json()
17 | watch(data, (newValue) => shortcuts.value = newValue?.map(x => x.path))
18 | return shortcuts
19 | }
20 |
--------------------------------------------------------------------------------
/config-ui/src/styles/settings.scss:
--------------------------------------------------------------------------------
1 | /**
2 | * src/styles/settings.scss
3 | *
4 | * Configures SASS variables and Vuetify overwrites
5 | */
6 |
7 | // https://vuetifyjs.com/features/sass-variables/`
8 | // @use 'vuetify/settings' with (
9 | // $color-pack: false
10 | // );
11 |
--------------------------------------------------------------------------------
/config-ui/src/types/config.ts:
--------------------------------------------------------------------------------
1 | export interface Action {
2 | isEmpty: boolean
3 | windowGroupID: number
4 | actionTypeID: number
5 | comment?: string
6 |
7 | actionValueID?: number
8 |
9 | remapToKey?: string
10 | keysToSend?: string
11 | ahkCode?: string
12 |
13 | winTitle?: string
14 | target?: string
15 | args?: string
16 | workingDir?: string
17 | runAsAdmin?: boolean
18 | runInBackground?: boolean
19 | detectHiddenWindow?: boolean
20 |
21 | }
22 | export interface Keymap {
23 | id: number
24 | name: string
25 | enable: boolean
26 | hotkey: string
27 | parentID: number
28 | delay: number
29 | isNew?: boolean
30 | hotkeys: {
31 | [key: string]: Array
32 | }
33 | }
34 |
35 | export interface Scroll {
36 | delay1: string
37 | delay2: string
38 | onceLineCount: string;
39 | }
40 |
41 | export interface Mouse {
42 | delay1: string
43 | delay2: string
44 | fastSingle: string
45 | fastRepeat: string
46 | slowSingle: string
47 | slowRepeat: string
48 | keepMouseMode: boolean
49 | showTip: boolean
50 | tipSymbol: string
51 | }
52 |
53 | export interface WindowGroup {
54 | id: number
55 | name: string
56 | value: string
57 | conditionType?: number
58 | }
59 |
60 | export type PathVariable = {
61 | name: string
62 | value: string
63 | }
64 |
65 | export interface Options {
66 | mykeymapVersion: string
67 | scroll: Scroll
68 | mouse: Mouse
69 | windowGroups: Array
70 | pathVariables: Array
71 | customShellMenu: string
72 | startup: boolean
73 | language: string
74 | keyMapping: string
75 | keyboardLayout: string
76 | commandInputSkin: any
77 | }
78 |
79 | export interface Config {
80 | keymaps: Array
81 | options: Options
82 | }
83 |
--------------------------------------------------------------------------------
/config-ui/src/views/Abbr.vue:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 | {{ formatSpace(hotkey as string) }}
65 |
66 |
67 |
68 |
69 |
70 |
71 |
73 |
--------------------------------------------------------------------------------
/config-ui/src/views/CustomHotkey.vue:
--------------------------------------------------------------------------------
1 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
52 |
53 |
56 | |
57 | {{ translate(getActionComment(action)) }} |
58 |
59 |
61 | |
62 |
63 |
64 |
65 |
66 | {{ translate('label:405') }}
67 |
68 |
69 |
70 |
71 |
72 |
73 |
85 |
86 |
87 |
88 | !c = Alt + C
89 | #c = Win + C
90 | ^c = Ctrl + C
91 | ^!c = Ctrl + Alt + C
92 | ^+c = Ctrl + Shift + C
93 | +!c = Shift + Alt + C
94 |
95 |
96 |
97 |
98 |
99 |
100 | F11 = F11
101 | !1 = Alt + 1
102 | +F2 = Shift + F2
103 | !space = Alt + Space
104 |
105 |
106 | 更多特殊按键参考: reference
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
141 |
--------------------------------------------------------------------------------
/config-ui/src/views/Home.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
34 |
--------------------------------------------------------------------------------
/config-ui/src/views/HomeSettings.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/config-ui/src/views/Keymap.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
Error: keymap not found
34 |
35 |
36 | {{ getKeyText(hotkey) }}
37 |
38 |
39 |
40 |
41 |
42 |
51 |
--------------------------------------------------------------------------------
/config-ui/src/vite-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import type { DefineComponent } from 'vue'
5 | const component: DefineComponent<{}, {}, any>
6 | export default component
7 | }
8 |
--------------------------------------------------------------------------------
/config-ui/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "target": "esnext",
5 | "useDefineForClassFields": true,
6 | "allowSyntheticDefaultImports": true,
7 | "module": "esnext",
8 | "moduleResolution": "node",
9 | "strict": true,
10 | "jsx": "preserve",
11 | "sourceMap": false,
12 | "resolveJsonModule": true,
13 | "esModuleInterop": true,
14 | "lib": ["esnext", "dom"],
15 | "types": [
16 | "node",
17 | "vuetify",
18 | // 不添加这个运行tsc --noEmit会抛出Cannot find name 'BluetoothLEScanFilter' 异常
19 | "web-bluetooth"
20 | ],
21 | "paths": {
22 | "@/*": ["src/*"]
23 | },
24 | },
25 | "include": [
26 | "src/**/*.ts",
27 | "src/**/*.d.ts",
28 | "src/**/*.tsx",
29 | "src/**/*.vue",
30 | "vite.config.ts"
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/config-ui/tsconfig.node.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": true,
4 | "module": "ESNext",
5 | "moduleResolution": "Node",
6 | "allowSyntheticDefaultImports": true
7 | },
8 | "include": ["vite.config.ts"]
9 | }
10 |
--------------------------------------------------------------------------------
/config-ui/vite.config.ts:
--------------------------------------------------------------------------------
1 | // Plugins
2 | import vue from '@vitejs/plugin-vue'
3 | import vuetify, { transformAssetUrls } from 'vite-plugin-vuetify'
4 |
5 | // Utilities
6 | import { defineConfig } from 'vite'
7 | import { fileURLToPath, URL } from 'node:url'
8 |
9 | // https://vitejs.dev/config/
10 | export default defineConfig({
11 | plugins: [
12 | vue({
13 | template: { transformAssetUrls }
14 | }),
15 | // https://github.com/vuetifyjs/vuetify-loader/tree/next/packages/vite-plugin
16 | vuetify({
17 | autoImport: true,
18 | styles: {
19 | configFile: 'src/styles/settings.scss',
20 | },
21 | }),
22 | ],
23 | define: { 'process.env': {} },
24 | resolve: {
25 | alias: {
26 | '@': fileURLToPath(new URL('./src', import.meta.url))
27 | },
28 | extensions: [
29 | '.js',
30 | '.json',
31 | '.jsx',
32 | '.mjs',
33 | '.ts',
34 | '.tsx',
35 | '.vue',
36 | ],
37 | },
38 | server: {
39 | port: 3000,
40 | },
41 | })
42 |
--------------------------------------------------------------------------------
/data/custom_functions.ahk:
--------------------------------------------------------------------------------
1 | ; 自定义的函数写在这个文件里, 然后能在 MyKeymap 中调用
2 |
3 | ; 使用如下写法,来加载当前目录下的其他 AutoHotKey v2 脚本
4 | ; #Include ../data/test.ahk
5 |
6 | sendSomeChinese() {
7 | Send("{text}你好中文!")
8 | }
--------------------------------------------------------------------------------
/doc/features.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/doc/features.png
--------------------------------------------------------------------------------
/doc/settings.en.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/doc/settings.en.png
--------------------------------------------------------------------------------
/doc/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/doc/settings.png
--------------------------------------------------------------------------------
/doc/夏日大作战.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/doc/夏日大作战.gif
--------------------------------------------------------------------------------
/lanzou_client.py:
--------------------------------------------------------------------------------
1 | import json
2 | import sys
3 | try:
4 | from lanzou.api import LanZouCloud
5 | except:
6 | print('missing package: pip3 install lanzou-api')
7 | exit(1)
8 |
9 | def show_progress(file_name, total_size, now_size):
10 | """显示进度的回调函数"""
11 | percent = now_size / total_size
12 | bar_len = 40 # 进度条长总度
13 | bar_str = '>' * round(bar_len * percent) + '=' * round(bar_len * (1 - percent))
14 | print('\r{:.2f}%\t[{}] {:.1f}/{:.1f}MB | {} '.format(
15 | percent * 100, bar_str, now_size / 1048576, total_size / 1048576, file_name), end='')
16 | if total_size == now_size:
17 | print('') # 下载完成换行
18 |
19 |
20 | def handler(fid, is_file):
21 | if is_file:
22 | # lzy.set_desc(fid, '设置文件的描述', is_file=True)
23 | lzy.set_passwd(fid, '1234', is_file)
24 | info = lzy.get_share_info(fid, is_file)
25 | json.dump({'url': info.url, 'password': info.pwd}, sys.stderr)
26 | print('', file=sys.stderr)
27 |
28 | def assertSuccess(code):
29 | if code != LanZouCloud.SUCCESS:
30 | raise RuntimeError("error code: " + str(code))
31 |
32 | def login():
33 | with open('lanzou_cookie.txt', 'r') as f:
34 | lines = f.read().splitlines()
35 | cookie = {'ylogin': lines[0], 'phpdisk_info': lines[1]}
36 | code = lzy.login_by_cookie(cookie)
37 | assertSuccess(code)
38 |
39 | if __name__ == '__main__':
40 | lzy = LanZouCloud()
41 | # 突然出现的 bug, 似乎换 ua 能解决: https://github.com/zaxtyson/LanZouCloud-API/issues/101
42 | lzy._headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36"
43 | file = sys.argv[1]
44 | login()
45 | code = lzy.upload_file(file, -1, callback=show_progress, uploaded_handler=handler)
46 | assertSuccess(code)
--------------------------------------------------------------------------------
/readme.en.md:
--------------------------------------------------------------------------------
1 | # MyKeymap
2 |
3 | A program helps you improve the efficiency of using the keyboard.
4 |
5 |
6 | ## Features
7 |
8 | - Quickly start and switch any application
9 | - ...
10 |
11 | ## Usage
12 |
13 | - Enter `CapsLock`, `S`, `E` to open settings page.
14 |
15 | ## Screenshots
16 | 
17 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/xianyukang/MyKeymap/blob/master/readme.en.md)
2 |
3 | # MyKeymap
4 |
5 | MyKeymap 是一款基于 [AutoHotkey](https://www.autohotkey.com/) 的键盘映射工具,用于增强 Windows 的键盘输入体验和窗口操作效率。
6 |
7 | ## Features
8 |
9 | - **程序启动切换**: 用快捷键启动程序和切换窗口,对比搜索型的启动器,效率更高
10 | - **键盘控制鼠标**: 用键盘控制鼠标,能减少键鼠切换,不必为了点一下而大幅移动手掌
11 | - **按键重新映射**:
12 | - 把常用按键重映射到主键区,能大大提升输入速度、编辑文字的效率
13 | - 内置了几套键位负责:「 光标控制 」、「 数字输入 」、「 符号输入 」
14 |
15 | ## Usage
16 |
17 | - [快速入门](https://xianyukang.com/MyKeymap.html#mykeymap-%E7%AE%80%E4%BB%8B) & [视频介绍](https://www.bilibili.com/video/BV1Sf4y1c7p8)
18 | - [MyKeymap 2.0-beta33](https://wwqw.lanzouu.com/irujX2nesore) ( 提取码 1234 )
19 |
20 | |  |  |
21 | | ------------------------------- | ----------------------------------- |
22 |
23 | ## Screenshots
24 | 
25 |
--------------------------------------------------------------------------------
/tailwind/help_page.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: ['../config-ui/src/components/HelpPage.vue'],
3 | theme: {
4 | extend: {},
5 | },
6 | plugins: [],
7 | }
8 |
--------------------------------------------------------------------------------
/tailwind/help_page.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
--------------------------------------------------------------------------------
/tailwind/html-tools.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | content: [
3 | '../bin/html-tools/**/*.html',
4 | ],
5 | theme: {
6 | extend: {},
7 | },
8 | plugins: [],
9 | }
10 |
--------------------------------------------------------------------------------
/tailwind/index.css:
--------------------------------------------------------------------------------
1 | /* 因为关掉了 tailwind 的 preflight, 所以少了某些依赖样式, 这里补上 */
2 | .tailwind-scope * {
3 | border-width: 0;
4 | border-style: solid;
5 | }
6 |
7 | .tailwind-scope table {
8 | border-collapse: collapse;
9 | }
10 |
11 | @tailwind base;
12 | @tailwind components;
13 | @tailwind utilities;
14 |
--------------------------------------------------------------------------------
/tailwind/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dumb-package",
3 | "version": "1.0.0",
4 | "private": true,
5 | "main": "index.js",
6 | "scripts": {
7 | "build": "tailwind -i index.css -o ../config-ui/src/style/tailwind.css",
8 | "watch": "tailwind --watch -i index.css -o ../config-ui/src/style/tailwind.css",
9 | "build-help-page-css": "tailwind -c help_page.config.js -i help_page.css -o ../bin/site/css/HelpPage.css",
10 | "watch-help-page-css": "tailwind -c help_page.config.js -i help_page.css -o ../config-ui/src/style/HelpPage.css --watch",
11 | "build-html-tools-css": "tailwind -c html-tools.config.js -i help_page.css -o ../bin/site/css/tailwind.css",
12 | "watch-html-tools-css": "tailwind -c html-tools.config.js -i help_page.css -o ../bin/html-tools/tailwind.css --watch"
13 | },
14 | "devDependencies": {
15 | "tailwindcss": "^3.0.23"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/tailwind/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | important: '.tailwind-scope',
3 | corePlugins: {
4 | preflight: false,
5 | },
6 | content: ['../config-ui/src/**/*.vue'],
7 | theme: {
8 | extend: {},
9 | },
10 | plugins: [],
11 | }
12 |
--------------------------------------------------------------------------------
/tools/Rexplorer_x64.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xianyukang/MyKeymap/968394b4bfceace5c911dee0753254cfa1160b94/tools/Rexplorer_x64.exe
--------------------------------------------------------------------------------
/tools/工具来源.txt:
--------------------------------------------------------------------------------
1 | Rexplorer_x64 用于重启文件资源管理器
2 | https://www.sordum.org/9192/restart-explorer-v1-7/
--------------------------------------------------------------------------------
/变量方法差别.md:
--------------------------------------------------------------------------------
1 | 以下是配置中的变量名、方法名变量的变化
2 | 左边为V1 右边为V2
3 |
4 |
5 |
6 | ### 方法
7 | centerMouse => MouseToActiveWindowCenter
8 |
9 | close_window_processes =>CloseWindowProcesses
10 | setColor => changeTextStyle
11 | close_same_class_window => CloseSameClassWindows
12 | set_window_position_and_size => SetWindowPositionAndSize
13 | launch_multiple => LaunchMultiple
14 | activate_it_by_hotkey_or_run => ProcessExistSendKeyOrRun
15 | wrap_selected_text => WrapSelectedText
16 |
17 |
18 |
19 |
20 | ### 删除方法
--------------------------------------------------------------------------------
/误报病毒时执行这个.bat:
--------------------------------------------------------------------------------
1 | @bin\settings.exe UseOriginalAHK
2 | @pause
--------------------------------------------------------------------------------