1 // This file is part of Desktop App Toolkit,
2 // a set of libraries for developing nice desktop applications.
3 //
4 // For license and copyright information please follow this link:
5 // https://github.com/desktop-app/legal/blob/master/LEGAL
6 //
7 #include "base/global_shortcuts_generic.h"
8
9 #include "base/platform/base_platform_global_shortcuts.h"
10 #include "base/invoke_queued.h"
11
12 namespace base {
13 namespace {
14
15 constexpr auto kShortcutLimit = 4;
16
17 std::mutex GlobalMutex;
18 std::vector<not_null<GlobalShortcutManagerGeneric*>> Managers;
19
MakeShortcut(std::vector<GlobalShortcutKeyGeneric> descriptors)20 [[nodiscard]] GlobalShortcut MakeShortcut(
21 std::vector<GlobalShortcutKeyGeneric> descriptors) {
22 return std::make_shared<GlobalShortcutValueGeneric>(
23 std::move(descriptors));
24 }
25
Matches(const std::vector<GlobalShortcutKeyGeneric> & sorted,const flat_set<GlobalShortcutKeyGeneric> & down)26 [[nodiscard]] bool Matches(
27 const std::vector<GlobalShortcutKeyGeneric> &sorted,
28 const flat_set<GlobalShortcutKeyGeneric> &down) {
29 if (sorted.size() > down.size()) {
30 return false;
31 }
32 auto j = begin(down);
33 for (const auto descriptor : sorted) {
34 while (true) {
35 if (*j > descriptor) {
36 return false;
37 } else if (*j < descriptor) {
38 ++j;
39 if (j == end(down)) {
40 return false;
41 }
42 } else {
43 break;
44 }
45 }
46 }
47 return true;
48 }
49
ScheduleForAll(GlobalShortcutKeyGeneric descriptor,bool down)50 void ScheduleForAll(GlobalShortcutKeyGeneric descriptor, bool down) {
51 std::unique_lock lock{ GlobalMutex };
52 for (const auto manager : Managers) {
53 manager->schedule(descriptor, down);
54 }
55 }
56
57 } // namespace
58
CreateGlobalShortcutManager()59 std::unique_ptr<GlobalShortcutManager> CreateGlobalShortcutManager() {
60 return std::make_unique<GlobalShortcutManagerGeneric>();
61 }
62
GlobalShortcutsAvailable()63 bool GlobalShortcutsAvailable() {
64 return Platform::GlobalShortcuts::Available();
65 }
66
GlobalShortcutsAllowed()67 bool GlobalShortcutsAllowed() {
68 return Platform::GlobalShortcuts::Allowed();
69 }
70
GlobalShortcutValueGeneric(std::vector<GlobalShortcutKeyGeneric> descriptors)71 GlobalShortcutValueGeneric::GlobalShortcutValueGeneric(
72 std::vector<GlobalShortcutKeyGeneric> descriptors)
73 : _descriptors(std::move(descriptors)) {
74 Expects(!_descriptors.empty());
75 }
76
toDisplayString()77 QString GlobalShortcutValueGeneric::toDisplayString() {
78 auto result = QStringList();
79 result.reserve(_descriptors.size());
80 for (const auto descriptor : _descriptors) {
81 result.push_back(Platform::GlobalShortcuts::KeyName(descriptor));
82 }
83 return result.join(" + ");
84 }
85
serialize()86 QByteArray GlobalShortcutValueGeneric::serialize() {
87 static_assert(sizeof(GlobalShortcutKeyGeneric) == sizeof(uint64));
88
89 const auto size = sizeof(GlobalShortcutKeyGeneric) * _descriptors.size();
90 auto result = QByteArray(size, Qt::Uninitialized);
91 memcpy(result.data(), _descriptors.data(), size);
92 return result;
93 }
94
GlobalShortcutManagerGeneric()95 GlobalShortcutManagerGeneric::GlobalShortcutManagerGeneric() {
96 std::unique_lock lock{ GlobalMutex };
97 const auto start = Managers.empty();
98 Managers.push_back(this);
99 lock.unlock();
100
101 if (start) {
102 Platform::GlobalShortcuts::Start(ScheduleForAll);
103 }
104 }
105
~GlobalShortcutManagerGeneric()106 GlobalShortcutManagerGeneric::~GlobalShortcutManagerGeneric() {
107 std::unique_lock lock{ GlobalMutex };
108 Managers.erase(ranges::remove(Managers, not_null{ this }), end(Managers));
109 const auto stop = Managers.empty();
110 lock.unlock();
111
112 if (stop) {
113 Platform::GlobalShortcuts::Stop();
114 }
115 }
116
startRecording(Fn<void (GlobalShortcut)> progress,Fn<void (GlobalShortcut)> done)117 void GlobalShortcutManagerGeneric::startRecording(
118 Fn<void(GlobalShortcut)> progress,
119 Fn<void(GlobalShortcut)> done) {
120 Expects(done != nullptr);
121
122 _recordingDown.clear();
123 _recordingUp.clear();
124 _recording = true;
125 _recordingProgress = std::move(progress);
126 _recordingDone = std::move(done);
127 }
128
stopRecording()129 void GlobalShortcutManagerGeneric::stopRecording() {
130 _recordingDown.clear();
131 _recordingUp.clear();
132 _recording = false;
133 _recordingDone = nullptr;
134 _recordingProgress = nullptr;
135 }
136
startWatching(GlobalShortcut shortcut,Fn<void (bool pressed)> callback)137 void GlobalShortcutManagerGeneric::startWatching(
138 GlobalShortcut shortcut,
139 Fn<void(bool pressed)> callback) {
140 Expects(shortcut != nullptr);
141 Expects(callback != nullptr);
142
143 const auto i = ranges::find(_watchlist, shortcut, &Watch::shortcut);
144 if (i != end(_watchlist)) {
145 i->callback = std::move(callback);
146 } else {
147 auto sorted = static_cast<GlobalShortcutValueGeneric*>(
148 shortcut.get())->descriptors();
149 std::sort(begin(sorted), end(sorted));
150 _watchlist.push_back(Watch{
151 std::move(shortcut),
152 std::move(sorted),
153 std::move(callback)
154 });
155 }
156 }
157
stopWatching(GlobalShortcut shortcut)158 void GlobalShortcutManagerGeneric::stopWatching(GlobalShortcut shortcut) {
159 const auto i = ranges::find(_watchlist, shortcut, &Watch::shortcut);
160 if (i != end(_watchlist)) {
161 _watchlist.erase(i);
162 }
163 _pressed.erase(ranges::find(_pressed, shortcut), end(_pressed));
164 }
165
shortcutFromSerialized(QByteArray serialized)166 GlobalShortcut GlobalShortcutManagerGeneric::shortcutFromSerialized(
167 QByteArray serialized) {
168 const auto single = sizeof(GlobalShortcutKeyGeneric);
169 if (serialized.isEmpty() || serialized.size() % single) {
170 return nullptr;
171 }
172 auto count = serialized.size() / single;
173 auto list = std::vector<GlobalShortcutKeyGeneric>(count);
174 memcpy(list.data(), serialized.constData(), serialized.size());
175 return MakeShortcut(std::move(list));
176 }
177
schedule(GlobalShortcutKeyGeneric descriptor,bool down)178 void GlobalShortcutManagerGeneric::schedule(
179 GlobalShortcutKeyGeneric descriptor,
180 bool down) {
181 InvokeQueued(this, [=] { process(descriptor, down); });
182 }
183
process(GlobalShortcutKeyGeneric descriptor,bool down)184 void GlobalShortcutManagerGeneric::process(
185 GlobalShortcutKeyGeneric descriptor,
186 bool down) {
187 if (!down) {
188 _down.remove(descriptor);
189 }
190 if (_recording) {
191 processRecording(descriptor, down);
192 return;
193 }
194 auto scheduled = std::vector<Fn<void(bool pressed)>>();
195 if (down) {
196 _down.emplace(descriptor);
197 for (const auto &watch : _watchlist) {
198 if (watch.sorted.size() > _down.size()
199 || ranges::contains(_pressed, watch.shortcut)) {
200 continue;
201 } else if (Matches(watch.sorted, _down)) {
202 _pressed.push_back(watch.shortcut);
203 scheduled.push_back(watch.callback);
204 }
205 }
206 } else {
207 _down.remove(descriptor);
208 for (auto i = begin(_pressed); i != end(_pressed);) {
209 const auto generic = static_cast<GlobalShortcutValueGeneric*>(
210 i->get());
211 if (!ranges::contains(generic->descriptors(), descriptor)) {
212 ++i;
213 } else {
214 const auto j = ranges::find(
215 _watchlist,
216 *i,
217 &Watch::shortcut);
218 Assert(j != end(_watchlist));
219 scheduled.push_back(j->callback);
220
221 i = _pressed.erase(i);
222 }
223 }
224 }
225 for (const auto &callback : scheduled) {
226 callback(down);
227 }
228 }
229
processRecording(GlobalShortcutKeyGeneric descriptor,bool down)230 void GlobalShortcutManagerGeneric::processRecording(
231 GlobalShortcutKeyGeneric descriptor,
232 bool down) {
233 if (down) {
234 processRecordingPress(descriptor);
235 } else {
236 processRecordingRelease(descriptor);
237 }
238 }
239
processRecordingPress(GlobalShortcutKeyGeneric descriptor)240 void GlobalShortcutManagerGeneric::processRecordingPress(
241 GlobalShortcutKeyGeneric descriptor) {
242 auto changed = false;
243 _recordingUp.remove(descriptor);
244 for (const auto descriptor : _recordingUp) {
245 const auto i = ranges::remove(_recordingDown, descriptor);
246 if (i != end(_recordingDown)) {
247 _recordingDown.erase(i, end(_recordingDown));
248 changed = true;
249 }
250 }
251 _recordingUp.clear();
252
253 const auto i = std::find(
254 begin(_recordingDown),
255 end(_recordingDown),
256 descriptor);
257 if (i == _recordingDown.end()) {
258 _recordingDown.push_back(descriptor);
259 changed = true;
260 }
261 if (!changed) {
262 return;
263 } else if (_recordingDown.size() == kShortcutLimit) {
264 finishRecording();
265 } else if (const auto onstack = _recordingProgress) {
266 onstack(MakeShortcut(_recordingDown));
267 }
268 }
269
processRecordingRelease(GlobalShortcutKeyGeneric descriptor)270 void GlobalShortcutManagerGeneric::processRecordingRelease(
271 GlobalShortcutKeyGeneric descriptor) {
272 const auto i = std::find(
273 begin(_recordingDown),
274 end(_recordingDown),
275 descriptor);
276 if (i == end(_recordingDown)) {
277 return;
278 }
279 _recordingUp.emplace(descriptor);
280 Assert(_recordingUp.size() <= _recordingDown.size());
281 if (_recordingUp.size() == _recordingDown.size()) {
282 // All keys are up, we got the shortcut.
283 // Some down keys are not up yet.
284 finishRecording();
285 }
286 }
287
finishRecording()288 void GlobalShortcutManagerGeneric::finishRecording() {
289 Expects(!_recordingDown.empty());
290
291 auto result = MakeShortcut(std::move(_recordingDown));
292 _recordingDown.clear();
293 _recordingUp.clear();
294 _recording = false;
295 const auto done = _recordingDone;
296 _recordingDone = nullptr;
297 _recordingProgress = nullptr;
298
299 done(std::move(result));
300 }
301
302 } // namespace base
303