-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinit.lua
More file actions
executable file
·675 lines (585 loc) · 18.5 KB
/
Copy pathinit.lua
File metadata and controls
executable file
·675 lines (585 loc) · 18.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
---@diagnostic disable: undefined-global
hs.alert.show("Hammerspoon Config loaded")
--------------------------------------------------------------------------------
-- 配置变量
--------------------------------------------------------------------------------
-- 输入法枚举
InputMethodEnum = {
-- Mac自带的,无法删除的输入法,以键盘布局存在
english = {
id = "com.apple.keylayout.ABC",
name = "ABC"
},
pinyin = {
id = "com.apple.inputmethod.SCIM.ITABC",
name = "拼音"
},
shuangpin = {
id = "com.apple.inputmethod.SCIM.Shuangpin",
name = "双拼"
},
sougou = {
id = "com.sogou.inputmethod.sogou.pinyin",
name = "搜狗"
},
wubi = {
id = "com.apple.inputmethod.SCIM.WBX",
name = "五笔"
},
fantipinyin = {
id = "com.apple.inputmethod.TCIM.Pinyin",
name = "繁体拼音"
},
fantizhuyin = {
id = "com.apple.keylayout.ZhuyinBopomofo",
name = "繁体注音"
},
}
--------------------------------------------------------------------------------
-- 工具方法
--------------------------------------------------------------------------------
-- 触发指定的键
local function sendKey(modifier, key)
hs.eventtap.keyStroke(modifier, key, 0)
end
-- 使用系统命令复制文件
local function copyFile(source, target)
return os.execute(string.format("/bin/cp '%s' '%s'", source, target))
end
-- 使用系统命令创建目录
local function makeDir(path)
return os.execute(string.format("/bin/mkdir -p '%s'", path))
end
-- 使用系统命令删除文件
local function removeFile(path)
return os.execute(string.format("/bin/rm '%s'", path))
end
-- 读取目录下的所有文件
local function readDirFiles(dir)
local files = {}
for file in hs.fs.dir(dir) do
if file ~= "." and file ~= ".." then
local path = dir .. "/" .. file
local attr = hs.fs.attributes(path)
if attr then
table.insert(files, { path = path, time = attr.modification })
end
end
end
return files
end
-- 获取所有输入法
local function getAllInputSources()
local sources = {}
-- 获取所有键盘布局
local layouts = hs.keycodes.layouts(true)
for _, sourceID in pairs(layouts) do
table.insert(sources, sourceID)
end
-- 获取所有输入法
local methods = hs.keycodes.methods(true)
for _, sourceID in pairs(methods) do
table.insert(sources, sourceID)
end
return sources
end
-- 循环切换下一个输入法
local function switchNextInputMethod()
local sources = getAllInputSources()
if #sources == 0 then
return
end
local currentID = hs.keycodes.currentSourceID()
-- 获取当前输入法的索引
local currentIndex = 1
for i, id in ipairs(sources) do
if id == currentID then
currentIndex = i
break
end
end
-- 获取下一个输入法的索引
local nextIndex = currentIndex % #sources + 1
hs.keycodes.currentSourceID(sources[nextIndex])
end
--------------------------------------------------------------------------------
-- 功能实现
--------------------------------------------------------------------------------
----------------------------------------
-- CapsLock 按下期间是否有其他键按下
----------------------------------------
CapsLockOtherKeyPressed = false
-- 将 CapsLock 映射到 F13
local function remapCapsLockToF13()
-- 创建 F13 模态环境
local f13Modal = hs.hotkey.modal.new()
-- 将 CapsLock (0x700000039) 重映射到 F13 (0x700000068)
local command =
"hidutil property --set '{\"UserKeyMapping\":[{\"HIDKeyboardModifierMappingSrc\": 0x700000039, \"HIDKeyboardModifierMappingDst\": 0x700000068}]}'"
local success = os.execute(command)
if success then
hs.alert.show("CapsLock 映射为 F13")
else
hs.alert.show("CapsLock 映射为 F13 失败")
return
end
local f13HotkeyBinding = hs.hotkey.bind({}, 'f13', function()
-- 开始重置状态
CapsLockOtherKeyPressed = false
-- 进入模态
f13Modal:enter()
end, function()
-- 如果 CapsLock 是单独按下则发送Esc
if not CapsLockOtherKeyPressed then
sendKey({}, "escape")
end
-- 退出模态
f13Modal:exit()
-- 结束重置状态
CapsLockOtherKeyPressed = false
end)
return f13Modal, f13HotkeyBinding
end
----------------------------------------
-- 绑定 F13 按键,支持按键重复
----------------------------------------
local function bindF13Key(f13Modal, key, fn, shouldRepeat)
if shouldRepeat then
f13Modal:bind({}, key,
-- pressedfn (按下时触发)
function()
CapsLockOtherKeyPressed = true
fn()
end,
-- releasedfn (松开时触发)
function() end,
-- repeatfn (重复时触发)
function() fn() end
)
else
f13Modal:bind({}, key,
-- pressedfn (按下时触发)
function()
CapsLockOtherKeyPressed = true
fn()
end,
-- releasedfn (松开时触发)
function() end,
-- repeatfn (重复时触发)
function() end
)
end
end
----------------------------------------
-- 撤销 CapsLock 映射到 F13
----------------------------------------
local function removeCapsLockToF13(f13Modal, f13HotkeyBinding)
-- 删除 F13 热键绑定
if f13HotkeyBinding then
f13HotkeyBinding:delete()
f13HotkeyBinding = nil
end
-- 退出模态
if f13Modal then
f13Modal:exit()
f13Modal = nil
end
-- 清除映射
local command = "hidutil property --set '{\"UserKeyMapping\":[]}'"
local success = os.execute(command)
if success then
hs.alert.show("撤销 CapsLock 映射为 F13")
else
hs.alert.show("撤销 CapsLock 映射为 F13 失败")
end
end
----------------------------------------
-- 改变窗口布局
----------------------------------------
local function changeWindowLayout(mode)
local win = hs.window.focusedWindow()
if win == nil then
return
end
local id = win:id()
if id == nil then
return
end
if win:isFullScreen() and mode ~= "fullScreen" then
win:setFullScreen(false)
end
if mode == "fullScreen" then
win:setFullScreen(true)
elseif mode == "maximize" then
win:maximize()
elseif mode == "left" then
win:moveToUnit({ 0, 0, 0.5, 1 })
elseif mode == "right" then
win:moveToUnit({ 0.5, 0, 0.5, 1 })
end
end
----------------------------------------
-- 移动窗口到下一个屏幕
----------------------------------------
local function moveWindowNextScreen()
local allScreens = hs.screen.allScreens()
if #allScreens == 0 then
return
end
local win = hs.window.focusedWindow()
if win == nil then
return
end
win:moveToScreen(win:screen():next())
end
----------------------------------------
-- 上一个标签页
----------------------------------------
local function prevTab()
sendKey({ "cmd", "shift" }, "[")
end
----------------------------------------
-- 下一个标签页
----------------------------------------
local function nextTab()
sendKey({ "cmd", "shift" }, "]")
end
----------------------------------------
-- 开关音量
----------------------------------------
local function toggleAudio()
local outputDevice = hs.audiodevice.defaultOutputDevice()
if outputDevice == nil then
return
end
local outputState = outputDevice:outputMuted()
if outputState == nil then
return
end
outputState = not outputState
outputDevice:setOutputMuted(outputState)
if outputState then
hs.alert.show("静音")
else
local outputVolume = outputDevice:outputVolume()
if outputVolume == nil then
return
end
outputVolume = string.format("%.2f", outputVolume .. "")
hs.alert.show("开启声音:" .. outputVolume .. "%")
end
end
----------------------------------------
-- 防止系统休眠
----------------------------------------
PreventSleepState = false
PreventSleepMenuBar = nil
local function togglePreventSystemSleep()
return function()
PreventSleepState = not PreventSleepState
if PreventSleepState then
hs.caffeinate.set("displayIdle", true, true)
if not PreventSleepMenuBar then
PreventSleepMenuBar = hs.menubar.new()
end
PreventSleepMenuBar:setTitle("⏳")
PreventSleepMenuBar:setTooltip("防休眠已开启")
hs.alert("防休眠已开启 ⏳")
else
hs.caffeinate.set("displayIdle", false, false)
if PreventSleepMenuBar then
PreventSleepMenuBar:delete()
PreventSleepMenuBar = nil
end
hs.alert("防休眠已关闭 💤")
end
end
end
----------------------------------------
-- 启动App
----------------------------------------
local function launchApp(app)
hs.application.launchOrFocus(app)
end
----------------------------------------
-- 剪切板
----------------------------------------
ClipboardLite = nil
local function toggleClipboard(f13Modal, hotkey)
ClipboardLite = hs.loadSpoon("ClipboardLite")
ClipboardLite:start()
-- 触发剪贴板界面显示
bindF13Key(f13Modal, hotkey, function() ClipboardLite:toggleClipboard() end)
end
--ClipboardTool = nil
---- 剪切板
--local function toggleClipboard(f13Modal, hotkey)
-- ClipboardTool = hs.loadSpoon("ClipboardTool")
-- -- 显示最大长度
-- ClipboardTool.display_max_length = 200
-- -- 历史大小
-- ClipboardTool.hist_size = 30
-- -- 选择时粘贴
-- ClipboardTool.paste_on_select = true
-- -- 显示复制警报
-- ClipboardTool.show_copied_alert = false
-- -- 在菜单中显示
-- ClipboardTool.show_in_menubar = false
--
-- ClipboardTool:start()
--
-- -- 触发剪贴板界面显示
-- bindF13Key(f13Modal, hotkey, function() ClipboardTool:toggleClipboard() end)
--end
----------------------------------------
-- 输入法切换弹窗
----------------------------------------
-- 上次使用的输入法id
LastInputSourceId = nil
local function inputMethodSwitchAlert()
hs.keycodes.inputSourceChanged(function()
local currentSourceID = hs.keycodes.currentSourceID()
if currentSourceID == LastInputSourceId then
return
end
local found = false
for _, method in pairs(InputMethodEnum) do
if method.id == currentSourceID then
-- atScreenEdge 0居中 1顶部 2底部
hs.alert.show(method.name, { atScreenEdge = 2 }, 0.6)
found = true
break
end
end
if not found then
hs.alert.show("未知的输入法:" .. currentSourceID)
end
LastInputSourceId = currentSourceID
end)
end
----------------------------------------
-- 聚焦 App 时切换指定输入法
----------------------------------------
SwitchInputMethodWatcher = nil
local function focusAppSwitchInputMethod(appMapping)
SwitchInputMethodWatcher = hs.application.watcher.new(function(_, event, app)
if app == nil then
return
end
if (event ~= hs.application.watcher.activated and event ~= hs.application.watcher.launched) then
return
end
local inputMethod = appMapping[app:path()]
if inputMethod == nil then
return
end
hs.keycodes.currentSourceID(inputMethod.id)
end)
SwitchInputMethodWatcher:start()
end
----------------------------------------
-- 单击 Shfit 切换输入法
----------------------------------------
ShiftDownEvent = nil
KeyDownEvent = nil
-- Shfit 状态机:"idle" | "pending" | "other"
ShiftDownFlag = "idle"
local function clickShiftSwitchInputMethod()
-- 监听修饰键更改事件
ShiftDownEvent = hs.eventtap.new({ hs.eventtap.event.types.flagsChanged }, function(event)
local keyCode = event:getProperty(hs.eventtap.event.properties.keyboardEventKeycode)
-- 非 Shift,忽略
if keyCode ~= 56 and keyCode ~= 60 then
return false
end
local shiftPressed = event:getFlags().shift
if shiftPressed then
-- Shift 按下
ShiftDownFlag = "pending"
else
-- Shift 松开
if ShiftDownFlag == "pending" then
-- 切换输入法
switchNextInputMethod()
end
-- 松开后回到 idle
ShiftDownFlag = "idle"
end
-- 不阻止默认的事件
return false
end)
ShiftDownEvent:start()
-- 监听修饰键以外的按键按下事件
KeyDownEvent = hs.eventtap.new({ hs.eventtap.event.types.keyDown }, function(event)
if ShiftDownFlag == "pending" then
-- 不触发切换
ShiftDownFlag = "other"
end
-- 不阻止默认的事件
return false
end)
KeyDownEvent:start()
end
----------------------------------------
-- 鼠标线性反向滚动
----------------------------------------
MouseLinearReverseScrollEvent = nil
local function mouseLinearReverseScroll()
MouseLinearReverseScrollEvent = hs.eventtap.new({ hs.eventtap.event.types.scrollWheel }, function(event)
-- 触控板不进行更改
local isTrackpad = event:getProperty(hs.eventtap.event.properties.scrollWheelEventIsContinuous)
if isTrackpad == 1 then
return false
end
-- 按下 option 键则变速
local speedModifier = 1
local flags = event:getFlags()
if flags:contain({ "alt" }) then
speedModifier = 8
local newFlags = {}
for k, v in pairs(flags) do
if k ~= 'alt' then
newFlags[k] = v
end
end
event:setFlags(newFlags)
end
-- scrollWheelEventDeltaAxis1 的值应该是行,不是像素
local delta = event:getProperty(hs.eventtap.event.properties.scrollWheelEventDeltaAxis1)
-- 线性加变为每次滚动3行
event:setProperty(hs.eventtap.event.properties.scrollWheelEventDeltaAxis1,
delta > 0 and -3 * speedModifier or 3 * speedModifier)
-- 不阻止默认的事件
return false
end)
MouseLinearReverseScrollEvent:start()
end
----------------------------------------
-- 点击鼠标侧键切换桌面
----------------------------------------
ClickMouseSideEvent = nil
local function clickMouseSideButtonSwitchDesktop()
ClickMouseSideEvent = hs.eventtap.new({ hs.eventtap.event.types.otherMouseDown }, function(event)
local buttonNumber = event:getProperty(hs.eventtap.event.properties.mouseEventButtonNumber)
if buttonNumber == 3 then
sendKey({ "ctrl", "fn" }, "right")
return true
elseif buttonNumber == 4 then
sendKey({ "ctrl", "fn" }, "left")
return true
end
-- 不阻止默认的事件
return false
end)
ClickMouseSideEvent:start()
end
----------------------------------------
-- 同步文件到 iCloud
----------------------------------------
SyncFilesTimers = {}
local function syncFileToICloud(fileMapping)
local function createSyncTask(config)
return function()
-- 检查源文件
local sourceAttr = hs.fs.attributes(config.sourceFile)
if not sourceAttr then
print("[文件同步] 源文件不存在: " .. config.sourceFile)
return
end
-- 解析路径
local fileName = string.match(config.sourceFile, "([^/]+)$")
local baseName = string.match(fileName, "(.+)%.")
local ext = string.match(fileName, "%.([^%.]+)$")
-- 创建并复制到备份目录
local backupDir = config.targetDir .. "/" .. baseName
makeDir(backupDir)
local newTarget = string.format("%s/%s_%s.%s", backupDir, baseName, os.date("%y%m%d%H%M%S"), ext)
if copyFile(config.sourceFile, newTarget) then
print("[文件同步] 成功: " .. newTarget)
-- 清理旧备份
if config.maxBackupNumber then
local files = readDirFiles(backupDir)
table.sort(files, function(a, b) return a.time > b.time end)
for i = config.maxBackupNumber + 1, #files do
removeFile(files[i].path)
end
end
else
print("[文件同步] 失败: " .. config.sourceFile)
end
end
end
-- 清理旧的定时器
for _, timer in pairs(SyncFilesTimers) do
timer:stop()
end
SyncFilesTimers = {}
-- 为每个配置创建定时器
for _, config in ipairs(fileMapping) do
if config.backupIntervalSeconds then
local timer = hs.timer.doEvery(config.backupIntervalSeconds, createSyncTask(config))
timer:start()
table.insert(SyncFilesTimers, timer)
-- 立即执行一次
createSyncTask(config)()
end
end
end
--------------------------------------------------------------------------------
-- 功能调用
--------------------------------------------------------------------------------
-- 将 CapsLock 键映射为 F13 键
F13Modal, F13HotkeyBinding = remapCapsLockToF13()
-- removeCapsLockToF13(F13Modal, F13HotkeyBinding)
-- 改变窗口布局
bindF13Key(F13Modal, "up", function() changeWindowLayout("maximize") end)
bindF13Key(F13Modal, "down", function() changeWindowLayout("fullScreen") end)
bindF13Key(F13Modal, "left", function() changeWindowLayout("left") end)
bindF13Key(F13Modal, "right", function() changeWindowLayout("right") end)
-- 移动窗口到下一个屏幕
bindF13Key(F13Modal, "u", moveWindowNextScreen)
-- 切换标签页
bindF13Key(F13Modal, "i", prevTab)
bindF13Key(F13Modal, "o", nextTab)
-- 开关音量
bindF13Key(F13Modal, "g", toggleAudio)
-- 防止系统休眠
bindF13Key(F13Modal, "p", togglePreventSystemSleep())
-- 快捷启动应用
bindF13Key(F13Modal, "r", function() launchApp("Terminal") end)
bindF13Key(F13Modal, "f", function() launchApp("Finder") end)
bindF13Key(F13Modal, "space", function() launchApp("Launchpad") end)
bindF13Key(F13Modal, "w", function() launchApp("/Applications/WeChat.app") end)
bindF13Key(F13Modal, "s", function() launchApp("/Applications/Visual Studio Code.app") end)
bindF13Key(F13Modal, "d", function() launchApp("/Applications/网易有道翻译.app") end)
bindF13Key(F13Modal, "c", function() launchApp("/Applications/Google Chrome.app") end)
-- 剪切板
toggleClipboard(F13Modal, "v")
-- 实现支持重复的 Vim 风格方向键
bindF13Key(F13Modal, "h", function() sendKey({}, "left") end, true)
bindF13Key(F13Modal, "j", function() sendKey({}, "down") end, true)
bindF13Key(F13Modal, "k", function() sendKey({}, "up") end, true)
bindF13Key(F13Modal, "l", function() sendKey({}, "right") end, true)
-- 输入法切换弹窗
inputMethodSwitchAlert()
-- 聚焦 App 时切换指定输入法
focusAppSwitchInputMethod({
['/System/Applications/Utilities/Terminal.app'] = InputMethodEnum.english,
['/Applications/Termius.app'] = InputMethodEnum.english,
['/Applications/FinalShell.app'] = InputMethodEnum.english,
})
-- 单击 Shift 切换输入法
clickShiftSwitchInputMethod()
-- 鼠标线性反向滚动
mouseLinearReverseScroll()
-- 点击鼠标侧键切换桌面
clickMouseSideButtonSwitchDesktop()
-- 定时同步文件到 iCloud
-- syncFileToICloud({
-- {
-- sourceFile = "/Users/xxx/Desktop/temp.txt",
-- targetDir = os.getenv("HOME") .. "/Library/Mobile Documents/com~apple~CloudDocs",
-- maxBackupNumber = 5,
-- backupIntervalSeconds = 60 * 30
-- },
-- })