Mac Quick Send:全局热键 + 菜单栏(Phase 4c)
- MacQuickSend(@Observable 单例):激活 app + NSOpenPanel 多选文件 → 置 pending;MacRootView 观察 pending → 切到「传输」区 + 弹 MacComposeSheet 选目标设备发送(复用现有发送流程与 engine.sendFile) - MacHotkey:Carbon RegisterEventHotKey 全局热键 ⌃⌥⌘S(无须辅助功能权限)触发 Quick Send。事件处理器须 C 函数指针,用不捕获闭包(只调静态 MacQuickSend.trigger)+ dispatch 主队列再弹模态(避免 Carbon 回调内跑模态)。登录(MacRootView onAppear)注册、登出(onDisappear)注销 - 菜单栏加「快速发送文件…」(登录态) - 桌面 Wails 实际未实现全局热键(仅曾计划),此为原生 Mac 增益。键位暂固定 ⌃⌥⌘S,后续可做成设置项 - 验证:just mac-build(CommilitiaDropMac.app)BUILD SUCCEEDED;iOS 无关(仅改 macOS-only 文件),不受影响
This commit is contained in:
@@ -0,0 +1,38 @@
|
|||||||
|
#if os(macOS)
|
||||||
|
import AppKit
|
||||||
|
import Carbon.HIToolbox
|
||||||
|
|
||||||
|
// 全局热键(Carbon RegisterEventHotKey,无须辅助功能权限):⌃⌥⌘S 触发 Quick Send。登录时注册、登出注销。
|
||||||
|
// 键位暂固定,后续可做成设置项。RegisterEventHotKey 的事件处理器须 C 函数指针,故用不捕获闭包(只调
|
||||||
|
// 静态 MacQuickSend.trigger),并 dispatch 到主队列再弹 NSOpenPanel(避免在 Carbon 事件回调内跑模态)。
|
||||||
|
final class MacHotkey
|
||||||
|
{
|
||||||
|
static let shared = MacHotkey()
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
private var hotKeyRef: EventHotKeyRef?
|
||||||
|
private var handlerRef: EventHandlerRef?
|
||||||
|
|
||||||
|
func register()
|
||||||
|
{
|
||||||
|
guard hotKeyRef == nil else { return }
|
||||||
|
var spec = EventTypeSpec(eventClass: OSType(kEventClassKeyboard),
|
||||||
|
eventKind: OSType(kEventHotKeyPressed))
|
||||||
|
InstallEventHandler(GetApplicationEventTarget(),
|
||||||
|
{ _, _, _ in DispatchQueue.main.async { MacQuickSend.trigger() }; return noErr },
|
||||||
|
1, &spec, nil, &handlerRef)
|
||||||
|
var hotID = EventHotKeyID(signature: OSType(0x4344_5248), id: 1) // 'CDRH'
|
||||||
|
RegisterEventHotKey(UInt32(kVK_ANSI_S),
|
||||||
|
UInt32(controlKey | optionKey | cmdKey),
|
||||||
|
hotID, GetApplicationEventTarget(), 0, &hotKeyRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unregister()
|
||||||
|
{
|
||||||
|
if let hotKeyRef { UnregisterEventHotKey(hotKeyRef) }
|
||||||
|
hotKeyRef = nil
|
||||||
|
if let handlerRef { RemoveEventHandler(handlerRef) }
|
||||||
|
handlerRef = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -34,6 +34,7 @@ struct MacMenuContent: View
|
|||||||
if auth.session != nil
|
if auth.session != nil
|
||||||
{
|
{
|
||||||
Divider()
|
Divider()
|
||||||
|
Button("快速发送文件…") { MacQuickSend.trigger() }
|
||||||
Button("上传本机剪贴板") { engine.uploadClipboard() }
|
Button("上传本机剪贴板") { engine.uploadClipboard() }
|
||||||
Button("拉取云剪贴板") { engine.pullClipboard() }
|
Button("拉取云剪贴板") { engine.pullClipboard() }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#if os(macOS)
|
||||||
|
import AppKit
|
||||||
|
import Observation
|
||||||
|
|
||||||
|
// Quick Send:全局热键(MacHotkey)或菜单栏触发 → 激活 app + NSOpenPanel 选文件 → 主窗口切到「传输」区
|
||||||
|
// 并弹发送表单选目标设备发送。pending 为待发文件,MacRootView 观察它、présente MacComposeSheet。
|
||||||
|
@Observable
|
||||||
|
final class MacQuickSend
|
||||||
|
{
|
||||||
|
static let shared = MacQuickSend()
|
||||||
|
private init() {}
|
||||||
|
|
||||||
|
var pending: [URL] = []
|
||||||
|
|
||||||
|
// 激活 app + 选文件(主线程同步)。有选中即置 pending,触发主窗口的发送表单。未登录时 MacRootView
|
||||||
|
// 未挂载、表单不出现——Quick Send 仅登录态有意义(菜单项 / 热键均在登录后才提供 / 注册)。
|
||||||
|
static func trigger()
|
||||||
|
{
|
||||||
|
NSApp.activate()
|
||||||
|
let panel = NSOpenPanel()
|
||||||
|
panel.canChooseFiles = true
|
||||||
|
panel.canChooseDirectories = false
|
||||||
|
panel.allowsMultipleSelection = true
|
||||||
|
panel.prompt = "发送"
|
||||||
|
if panel.runModal() == .OK, !panel.urls.isEmpty
|
||||||
|
{
|
||||||
|
shared.pending = panel.urls
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -11,6 +11,7 @@ struct MacRootView: View
|
|||||||
@Environment(EngineController.self) private var engine
|
@Environment(EngineController.self) private var engine
|
||||||
@Environment(AuthManager.self) private var auth
|
@Environment(AuthManager.self) private var auth
|
||||||
@AppStorage("macClipboardAutoSync") private var autoSync = true
|
@AppStorage("macClipboardAutoSync") private var autoSync = true
|
||||||
|
@State private var quickSend = MacQuickSend.shared
|
||||||
@State private var selection: MacSection = .transfers
|
@State private var selection: MacSection = .transfers
|
||||||
|
|
||||||
var body: some View
|
var body: some View
|
||||||
@@ -57,9 +58,27 @@ struct MacRootView: View
|
|||||||
NoticeOverlay()
|
NoticeOverlay()
|
||||||
}
|
}
|
||||||
// 剪贴板自动同步(上行):登录进壳即按开关启停;开关变化即时生效;登出(本视图消失)停。
|
// 剪贴板自动同步(上行):登录进壳即按开关启停;开关变化即时生效;登出(本视图消失)停。
|
||||||
.onAppear { syncClipboardMonitor() }
|
// 同时注册 / 注销 Quick Send 全局热键(⌃⌥⌘S)。
|
||||||
|
.onAppear { syncClipboardMonitor(); MacHotkey.shared.register() }
|
||||||
.onChange(of: autoSync) { syncClipboardMonitor() }
|
.onChange(of: autoSync) { syncClipboardMonitor() }
|
||||||
.onDisappear { MacClipboardMonitor.shared.stop() }
|
.onDisappear { MacClipboardMonitor.shared.stop(); MacHotkey.shared.unregister() }
|
||||||
|
// Quick Send(热键 / 菜单栏触发):有待发文件即切到「传输」并弹发送表单。
|
||||||
|
.onChange(of: quickSend.pending)
|
||||||
|
{ _, urls in
|
||||||
|
if !urls.isEmpty { selection = .transfers }
|
||||||
|
}
|
||||||
|
.sheet(isPresented: Binding(get: { !quickSend.pending.isEmpty },
|
||||||
|
set: { if !$0 { quickSend.pending = [] } }))
|
||||||
|
{
|
||||||
|
MacComposeSheet(urls: quickSend.pending,
|
||||||
|
onCancel: { quickSend.pending = [] },
|
||||||
|
onSend:
|
||||||
|
{ device in
|
||||||
|
for url in quickSend.pending { engine.sendFile(to: device, fileURL: url) }
|
||||||
|
quickSend.pending = []
|
||||||
|
})
|
||||||
|
.environment(engine)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func syncClipboardMonitor()
|
private func syncClipboardMonitor()
|
||||||
|
|||||||
Reference in New Issue
Block a user