@@ -29,11 +29,19 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
29
29
@AppStorage ( SettingKeys . StickyNote ( ) . keyMarkdownAction) private var isEnableMarkdown : Bool = SettingKeys . StickyNote ( ) . initialMarkdownAction
30
30
@AppStorage ( SettingKeys . StickyNote ( ) . keyShowMarkdownPreview) private var showMarkdownPreview : Bool = SettingKeys . StickyNote ( ) . initialShowMarkdownPreview
31
31
32
+ @AppStorage ( SettingKeys . StickyNote. KeyboardShortcuts ( ) . keyKeyboardShortcutAction) private var keyboardShortcutPattern : Int = SettingKeys . StickyNote. KeyboardShortcuts. KeyboardPattern ( ) . none
33
+
32
34
private var statusItem : NSStatusItem ?
33
35
private var popover : NSPopover = NSPopover ( )
34
36
35
37
private var isOpenNote : Bool = true
36
38
39
+ // For keyboard shortcut
40
+ private weak var statusBarButton : NSStatusBarButton ?
41
+ private var monitor : Any ?
42
+ private var noteOpenCounter : Int = SettingKeys . StickyNote. KeyboardShortcuts ( ) . initialNoteOpenCounter
43
+ private var counterResetTask : Task < Void , Never > ?
44
+
37
45
func applicationDidFinishLaunching( _ notification: Notification ) {
38
46
NSApp . setActivationPolicy ( . accessory)
39
47
@@ -48,6 +56,8 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
48
56
button. image = NSImage ( named: " MenubarIcon " )
49
57
button. action = #selector( showPopover)
50
58
button. sendAction ( on: [ . leftMouseUp, . rightMouseUp] )
59
+
60
+ statusBarButton = button
51
61
}
52
62
53
63
popover. contentViewController = NSHostingController ( rootView: ContentView ( delegate: self ) )
@@ -57,66 +67,128 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
57
67
} else {
58
68
disablePinning ( )
59
69
}
70
+
71
+ if keyboardShortcutPattern != SettingKeys . StickyNote. KeyboardShortcuts. KeyboardPattern ( ) . none {
72
+ // Check accessibility permission
73
+ let options : [ String : Bool ] = [ kAXTrustedCheckOptionPrompt. takeUnretainedValue ( ) as String : true ]
74
+ let isTrusted = AXIsProcessTrustedWithOptions ( options as CFDictionary )
75
+
76
+ if isTrusted {
77
+ monitor = NSEvent . addGlobalMonitorForEvents ( matching: [ . flagsChanged] ) { event in
78
+ self . handleKeyEvent ( event)
79
+ }
80
+ }
81
+ }
60
82
}
61
83
62
- @objc func showPopover( sender: NSStatusBarButton ) {
63
- guard let currentEvent = NSApp . currentEvent else {
64
- return
65
- }
66
-
67
- guard let unwrappedStatusItem = statusItem else {
68
- return
84
+ func applicationWillTerminate( _ aNotification: Notification ) {
85
+ // Remove a handle key event.
86
+ if let monitor = monitor {
87
+ NSEvent . removeMonitor ( monitor)
69
88
}
70
-
71
- if currentEvent. type == NSEvent . EventType. rightMouseUp {
89
+ }
72
90
73
- let menu = NSMenu ( )
74
-
75
- menu. addItem (
76
- withTitle: NSLocalizedString ( " menubar.clickevent.right.menu.item.quit " , comment: " " ) ,
77
- action: #selector( quitApp) ,
78
- keyEquivalent: " "
79
- )
80
-
81
- unwrappedStatusItem. menu = menu
82
- unwrappedStatusItem. button? . performClick ( nil )
83
- unwrappedStatusItem. menu = nil
84
-
85
- } else if currentEvent. type == NSEvent . EventType. leftMouseUp {
91
+ private func handleKeyEvent( _ event: NSEvent ) {
92
+
93
+ /**
94
+ * ## How do keyboard shortcuts for opening the note work?
95
+ *
96
+ * When the user types the keyboard shortcut key three times within a very short time, the note will open.
97
+ * Each time the user types the shortcut key, the note open counter is decremented.
98
+ * If any key other than the shortcut key is pressed, or if the user does not press the shortcut key consecutively within a very short time,
99
+ * the note open counter is reset to the initial value.
100
+ *
101
+ * Additionally, since func handleKeyEvent is called both when a key is pressed and when it is released,
102
+ * the initial value of the note open counter must be set to twice the number of times you want the user to press the shortcut key.
103
+ *
104
+ */
105
+ if ( event. keyCode == keyboardShortcutPattern) {
106
+ noteOpenCounter -= 1 ;
107
+
108
+ if noteOpenCounter <= 0
109
+ {
110
+ if let button = statusBarButton {
111
+ showPopover ( sender: button, isGlobalHotKey: true )
112
+ }
113
+ }
114
+
115
+ counterResetTask? . cancel ( )
86
116
87
- if isPinNote {
88
-
89
- isOpenNote. toggle ( )
90
-
91
- if isOpenNote {
117
+ counterResetTask = Task {
118
+ do {
119
+ try Task . checkCancellation ( )
120
+ try await Task . sleep ( nanoseconds: 3 * 100_000_000 )
121
+ noteOpenCounter = SettingKeys . StickyNote. KeyboardShortcuts ( ) . initialNoteOpenCounter
122
+
123
+ } catch {
124
+ if Task . isCancelled {
125
+ print ( " Task is Canceled " )
126
+ } else {
127
+ print ( " Unexcepted error " )
128
+ }
129
+ }
130
+ }
131
+ }
132
+ else
133
+ {
134
+ noteOpenCounter = SettingKeys . StickyNote. KeyboardShortcuts ( ) . initialNoteOpenCounter
135
+ }
136
+ }
137
+
138
+ @objc func showPopover( sender: NSStatusBarButton , isGlobalHotKey: Bool = false ) {
139
+
140
+ if let currentEvent = NSApp . currentEvent, let unwrappedStatusItem = statusItem {
141
+
142
+ if currentEvent. type == NSEvent . EventType. rightMouseUp {
143
+
144
+ let menu = NSMenu ( )
145
+
146
+ menu. addItem (
147
+ withTitle: NSLocalizedString ( " menubar.clickevent.right.menu.item.quit " , comment: " " ) ,
148
+ action: #selector( quitApp) ,
149
+ keyEquivalent: " "
150
+ )
151
+
152
+ unwrappedStatusItem. menu = menu
153
+ unwrappedStatusItem. button? . performClick ( nil )
154
+ unwrappedStatusItem. menu = nil
155
+
156
+ } else if currentEvent. type == NSEvent . EventType. leftMouseUp || isGlobalHotKey == true {
157
+
158
+ if isPinNote {
159
+
160
+ isOpenNote. toggle ( )
161
+
162
+ if isOpenNote {
163
+ popover. show ( relativeTo: sender. bounds, of: sender, preferredEdge: NSRectEdge . maxY)
164
+
165
+ // When the user enables "Markdown preview (turn on 'isEnableMarkdown')",
166
+ // "showMarkdownPreview" will be TRUE every time the user opens a note to the Markdown preview.
167
+ if isEnableMarkdown {
168
+ showMarkdownPreview = true
169
+ }
170
+
171
+ } else {
172
+ popover. close ( )
173
+ }
174
+ } else {
92
175
popover. show ( relativeTo: sender. bounds, of: sender, preferredEdge: NSRectEdge . maxY)
93
176
177
+ // Initialize "isOpenNote" when "pin a note" is enable next time.
178
+ isOpenNote = false
179
+
94
180
// When the user enables "Markdown preview (turn on 'isEnableMarkdown')",
95
181
// "showMarkdownPreview" will be TRUE every time the user opens a note to the Markdown preview.
96
182
if isEnableMarkdown {
97
183
showMarkdownPreview = true
98
184
}
99
-
100
- } else {
101
- popover. close ( )
102
- }
103
- } else {
104
- popover. show ( relativeTo: sender. bounds, of: sender, preferredEdge: NSRectEdge . maxY)
105
-
106
- // Initialize "isOpenNote" when "pin a note" is enable next time.
107
- isOpenNote = false
108
-
109
- // When the user enables "Markdown preview (turn on 'isEnableMarkdown')",
110
- // "showMarkdownPreview" will be TRUE every time the user opens a note to the Markdown preview.
111
- if isEnableMarkdown {
112
- showMarkdownPreview = true
113
185
}
114
- }
115
186
116
- popover. contentViewController? . view. window? . makeKey ( )
117
-
118
- if #available( macOS 14 , * ) {
119
- NSApp . activate ( ignoringOtherApps: true )
187
+ popover. contentViewController? . view. window? . makeKey ( )
188
+
189
+ if #available( macOS 14 , * ) {
190
+ NSApp . activate ( ignoringOtherApps: true )
191
+ }
120
192
}
121
193
}
122
194
}
0 commit comments