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