1 /******************************************************************************
2     Copyright (C) 2014-2015 by Ruwen Hahn <palana@stunned.de>
3 
4     This program is free software: you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation, either version 2 of the License, or
7     (at your option) any later version.
8 
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13 
14     You should have received a copy of the GNU General Public License
15     along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 ******************************************************************************/
17 
18 #include <inttypes.h>
19 
20 #include "obs-internal.h"
21 
lock(void)22 static inline bool lock(void)
23 {
24 	if (!obs)
25 		return false;
26 
27 	pthread_mutex_lock(&obs->hotkeys.mutex);
28 	return true;
29 }
30 
unlock(void)31 static inline void unlock(void)
32 {
33 	pthread_mutex_unlock(&obs->hotkeys.mutex);
34 }
35 
obs_hotkey_get_id(const obs_hotkey_t * key)36 obs_hotkey_id obs_hotkey_get_id(const obs_hotkey_t *key)
37 {
38 	return key->id;
39 }
40 
obs_hotkey_get_name(const obs_hotkey_t * key)41 const char *obs_hotkey_get_name(const obs_hotkey_t *key)
42 {
43 	return key->name;
44 }
45 
obs_hotkey_get_description(const obs_hotkey_t * key)46 const char *obs_hotkey_get_description(const obs_hotkey_t *key)
47 {
48 	return key->description;
49 }
50 
obs_hotkey_get_registerer_type(const obs_hotkey_t * key)51 obs_hotkey_registerer_t obs_hotkey_get_registerer_type(const obs_hotkey_t *key)
52 {
53 	return key->registerer_type;
54 }
55 
obs_hotkey_get_registerer(const obs_hotkey_t * key)56 void *obs_hotkey_get_registerer(const obs_hotkey_t *key)
57 {
58 	return key->registerer;
59 }
60 
obs_hotkey_get_pair_partner_id(const obs_hotkey_t * key)61 obs_hotkey_id obs_hotkey_get_pair_partner_id(const obs_hotkey_t *key)
62 {
63 	return key->pair_partner_id;
64 }
65 
66 obs_key_combination_t
obs_hotkey_binding_get_key_combination(obs_hotkey_binding_t * binding)67 obs_hotkey_binding_get_key_combination(obs_hotkey_binding_t *binding)
68 {
69 	return binding->key;
70 }
71 
obs_hotkey_binding_get_hotkey_id(obs_hotkey_binding_t * binding)72 obs_hotkey_id obs_hotkey_binding_get_hotkey_id(obs_hotkey_binding_t *binding)
73 {
74 	return binding->hotkey_id;
75 }
76 
obs_hotkey_binding_get_hotkey(obs_hotkey_binding_t * binding)77 obs_hotkey_t *obs_hotkey_binding_get_hotkey(obs_hotkey_binding_t *binding)
78 {
79 	return binding->hotkey;
80 }
81 
82 static inline bool find_id(obs_hotkey_id id, size_t *idx);
obs_hotkey_set_name(obs_hotkey_id id,const char * name)83 void obs_hotkey_set_name(obs_hotkey_id id, const char *name)
84 {
85 	size_t idx;
86 
87 	if (!find_id(id, &idx))
88 		return;
89 
90 	obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx];
91 	bfree(hotkey->name);
92 	hotkey->name = bstrdup(name);
93 }
94 
obs_hotkey_set_description(obs_hotkey_id id,const char * desc)95 void obs_hotkey_set_description(obs_hotkey_id id, const char *desc)
96 {
97 	size_t idx;
98 
99 	if (!find_id(id, &idx))
100 		return;
101 
102 	obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx];
103 	bfree(hotkey->description);
104 	hotkey->description = bstrdup(desc);
105 }
106 
107 static inline bool find_pair_id(obs_hotkey_pair_id id, size_t *idx);
obs_hotkey_pair_set_names(obs_hotkey_pair_id id,const char * name0,const char * name1)108 void obs_hotkey_pair_set_names(obs_hotkey_pair_id id, const char *name0,
109 			       const char *name1)
110 {
111 	size_t idx;
112 	obs_hotkey_pair_t pair;
113 
114 	if (!find_pair_id(id, &idx))
115 		return;
116 
117 	pair = obs->hotkeys.hotkey_pairs.array[idx];
118 
119 	obs_hotkey_set_name(pair.id[0], name0);
120 	obs_hotkey_set_name(pair.id[1], name1);
121 }
122 
obs_hotkey_pair_set_descriptions(obs_hotkey_pair_id id,const char * desc0,const char * desc1)123 void obs_hotkey_pair_set_descriptions(obs_hotkey_pair_id id, const char *desc0,
124 				      const char *desc1)
125 {
126 	size_t idx;
127 	obs_hotkey_pair_t pair;
128 
129 	if (!find_pair_id(id, &idx))
130 		return;
131 
132 	pair = obs->hotkeys.hotkey_pairs.array[idx];
133 
134 	obs_hotkey_set_description(pair.id[0], desc0);
135 	obs_hotkey_set_description(pair.id[1], desc1);
136 }
137 
hotkey_signal(const char * signal,obs_hotkey_t * hotkey)138 static void hotkey_signal(const char *signal, obs_hotkey_t *hotkey)
139 {
140 	calldata_t data;
141 	calldata_init(&data);
142 	calldata_set_ptr(&data, "key", hotkey);
143 
144 	signal_handler_signal(obs->hotkeys.signals, signal, &data);
145 
146 	calldata_free(&data);
147 }
148 
149 static inline void fixup_pointers(void);
150 static inline void load_bindings(obs_hotkey_t *hotkey, obs_data_array_t *data);
151 
context_add_hotkey(struct obs_context_data * context,obs_hotkey_id id)152 static inline void context_add_hotkey(struct obs_context_data *context,
153 				      obs_hotkey_id id)
154 {
155 	da_push_back(context->hotkeys, &id);
156 }
157 
158 static inline obs_hotkey_id
obs_hotkey_register_internal(obs_hotkey_registerer_t type,void * registerer,struct obs_context_data * context,const char * name,const char * description,obs_hotkey_func func,void * data)159 obs_hotkey_register_internal(obs_hotkey_registerer_t type, void *registerer,
160 			     struct obs_context_data *context, const char *name,
161 			     const char *description, obs_hotkey_func func,
162 			     void *data)
163 {
164 	if ((obs->hotkeys.next_id + 1) == OBS_INVALID_HOTKEY_ID)
165 		blog(LOG_WARNING, "obs-hotkey: Available hotkey ids exhausted");
166 
167 	obs_hotkey_t *base_addr = obs->hotkeys.hotkeys.array;
168 	obs_hotkey_id result = obs->hotkeys.next_id++;
169 	obs_hotkey_t *hotkey = da_push_back_new(obs->hotkeys.hotkeys);
170 
171 	hotkey->id = result;
172 	hotkey->name = bstrdup(name);
173 	hotkey->description = bstrdup(description);
174 	hotkey->func = func;
175 	hotkey->data = data;
176 	hotkey->registerer_type = type;
177 	hotkey->registerer = registerer;
178 	hotkey->pair_partner_id = OBS_INVALID_HOTKEY_PAIR_ID;
179 
180 	if (context) {
181 		obs_data_array_t *data =
182 			obs_data_get_array(context->hotkey_data, name);
183 		load_bindings(hotkey, data);
184 		obs_data_array_release(data);
185 
186 		context_add_hotkey(context, result);
187 	}
188 
189 	if (base_addr != obs->hotkeys.hotkeys.array)
190 		fixup_pointers();
191 
192 	hotkey_signal("hotkey_register", hotkey);
193 
194 	return result;
195 }
196 
obs_hotkey_register_frontend(const char * name,const char * description,obs_hotkey_func func,void * data)197 obs_hotkey_id obs_hotkey_register_frontend(const char *name,
198 					   const char *description,
199 					   obs_hotkey_func func, void *data)
200 {
201 	if (!lock())
202 		return OBS_INVALID_HOTKEY_ID;
203 
204 	obs_hotkey_id id = obs_hotkey_register_internal(
205 		OBS_HOTKEY_REGISTERER_FRONTEND, NULL, NULL, name, description,
206 		func, data);
207 
208 	unlock();
209 	return id;
210 }
211 
obs_hotkey_register_encoder(obs_encoder_t * encoder,const char * name,const char * description,obs_hotkey_func func,void * data)212 obs_hotkey_id obs_hotkey_register_encoder(obs_encoder_t *encoder,
213 					  const char *name,
214 					  const char *description,
215 					  obs_hotkey_func func, void *data)
216 {
217 	if (!encoder || !lock())
218 		return OBS_INVALID_HOTKEY_ID;
219 
220 	obs_hotkey_id id = obs_hotkey_register_internal(
221 		OBS_HOTKEY_REGISTERER_ENCODER,
222 		obs_encoder_get_weak_encoder(encoder), &encoder->context, name,
223 		description, func, data);
224 
225 	unlock();
226 	return id;
227 }
228 
obs_hotkey_register_output(obs_output_t * output,const char * name,const char * description,obs_hotkey_func func,void * data)229 obs_hotkey_id obs_hotkey_register_output(obs_output_t *output, const char *name,
230 					 const char *description,
231 					 obs_hotkey_func func, void *data)
232 {
233 	if (!output || !lock())
234 		return OBS_INVALID_HOTKEY_ID;
235 
236 	obs_hotkey_id id = obs_hotkey_register_internal(
237 		OBS_HOTKEY_REGISTERER_OUTPUT,
238 		obs_output_get_weak_output(output), &output->context, name,
239 		description, func, data);
240 
241 	unlock();
242 	return id;
243 }
244 
obs_hotkey_register_service(obs_service_t * service,const char * name,const char * description,obs_hotkey_func func,void * data)245 obs_hotkey_id obs_hotkey_register_service(obs_service_t *service,
246 					  const char *name,
247 					  const char *description,
248 					  obs_hotkey_func func, void *data)
249 {
250 	if (!service || !lock())
251 		return OBS_INVALID_HOTKEY_ID;
252 
253 	obs_hotkey_id id = obs_hotkey_register_internal(
254 		OBS_HOTKEY_REGISTERER_SERVICE,
255 		obs_service_get_weak_service(service), &service->context, name,
256 		description, func, data);
257 
258 	unlock();
259 	return id;
260 }
261 
obs_hotkey_register_source(obs_source_t * source,const char * name,const char * description,obs_hotkey_func func,void * data)262 obs_hotkey_id obs_hotkey_register_source(obs_source_t *source, const char *name,
263 					 const char *description,
264 					 obs_hotkey_func func, void *data)
265 {
266 	if (!source || source->context.private || !lock())
267 		return OBS_INVALID_HOTKEY_ID;
268 
269 	obs_hotkey_id id = obs_hotkey_register_internal(
270 		OBS_HOTKEY_REGISTERER_SOURCE,
271 		obs_source_get_weak_source(source), &source->context, name,
272 		description, func, data);
273 
274 	unlock();
275 	return id;
276 }
277 
278 static inline void fixup_pair_pointers(void);
279 
create_hotkey_pair(struct obs_context_data * context,obs_hotkey_active_func func0,obs_hotkey_active_func func1,void * data0,void * data1)280 static obs_hotkey_pair_t *create_hotkey_pair(struct obs_context_data *context,
281 					     obs_hotkey_active_func func0,
282 					     obs_hotkey_active_func func1,
283 					     void *data0, void *data1)
284 {
285 	if ((obs->hotkeys.next_pair_id + 1) == OBS_INVALID_HOTKEY_PAIR_ID)
286 		blog(LOG_WARNING, "obs-hotkey: Available hotkey pair ids "
287 				  "exhausted");
288 
289 	obs_hotkey_pair_t *base_addr = obs->hotkeys.hotkey_pairs.array;
290 	obs_hotkey_pair_t *pair = da_push_back_new(obs->hotkeys.hotkey_pairs);
291 
292 	pair->pair_id = obs->hotkeys.next_pair_id++;
293 	pair->func[0] = func0;
294 	pair->func[1] = func1;
295 	pair->id[0] = OBS_INVALID_HOTKEY_ID;
296 	pair->id[1] = OBS_INVALID_HOTKEY_ID;
297 	pair->data[0] = data0;
298 	pair->data[1] = data1;
299 
300 	if (context)
301 		da_push_back(context->hotkey_pairs, &pair->pair_id);
302 
303 	if (base_addr != obs->hotkeys.hotkey_pairs.array)
304 		fixup_pair_pointers();
305 
306 	return pair;
307 }
308 
obs_hotkey_pair_first_func(void * data,obs_hotkey_id id,obs_hotkey_t * hotkey,bool pressed)309 static void obs_hotkey_pair_first_func(void *data, obs_hotkey_id id,
310 				       obs_hotkey_t *hotkey, bool pressed)
311 {
312 	UNUSED_PARAMETER(id);
313 
314 	obs_hotkey_pair_t *pair = data;
315 	if (pair->pressed1)
316 		return;
317 
318 	if (pair->pressed0 && !pressed)
319 		pair->pressed0 = false;
320 	else if (pair->func[0](pair->data[0], pair->pair_id, hotkey, pressed))
321 		pair->pressed0 = pressed;
322 }
323 
obs_hotkey_pair_second_func(void * data,obs_hotkey_id id,obs_hotkey_t * hotkey,bool pressed)324 static void obs_hotkey_pair_second_func(void *data, obs_hotkey_id id,
325 					obs_hotkey_t *hotkey, bool pressed)
326 {
327 	UNUSED_PARAMETER(id);
328 
329 	obs_hotkey_pair_t *pair = data;
330 	if (pair->pressed0)
331 		return;
332 
333 	if (pair->pressed1 && !pressed)
334 		pair->pressed1 = false;
335 	else if (pair->func[1](pair->data[1], pair->pair_id, hotkey, pressed))
336 		pair->pressed1 = pressed;
337 }
338 
339 static inline bool find_id(obs_hotkey_id id, size_t *idx);
register_hotkey_pair_internal(obs_hotkey_registerer_t type,void * registerer,void * (* weak_ref)(void *),struct obs_context_data * context,const char * name0,const char * description0,const char * name1,const char * description1,obs_hotkey_active_func func0,obs_hotkey_active_func func1,void * data0,void * data1)340 static obs_hotkey_pair_id register_hotkey_pair_internal(
341 	obs_hotkey_registerer_t type, void *registerer,
342 	void *(*weak_ref)(void *), struct obs_context_data *context,
343 	const char *name0, const char *description0, const char *name1,
344 	const char *description1, obs_hotkey_active_func func0,
345 	obs_hotkey_active_func func1, void *data0, void *data1)
346 {
347 
348 	if (!lock())
349 		return OBS_INVALID_HOTKEY_PAIR_ID;
350 
351 	obs_hotkey_pair_t *pair =
352 		create_hotkey_pair(context, func0, func1, data0, data1);
353 
354 	pair->id[0] = obs_hotkey_register_internal(type, weak_ref(registerer),
355 						   context, name0, description0,
356 						   obs_hotkey_pair_first_func,
357 						   pair);
358 
359 	pair->id[1] = obs_hotkey_register_internal(type, weak_ref(registerer),
360 						   context, name1, description1,
361 						   obs_hotkey_pair_second_func,
362 						   pair);
363 
364 	size_t idx;
365 	if (find_id(pair->id[0], &idx))
366 		obs->hotkeys.hotkeys.array[idx].pair_partner_id = pair->id[1];
367 
368 	if (find_id(pair->id[1], &idx))
369 		obs->hotkeys.hotkeys.array[idx].pair_partner_id = pair->id[0];
370 
371 	obs_hotkey_pair_id id = pair->pair_id;
372 
373 	unlock();
374 	return id;
375 }
376 
obs_id_(void * id_)377 static inline void *obs_id_(void *id_)
378 {
379 	return id_;
380 }
381 
obs_hotkey_pair_register_frontend(const char * name0,const char * description0,const char * name1,const char * description1,obs_hotkey_active_func func0,obs_hotkey_active_func func1,void * data0,void * data1)382 obs_hotkey_pair_id obs_hotkey_pair_register_frontend(
383 	const char *name0, const char *description0, const char *name1,
384 	const char *description1, obs_hotkey_active_func func0,
385 	obs_hotkey_active_func func1, void *data0, void *data1)
386 {
387 	return register_hotkey_pair_internal(OBS_HOTKEY_REGISTERER_FRONTEND,
388 					     NULL, obs_id_, NULL, name0,
389 					     description0, name1, description1,
390 					     func0, func1, data0, data1);
391 }
392 
weak_encoder_ref(void * ref)393 static inline void *weak_encoder_ref(void *ref)
394 {
395 	return obs_encoder_get_weak_encoder(ref);
396 }
397 
obs_hotkey_pair_register_encoder(obs_encoder_t * encoder,const char * name0,const char * description0,const char * name1,const char * description1,obs_hotkey_active_func func0,obs_hotkey_active_func func1,void * data0,void * data1)398 obs_hotkey_pair_id obs_hotkey_pair_register_encoder(
399 	obs_encoder_t *encoder, const char *name0, const char *description0,
400 	const char *name1, const char *description1,
401 	obs_hotkey_active_func func0, obs_hotkey_active_func func1, void *data0,
402 	void *data1)
403 {
404 	if (!encoder)
405 		return OBS_INVALID_HOTKEY_PAIR_ID;
406 	return register_hotkey_pair_internal(OBS_HOTKEY_REGISTERER_ENCODER,
407 					     encoder, weak_encoder_ref,
408 					     &encoder->context, name0,
409 					     description0, name1, description1,
410 					     func0, func1, data0, data1);
411 }
412 
weak_output_ref(void * ref)413 static inline void *weak_output_ref(void *ref)
414 {
415 	return obs_output_get_weak_output(ref);
416 }
417 
obs_hotkey_pair_register_output(obs_output_t * output,const char * name0,const char * description0,const char * name1,const char * description1,obs_hotkey_active_func func0,obs_hotkey_active_func func1,void * data0,void * data1)418 obs_hotkey_pair_id obs_hotkey_pair_register_output(
419 	obs_output_t *output, const char *name0, const char *description0,
420 	const char *name1, const char *description1,
421 	obs_hotkey_active_func func0, obs_hotkey_active_func func1, void *data0,
422 	void *data1)
423 {
424 	if (!output)
425 		return OBS_INVALID_HOTKEY_PAIR_ID;
426 	return register_hotkey_pair_internal(OBS_HOTKEY_REGISTERER_OUTPUT,
427 					     output, weak_output_ref,
428 					     &output->context, name0,
429 					     description0, name1, description1,
430 					     func0, func1, data0, data1);
431 }
432 
weak_service_ref(void * ref)433 static inline void *weak_service_ref(void *ref)
434 {
435 	return obs_service_get_weak_service(ref);
436 }
437 
obs_hotkey_pair_register_service(obs_service_t * service,const char * name0,const char * description0,const char * name1,const char * description1,obs_hotkey_active_func func0,obs_hotkey_active_func func1,void * data0,void * data1)438 obs_hotkey_pair_id obs_hotkey_pair_register_service(
439 	obs_service_t *service, const char *name0, const char *description0,
440 	const char *name1, const char *description1,
441 	obs_hotkey_active_func func0, obs_hotkey_active_func func1, void *data0,
442 	void *data1)
443 {
444 	if (!service)
445 		return OBS_INVALID_HOTKEY_PAIR_ID;
446 	return register_hotkey_pair_internal(OBS_HOTKEY_REGISTERER_SERVICE,
447 					     service, weak_service_ref,
448 					     &service->context, name0,
449 					     description0, name1, description1,
450 					     func0, func1, data0, data1);
451 }
452 
weak_source_ref(void * ref)453 static inline void *weak_source_ref(void *ref)
454 {
455 	return obs_source_get_weak_source(ref);
456 }
457 
obs_hotkey_pair_register_source(obs_source_t * source,const char * name0,const char * description0,const char * name1,const char * description1,obs_hotkey_active_func func0,obs_hotkey_active_func func1,void * data0,void * data1)458 obs_hotkey_pair_id obs_hotkey_pair_register_source(
459 	obs_source_t *source, const char *name0, const char *description0,
460 	const char *name1, const char *description1,
461 	obs_hotkey_active_func func0, obs_hotkey_active_func func1, void *data0,
462 	void *data1)
463 {
464 	if (!source)
465 		return OBS_INVALID_HOTKEY_PAIR_ID;
466 	return register_hotkey_pair_internal(OBS_HOTKEY_REGISTERER_SOURCE,
467 					     source, weak_source_ref,
468 					     &source->context, name0,
469 					     description0, name1, description1,
470 					     func0, func1, data0, data1);
471 }
472 
473 typedef bool (*obs_hotkey_internal_enum_func)(void *data, size_t idx,
474 					      obs_hotkey_t *hotkey);
475 
enum_hotkeys(obs_hotkey_internal_enum_func func,void * data)476 static inline void enum_hotkeys(obs_hotkey_internal_enum_func func, void *data)
477 {
478 	const size_t num = obs->hotkeys.hotkeys.num;
479 	obs_hotkey_t *array = obs->hotkeys.hotkeys.array;
480 	for (size_t i = 0; i < num; i++) {
481 		if (!func(data, i, &array[i]))
482 			break;
483 	}
484 }
485 
486 typedef bool (*obs_hotkey_pair_internal_enum_func)(size_t idx,
487 						   obs_hotkey_pair_t *pair,
488 						   void *data);
489 
enum_hotkey_pairs(obs_hotkey_pair_internal_enum_func func,void * data)490 static inline void enum_hotkey_pairs(obs_hotkey_pair_internal_enum_func func,
491 				     void *data)
492 {
493 	const size_t num = obs->hotkeys.hotkey_pairs.num;
494 	obs_hotkey_pair_t *array = obs->hotkeys.hotkey_pairs.array;
495 	for (size_t i = 0; i < num; i++) {
496 		if (!func(i, &array[i], data))
497 			break;
498 	}
499 }
500 
501 typedef bool (*obs_hotkey_binding_internal_enum_func)(
502 	void *data, size_t idx, obs_hotkey_binding_t *binding);
503 
enum_bindings(obs_hotkey_binding_internal_enum_func func,void * data)504 static inline void enum_bindings(obs_hotkey_binding_internal_enum_func func,
505 				 void *data)
506 {
507 	const size_t num = obs->hotkeys.bindings.num;
508 	obs_hotkey_binding_t *array = obs->hotkeys.bindings.array;
509 	for (size_t i = 0; i < num; i++) {
510 		if (!func(data, i, &array[i]))
511 			break;
512 	}
513 }
514 
515 struct obs_hotkey_internal_find_forward {
516 	obs_hotkey_id id;
517 	bool found;
518 	size_t idx;
519 };
520 
find_id_helper(void * data,size_t idx,obs_hotkey_t * hotkey)521 static inline bool find_id_helper(void *data, size_t idx, obs_hotkey_t *hotkey)
522 {
523 	struct obs_hotkey_internal_find_forward *find = data;
524 	if (hotkey->id != find->id)
525 		return true;
526 
527 	find->idx = idx;
528 	find->found = true;
529 	return false;
530 }
531 
find_id(obs_hotkey_id id,size_t * idx)532 static inline bool find_id(obs_hotkey_id id, size_t *idx)
533 {
534 	struct obs_hotkey_internal_find_forward find = {id, false, 0};
535 	enum_hotkeys(find_id_helper, &find);
536 	*idx = find.idx;
537 	return find.found;
538 }
539 
pointer_fixup_func(void * data,size_t idx,obs_hotkey_binding_t * binding)540 static inline bool pointer_fixup_func(void *data, size_t idx,
541 				      obs_hotkey_binding_t *binding)
542 {
543 	UNUSED_PARAMETER(idx);
544 	UNUSED_PARAMETER(data);
545 
546 	size_t idx_;
547 	if (!find_id(binding->hotkey_id, &idx_)) {
548 		bcrash("obs-hotkey: Could not find hotkey id '%" PRIuMAX "' "
549 		       "for binding '%s' (modifiers 0x%x)",
550 		       (uintmax_t)binding->hotkey_id,
551 		       obs_key_to_name(binding->key.key),
552 		       binding->key.modifiers);
553 		binding->hotkey = NULL;
554 		return true;
555 	}
556 
557 	binding->hotkey = &obs->hotkeys.hotkeys.array[idx_];
558 
559 	return true;
560 }
561 
fixup_pointers(void)562 static inline void fixup_pointers(void)
563 {
564 	enum_bindings(pointer_fixup_func, NULL);
565 }
566 
567 struct obs_hotkey_internal_find_pair_forward {
568 	obs_hotkey_pair_id id;
569 	bool found;
570 	size_t idx;
571 };
572 
find_pair_id_helper(size_t idx,obs_hotkey_pair_t * pair,void * data)573 static inline bool find_pair_id_helper(size_t idx, obs_hotkey_pair_t *pair,
574 				       void *data)
575 {
576 	struct obs_hotkey_internal_find_pair_forward *find = data;
577 	if (pair->pair_id != find->id)
578 		return true;
579 
580 	find->idx = idx;
581 	find->found = true;
582 	return false;
583 }
584 
find_pair_id(obs_hotkey_pair_id id,size_t * idx)585 static inline bool find_pair_id(obs_hotkey_pair_id id, size_t *idx)
586 {
587 	struct obs_hotkey_internal_find_pair_forward find = {id, false, 0};
588 	enum_hotkey_pairs(find_pair_id_helper, &find);
589 	*idx = find.idx;
590 	return find.found;
591 }
592 
pair_pointer_fixup_func(size_t idx,obs_hotkey_pair_t * pair,void * data)593 static inline bool pair_pointer_fixup_func(size_t idx, obs_hotkey_pair_t *pair,
594 					   void *data)
595 {
596 	UNUSED_PARAMETER(idx);
597 	UNUSED_PARAMETER(data);
598 
599 	if (find_id(pair->id[0], &idx))
600 		obs->hotkeys.hotkeys.array[idx].data = pair;
601 
602 	if (find_id(pair->id[1], &idx))
603 		obs->hotkeys.hotkeys.array[idx].data = pair;
604 
605 	return true;
606 }
607 
fixup_pair_pointers(void)608 static inline void fixup_pair_pointers(void)
609 {
610 	enum_hotkey_pairs(pair_pointer_fixup_func, NULL);
611 }
612 
enum_context_hotkeys(struct obs_context_data * context,obs_hotkey_internal_enum_func func,void * data)613 static inline void enum_context_hotkeys(struct obs_context_data *context,
614 					obs_hotkey_internal_enum_func func,
615 					void *data)
616 {
617 	const size_t num = context->hotkeys.num;
618 	const obs_hotkey_id *array = context->hotkeys.array;
619 	obs_hotkey_t *hotkey_array = obs->hotkeys.hotkeys.array;
620 	for (size_t i = 0; i < num; i++) {
621 		size_t idx;
622 		if (!find_id(array[i], &idx))
623 			continue;
624 
625 		if (!func(data, idx, &hotkey_array[idx]))
626 			break;
627 	}
628 }
629 
load_modifier(uint32_t * modifiers,obs_data_t * data,const char * name,uint32_t flag)630 static inline void load_modifier(uint32_t *modifiers, obs_data_t *data,
631 				 const char *name, uint32_t flag)
632 {
633 	if (obs_data_get_bool(data, name))
634 		*modifiers |= flag;
635 }
636 
create_binding(obs_hotkey_t * hotkey,obs_key_combination_t combo)637 static inline void create_binding(obs_hotkey_t *hotkey,
638 				  obs_key_combination_t combo)
639 {
640 	obs_hotkey_binding_t *binding = da_push_back_new(obs->hotkeys.bindings);
641 	if (!binding)
642 		return;
643 
644 	binding->key = combo;
645 	binding->hotkey_id = hotkey->id;
646 	binding->hotkey = hotkey;
647 }
648 
load_binding(obs_hotkey_t * hotkey,obs_data_t * data)649 static inline void load_binding(obs_hotkey_t *hotkey, obs_data_t *data)
650 {
651 	if (!hotkey || !data)
652 		return;
653 
654 	obs_key_combination_t combo = {0};
655 	uint32_t *modifiers = &combo.modifiers;
656 	load_modifier(modifiers, data, "shift", INTERACT_SHIFT_KEY);
657 	load_modifier(modifiers, data, "control", INTERACT_CONTROL_KEY);
658 	load_modifier(modifiers, data, "alt", INTERACT_ALT_KEY);
659 	load_modifier(modifiers, data, "command", INTERACT_COMMAND_KEY);
660 
661 	combo.key = obs_key_from_name(obs_data_get_string(data, "key"));
662 	if (!modifiers &&
663 	    (combo.key == OBS_KEY_NONE || combo.key >= OBS_KEY_LAST_VALUE))
664 		return;
665 
666 	create_binding(hotkey, combo);
667 }
668 
load_bindings(obs_hotkey_t * hotkey,obs_data_array_t * data)669 static inline void load_bindings(obs_hotkey_t *hotkey, obs_data_array_t *data)
670 {
671 	const size_t count = obs_data_array_count(data);
672 	for (size_t i = 0; i < count; i++) {
673 		obs_data_t *item = obs_data_array_item(data, i);
674 		load_binding(hotkey, item);
675 		obs_data_release(item);
676 	}
677 
678 	hotkey_signal("hotkey_bindings_changed", hotkey);
679 }
680 
681 static inline void remove_bindings(obs_hotkey_id id);
682 
obs_hotkey_load_bindings(obs_hotkey_id id,obs_key_combination_t * combinations,size_t num)683 void obs_hotkey_load_bindings(obs_hotkey_id id,
684 			      obs_key_combination_t *combinations, size_t num)
685 {
686 	size_t idx;
687 
688 	if (!lock())
689 		return;
690 
691 	if (find_id(id, &idx)) {
692 		obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx];
693 		remove_bindings(id);
694 		for (size_t i = 0; i < num; i++)
695 			create_binding(hotkey, combinations[i]);
696 
697 		hotkey_signal("hotkey_bindings_changed", hotkey);
698 	}
699 	unlock();
700 }
701 
obs_hotkey_load(obs_hotkey_id id,obs_data_array_t * data)702 void obs_hotkey_load(obs_hotkey_id id, obs_data_array_t *data)
703 {
704 	size_t idx;
705 
706 	if (!lock())
707 		return;
708 
709 	if (find_id(id, &idx)) {
710 		remove_bindings(id);
711 		load_bindings(&obs->hotkeys.hotkeys.array[idx], data);
712 	}
713 	unlock();
714 }
715 
enum_load_bindings(void * data,size_t idx,obs_hotkey_t * hotkey)716 static inline bool enum_load_bindings(void *data, size_t idx,
717 				      obs_hotkey_t *hotkey)
718 {
719 	UNUSED_PARAMETER(idx);
720 
721 	obs_data_array_t *hotkey_data = obs_data_get_array(data, hotkey->name);
722 	if (!hotkey_data)
723 		return true;
724 
725 	load_bindings(hotkey, hotkey_data);
726 	obs_data_array_release(hotkey_data);
727 	return true;
728 }
729 
obs_hotkeys_load_encoder(obs_encoder_t * encoder,obs_data_t * hotkeys)730 void obs_hotkeys_load_encoder(obs_encoder_t *encoder, obs_data_t *hotkeys)
731 {
732 	if (!encoder || !hotkeys)
733 		return;
734 	if (!lock())
735 		return;
736 
737 	enum_context_hotkeys(&encoder->context, enum_load_bindings, hotkeys);
738 	unlock();
739 }
740 
obs_hotkeys_load_output(obs_output_t * output,obs_data_t * hotkeys)741 void obs_hotkeys_load_output(obs_output_t *output, obs_data_t *hotkeys)
742 {
743 	if (!output || !hotkeys)
744 		return;
745 	if (!lock())
746 		return;
747 
748 	enum_context_hotkeys(&output->context, enum_load_bindings, hotkeys);
749 	unlock();
750 }
751 
obs_hotkeys_load_service(obs_service_t * service,obs_data_t * hotkeys)752 void obs_hotkeys_load_service(obs_service_t *service, obs_data_t *hotkeys)
753 {
754 	if (!service || !hotkeys)
755 		return;
756 	if (!lock())
757 		return;
758 
759 	enum_context_hotkeys(&service->context, enum_load_bindings, hotkeys);
760 	unlock();
761 }
762 
obs_hotkeys_load_source(obs_source_t * source,obs_data_t * hotkeys)763 void obs_hotkeys_load_source(obs_source_t *source, obs_data_t *hotkeys)
764 {
765 	if (!source || !hotkeys)
766 		return;
767 	if (!lock())
768 		return;
769 
770 	enum_context_hotkeys(&source->context, enum_load_bindings, hotkeys);
771 	unlock();
772 }
773 
obs_hotkey_pair_load(obs_hotkey_pair_id id,obs_data_array_t * data0,obs_data_array_t * data1)774 void obs_hotkey_pair_load(obs_hotkey_pair_id id, obs_data_array_t *data0,
775 			  obs_data_array_t *data1)
776 {
777 	if ((!data0 && !data1) || !lock())
778 		return;
779 
780 	size_t idx;
781 	if (!find_pair_id(id, &idx))
782 		goto unlock;
783 
784 	obs_hotkey_pair_t *pair = &obs->hotkeys.hotkey_pairs.array[idx];
785 
786 	if (find_id(pair->id[0], &idx)) {
787 		remove_bindings(pair->id[0]);
788 		load_bindings(&obs->hotkeys.hotkeys.array[idx], data0);
789 	}
790 	if (find_id(pair->id[1], &idx)) {
791 		remove_bindings(pair->id[1]);
792 		load_bindings(&obs->hotkeys.hotkeys.array[idx], data1);
793 	}
794 
795 unlock:
796 	unlock();
797 }
798 
save_modifier(uint32_t modifiers,obs_data_t * data,const char * name,uint32_t flag)799 static inline void save_modifier(uint32_t modifiers, obs_data_t *data,
800 				 const char *name, uint32_t flag)
801 {
802 	if ((modifiers & flag) == flag)
803 		obs_data_set_bool(data, name, true);
804 }
805 
806 struct save_bindings_helper_t {
807 	obs_data_array_t *array;
808 	obs_hotkey_t *hotkey;
809 };
810 
save_bindings_helper(void * data,size_t idx,obs_hotkey_binding_t * binding)811 static inline bool save_bindings_helper(void *data, size_t idx,
812 					obs_hotkey_binding_t *binding)
813 {
814 	UNUSED_PARAMETER(idx);
815 	struct save_bindings_helper_t *h = data;
816 
817 	if (h->hotkey->id != binding->hotkey_id)
818 		return true;
819 
820 	obs_data_t *hotkey = obs_data_create();
821 
822 	uint32_t modifiers = binding->key.modifiers;
823 	save_modifier(modifiers, hotkey, "shift", INTERACT_SHIFT_KEY);
824 	save_modifier(modifiers, hotkey, "control", INTERACT_CONTROL_KEY);
825 	save_modifier(modifiers, hotkey, "alt", INTERACT_ALT_KEY);
826 	save_modifier(modifiers, hotkey, "command", INTERACT_COMMAND_KEY);
827 
828 	obs_data_set_string(hotkey, "key", obs_key_to_name(binding->key.key));
829 
830 	obs_data_array_push_back(h->array, hotkey);
831 
832 	obs_data_release(hotkey);
833 
834 	return true;
835 }
836 
save_hotkey(obs_hotkey_t * hotkey)837 static inline obs_data_array_t *save_hotkey(obs_hotkey_t *hotkey)
838 {
839 	obs_data_array_t *data = obs_data_array_create();
840 
841 	struct save_bindings_helper_t arg = {data, hotkey};
842 	enum_bindings(save_bindings_helper, &arg);
843 
844 	return data;
845 }
846 
obs_hotkey_save(obs_hotkey_id id)847 obs_data_array_t *obs_hotkey_save(obs_hotkey_id id)
848 {
849 	size_t idx;
850 	obs_data_array_t *result = NULL;
851 
852 	if (!lock())
853 		return result;
854 
855 	if (find_id(id, &idx))
856 		result = save_hotkey(&obs->hotkeys.hotkeys.array[idx]);
857 	unlock();
858 
859 	return result;
860 }
861 
obs_hotkey_pair_save(obs_hotkey_pair_id id,obs_data_array_t ** p_data0,obs_data_array_t ** p_data1)862 void obs_hotkey_pair_save(obs_hotkey_pair_id id, obs_data_array_t **p_data0,
863 			  obs_data_array_t **p_data1)
864 {
865 	if ((!p_data0 && !p_data1) || !lock())
866 		return;
867 
868 	size_t idx;
869 	if (!find_pair_id(id, &idx))
870 		goto unlock;
871 
872 	obs_hotkey_pair_t *pair = &obs->hotkeys.hotkey_pairs.array[idx];
873 
874 	if (p_data0 && find_id(pair->id[0], &idx)) {
875 		*p_data0 = save_hotkey(&obs->hotkeys.hotkeys.array[idx]);
876 	}
877 	if (p_data1 && find_id(pair->id[1], &idx)) {
878 		*p_data1 = save_hotkey(&obs->hotkeys.hotkeys.array[idx]);
879 	}
880 
881 unlock:
882 	unlock();
883 }
884 
enum_save_hotkey(void * data,size_t idx,obs_hotkey_t * hotkey)885 static inline bool enum_save_hotkey(void *data, size_t idx,
886 				    obs_hotkey_t *hotkey)
887 {
888 	UNUSED_PARAMETER(idx);
889 
890 	obs_data_array_t *hotkey_data = save_hotkey(hotkey);
891 	obs_data_set_array(data, hotkey->name, hotkey_data);
892 	obs_data_array_release(hotkey_data);
893 	return true;
894 }
895 
save_context_hotkeys(struct obs_context_data * context)896 static inline obs_data_t *save_context_hotkeys(struct obs_context_data *context)
897 {
898 	if (!context->hotkeys.num)
899 		return NULL;
900 
901 	obs_data_t *result = obs_data_create();
902 	enum_context_hotkeys(context, enum_save_hotkey, result);
903 	return result;
904 }
905 
obs_hotkeys_save_encoder(obs_encoder_t * encoder)906 obs_data_t *obs_hotkeys_save_encoder(obs_encoder_t *encoder)
907 {
908 	obs_data_t *result = NULL;
909 
910 	if (!lock())
911 		return result;
912 
913 	result = save_context_hotkeys(&encoder->context);
914 	unlock();
915 
916 	return result;
917 }
918 
obs_hotkeys_save_output(obs_output_t * output)919 obs_data_t *obs_hotkeys_save_output(obs_output_t *output)
920 {
921 	obs_data_t *result = NULL;
922 
923 	if (!lock())
924 		return result;
925 
926 	result = save_context_hotkeys(&output->context);
927 	unlock();
928 
929 	return result;
930 }
931 
obs_hotkeys_save_service(obs_service_t * service)932 obs_data_t *obs_hotkeys_save_service(obs_service_t *service)
933 {
934 	obs_data_t *result = NULL;
935 
936 	if (!lock())
937 		return result;
938 
939 	result = save_context_hotkeys(&service->context);
940 	unlock();
941 
942 	return result;
943 }
944 
obs_hotkeys_save_source(obs_source_t * source)945 obs_data_t *obs_hotkeys_save_source(obs_source_t *source)
946 {
947 	obs_data_t *result = NULL;
948 
949 	if (!lock())
950 		return result;
951 
952 	result = save_context_hotkeys(&source->context);
953 	unlock();
954 
955 	return result;
956 }
957 
958 struct binding_find_data {
959 	obs_hotkey_id id;
960 	size_t *idx;
961 	bool found;
962 };
963 
binding_finder(void * data,size_t idx,obs_hotkey_binding_t * binding)964 static inline bool binding_finder(void *data, size_t idx,
965 				  obs_hotkey_binding_t *binding)
966 {
967 	struct binding_find_data *find = data;
968 	if (binding->hotkey_id != find->id)
969 		return true;
970 
971 	*find->idx = idx;
972 	find->found = true;
973 	return false;
974 }
975 
find_binding(obs_hotkey_id id,size_t * idx)976 static inline bool find_binding(obs_hotkey_id id, size_t *idx)
977 {
978 	struct binding_find_data data = {id, idx, false};
979 	enum_bindings(binding_finder, &data);
980 	return data.found;
981 }
982 
983 static inline void release_pressed_binding(obs_hotkey_binding_t *binding);
984 
remove_bindings(obs_hotkey_id id)985 static inline void remove_bindings(obs_hotkey_id id)
986 {
987 	size_t idx;
988 	while (find_binding(id, &idx)) {
989 		obs_hotkey_binding_t *binding =
990 			&obs->hotkeys.bindings.array[idx];
991 
992 		if (binding->pressed)
993 			release_pressed_binding(binding);
994 
995 		da_erase(obs->hotkeys.bindings, idx);
996 	}
997 }
998 
release_registerer(obs_hotkey_t * hotkey)999 static void release_registerer(obs_hotkey_t *hotkey)
1000 {
1001 	switch (hotkey->registerer_type) {
1002 	case OBS_HOTKEY_REGISTERER_FRONTEND:
1003 		break;
1004 
1005 	case OBS_HOTKEY_REGISTERER_ENCODER:
1006 		obs_weak_encoder_release(hotkey->registerer);
1007 		break;
1008 
1009 	case OBS_HOTKEY_REGISTERER_OUTPUT:
1010 		obs_weak_output_release(hotkey->registerer);
1011 		break;
1012 
1013 	case OBS_HOTKEY_REGISTERER_SERVICE:
1014 		obs_weak_service_release(hotkey->registerer);
1015 		break;
1016 
1017 	case OBS_HOTKEY_REGISTERER_SOURCE:
1018 		obs_weak_source_release(hotkey->registerer);
1019 		break;
1020 	}
1021 
1022 	hotkey->registerer = NULL;
1023 }
1024 
unregister_hotkey(obs_hotkey_id id)1025 static inline bool unregister_hotkey(obs_hotkey_id id)
1026 {
1027 	if (id >= obs->hotkeys.next_id)
1028 		return false;
1029 
1030 	size_t idx;
1031 	if (!find_id(id, &idx))
1032 		return false;
1033 
1034 	obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx];
1035 
1036 	hotkey_signal("hotkey_unregister", hotkey);
1037 
1038 	release_registerer(hotkey);
1039 
1040 	bfree(hotkey->name);
1041 	bfree(hotkey->description);
1042 
1043 	if (hotkey->registerer_type == OBS_HOTKEY_REGISTERER_SOURCE)
1044 		obs_weak_source_release(hotkey->registerer);
1045 
1046 	da_erase(obs->hotkeys.hotkeys, idx);
1047 	remove_bindings(id);
1048 
1049 	return obs->hotkeys.hotkeys.num >= idx;
1050 }
1051 
unregister_hotkey_pair(obs_hotkey_pair_id id)1052 static inline bool unregister_hotkey_pair(obs_hotkey_pair_id id)
1053 {
1054 	if (id >= obs->hotkeys.next_pair_id)
1055 		return false;
1056 
1057 	size_t idx;
1058 	if (!find_pair_id(id, &idx))
1059 		return false;
1060 
1061 	obs_hotkey_pair_t *pair = &obs->hotkeys.hotkey_pairs.array[idx];
1062 
1063 	bool need_fixup = unregister_hotkey(pair->id[0]);
1064 	need_fixup = unregister_hotkey(pair->id[1]) || need_fixup;
1065 	if (need_fixup)
1066 		fixup_pointers();
1067 
1068 	da_erase(obs->hotkeys.hotkey_pairs, idx);
1069 	return obs->hotkeys.hotkey_pairs.num >= idx;
1070 }
1071 
obs_hotkey_unregister(obs_hotkey_id id)1072 void obs_hotkey_unregister(obs_hotkey_id id)
1073 {
1074 	if (!lock())
1075 		return;
1076 	if (unregister_hotkey(id))
1077 		fixup_pointers();
1078 	unlock();
1079 }
1080 
obs_hotkey_pair_unregister(obs_hotkey_pair_id id)1081 void obs_hotkey_pair_unregister(obs_hotkey_pair_id id)
1082 {
1083 	if (!lock())
1084 		return;
1085 
1086 	if (unregister_hotkey_pair(id))
1087 		fixup_pair_pointers();
1088 
1089 	unlock();
1090 }
1091 
context_release_hotkeys(struct obs_context_data * context)1092 static void context_release_hotkeys(struct obs_context_data *context)
1093 {
1094 	if (!context->hotkeys.num)
1095 		goto cleanup;
1096 
1097 	bool need_fixup = false;
1098 	for (size_t i = 0; i < context->hotkeys.num; i++)
1099 		need_fixup = unregister_hotkey(context->hotkeys.array[i]) ||
1100 			     need_fixup;
1101 
1102 	if (need_fixup)
1103 		fixup_pointers();
1104 
1105 cleanup:
1106 	da_free(context->hotkeys);
1107 }
1108 
context_release_hotkey_pairs(struct obs_context_data * context)1109 static void context_release_hotkey_pairs(struct obs_context_data *context)
1110 {
1111 	if (!context->hotkey_pairs.num)
1112 		goto cleanup;
1113 
1114 	bool need_fixup = false;
1115 	for (size_t i = 0; i < context->hotkey_pairs.num; i++)
1116 		need_fixup = unregister_hotkey_pair(
1117 				     context->hotkey_pairs.array[i]) ||
1118 			     need_fixup;
1119 
1120 	if (need_fixup)
1121 		fixup_pair_pointers();
1122 
1123 cleanup:
1124 	da_free(context->hotkey_pairs);
1125 }
1126 
obs_hotkeys_context_release(struct obs_context_data * context)1127 void obs_hotkeys_context_release(struct obs_context_data *context)
1128 {
1129 	if (!lock())
1130 		return;
1131 
1132 	context_release_hotkeys(context);
1133 	context_release_hotkey_pairs(context);
1134 
1135 	obs_data_release(context->hotkey_data);
1136 	unlock();
1137 }
1138 
obs_hotkeys_free(void)1139 void obs_hotkeys_free(void)
1140 {
1141 	const size_t num = obs->hotkeys.hotkeys.num;
1142 	obs_hotkey_t *hotkeys = obs->hotkeys.hotkeys.array;
1143 	for (size_t i = 0; i < num; i++) {
1144 		bfree(hotkeys[i].name);
1145 		bfree(hotkeys[i].description);
1146 
1147 		release_registerer(&hotkeys[i]);
1148 	}
1149 	da_free(obs->hotkeys.bindings);
1150 	da_free(obs->hotkeys.hotkeys);
1151 	da_free(obs->hotkeys.hotkey_pairs);
1152 
1153 	for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) {
1154 		if (obs->hotkeys.translations[i]) {
1155 			bfree(obs->hotkeys.translations[i]);
1156 			obs->hotkeys.translations[i] = NULL;
1157 		}
1158 	}
1159 }
1160 
1161 struct obs_hotkey_internal_enum_forward {
1162 	obs_hotkey_enum_func func;
1163 	void *data;
1164 };
1165 
enum_hotkey(void * data,size_t idx,obs_hotkey_t * hotkey)1166 static inline bool enum_hotkey(void *data, size_t idx, obs_hotkey_t *hotkey)
1167 {
1168 	UNUSED_PARAMETER(idx);
1169 
1170 	struct obs_hotkey_internal_enum_forward *forward = data;
1171 	return forward->func(forward->data, hotkey->id, hotkey);
1172 }
1173 
obs_enum_hotkeys(obs_hotkey_enum_func func,void * data)1174 void obs_enum_hotkeys(obs_hotkey_enum_func func, void *data)
1175 {
1176 	struct obs_hotkey_internal_enum_forward forwarder = {func, data};
1177 	if (!lock())
1178 		return;
1179 
1180 	enum_hotkeys(enum_hotkey, &forwarder);
1181 	unlock();
1182 }
1183 
obs_enum_hotkey_bindings(obs_hotkey_binding_enum_func func,void * data)1184 void obs_enum_hotkey_bindings(obs_hotkey_binding_enum_func func, void *data)
1185 {
1186 	if (!lock())
1187 		return;
1188 
1189 	enum_bindings(func, data);
1190 	unlock();
1191 }
1192 
modifiers_match(obs_hotkey_binding_t * binding,uint32_t modifiers_,bool strict_modifiers)1193 static inline bool modifiers_match(obs_hotkey_binding_t *binding,
1194 				   uint32_t modifiers_, bool strict_modifiers)
1195 {
1196 	uint32_t modifiers = binding->key.modifiers;
1197 	return !modifiers ||
1198 	       (!strict_modifiers && (modifiers & modifiers_) == modifiers) ||
1199 	       (strict_modifiers && modifiers == modifiers_);
1200 }
1201 
is_pressed(obs_key_t key)1202 static inline bool is_pressed(obs_key_t key)
1203 {
1204 	return obs_hotkeys_platform_is_pressed(obs->hotkeys.platform_context,
1205 					       key);
1206 }
1207 
press_released_binding(obs_hotkey_binding_t * binding)1208 static inline void press_released_binding(obs_hotkey_binding_t *binding)
1209 {
1210 	binding->pressed = true;
1211 
1212 	obs_hotkey_t *hotkey = binding->hotkey;
1213 	if (hotkey->pressed++)
1214 		return;
1215 
1216 	if (!obs->hotkeys.reroute_hotkeys)
1217 		hotkey->func(hotkey->data, hotkey->id, hotkey, true);
1218 	else if (obs->hotkeys.router_func)
1219 		obs->hotkeys.router_func(obs->hotkeys.router_func_data,
1220 					 hotkey->id, true);
1221 }
1222 
release_pressed_binding(obs_hotkey_binding_t * binding)1223 static inline void release_pressed_binding(obs_hotkey_binding_t *binding)
1224 {
1225 	binding->pressed = false;
1226 
1227 	obs_hotkey_t *hotkey = binding->hotkey;
1228 	if (--hotkey->pressed)
1229 		return;
1230 
1231 	if (!obs->hotkeys.reroute_hotkeys)
1232 		hotkey->func(hotkey->data, hotkey->id, hotkey, false);
1233 	else if (obs->hotkeys.router_func)
1234 		obs->hotkeys.router_func(obs->hotkeys.router_func_data,
1235 					 hotkey->id, false);
1236 }
1237 
handle_binding(obs_hotkey_binding_t * binding,uint32_t modifiers,bool no_press,bool strict_modifiers,bool * pressed)1238 static inline void handle_binding(obs_hotkey_binding_t *binding,
1239 				  uint32_t modifiers, bool no_press,
1240 				  bool strict_modifiers, bool *pressed)
1241 {
1242 	bool modifiers_match_ =
1243 		modifiers_match(binding, modifiers, strict_modifiers);
1244 	bool modifiers_only = binding->key.key == OBS_KEY_NONE;
1245 
1246 	if (!binding->key.modifiers)
1247 		binding->modifiers_match = true;
1248 
1249 	if (modifiers_only)
1250 		pressed = &modifiers_only;
1251 
1252 	if (!binding->key.modifiers && modifiers_only)
1253 		goto reset;
1254 
1255 	if ((!binding->modifiers_match && !modifiers_only) || !modifiers_match_)
1256 		goto reset;
1257 
1258 	if ((pressed && !*pressed) ||
1259 	    (!pressed && !is_pressed(binding->key.key)))
1260 		goto reset;
1261 
1262 	if (binding->pressed || no_press)
1263 		return;
1264 
1265 	press_released_binding(binding);
1266 	return;
1267 
1268 reset:
1269 	binding->modifiers_match = modifiers_match_;
1270 	if (!binding->pressed)
1271 		return;
1272 
1273 	release_pressed_binding(binding);
1274 }
1275 
1276 struct obs_hotkey_internal_inject {
1277 	obs_key_combination_t hotkey;
1278 	bool pressed;
1279 	bool strict_modifiers;
1280 };
1281 
inject_hotkey(void * data,size_t idx,obs_hotkey_binding_t * binding)1282 static inline bool inject_hotkey(void *data, size_t idx,
1283 				 obs_hotkey_binding_t *binding)
1284 {
1285 	UNUSED_PARAMETER(idx);
1286 	struct obs_hotkey_internal_inject *event = data;
1287 
1288 	if (modifiers_match(binding, event->hotkey.modifiers,
1289 			    event->strict_modifiers)) {
1290 		bool pressed = binding->key.key == event->hotkey.key &&
1291 			       event->pressed;
1292 		handle_binding(binding, event->hotkey.modifiers, false,
1293 			       event->strict_modifiers, &pressed);
1294 	}
1295 
1296 	return true;
1297 }
1298 
obs_hotkey_inject_event(obs_key_combination_t hotkey,bool pressed)1299 void obs_hotkey_inject_event(obs_key_combination_t hotkey, bool pressed)
1300 {
1301 	if (!lock())
1302 		return;
1303 
1304 	struct obs_hotkey_internal_inject event = {
1305 		{hotkey.modifiers, hotkey.key},
1306 		pressed,
1307 		obs->hotkeys.strict_modifiers,
1308 	};
1309 	enum_bindings(inject_hotkey, &event);
1310 	unlock();
1311 }
1312 
obs_hotkey_enable_background_press(bool enable)1313 void obs_hotkey_enable_background_press(bool enable)
1314 {
1315 	if (!lock())
1316 		return;
1317 
1318 	obs->hotkeys.thread_disable_press = !enable;
1319 	unlock();
1320 }
1321 
obs_hotkey_enable_strict_modifiers(bool enable)1322 void obs_hotkey_enable_strict_modifiers(bool enable)
1323 {
1324 	if (!lock())
1325 		return;
1326 
1327 	obs->hotkeys.strict_modifiers = enable;
1328 	unlock();
1329 }
1330 
1331 struct obs_query_hotkeys_helper {
1332 	uint32_t modifiers;
1333 	bool no_press;
1334 	bool strict_modifiers;
1335 };
1336 
query_hotkey(void * data,size_t idx,obs_hotkey_binding_t * binding)1337 static inline bool query_hotkey(void *data, size_t idx,
1338 				obs_hotkey_binding_t *binding)
1339 {
1340 	UNUSED_PARAMETER(idx);
1341 
1342 	struct obs_query_hotkeys_helper *param =
1343 		(struct obs_query_hotkeys_helper *)data;
1344 	handle_binding(binding, param->modifiers, param->no_press,
1345 		       param->strict_modifiers, NULL);
1346 
1347 	return true;
1348 }
1349 
query_hotkeys()1350 static inline void query_hotkeys()
1351 {
1352 	uint32_t modifiers = 0;
1353 	if (is_pressed(OBS_KEY_SHIFT))
1354 		modifiers |= INTERACT_SHIFT_KEY;
1355 	if (is_pressed(OBS_KEY_CONTROL))
1356 		modifiers |= INTERACT_CONTROL_KEY;
1357 	if (is_pressed(OBS_KEY_ALT))
1358 		modifiers |= INTERACT_ALT_KEY;
1359 	if (is_pressed(OBS_KEY_META))
1360 		modifiers |= INTERACT_COMMAND_KEY;
1361 
1362 	struct obs_query_hotkeys_helper param = {
1363 		modifiers,
1364 		obs->hotkeys.thread_disable_press,
1365 		obs->hotkeys.strict_modifiers,
1366 	};
1367 	enum_bindings(query_hotkey, &param);
1368 }
1369 
1370 #define NBSP "\xC2\xA0"
1371 
obs_hotkey_thread(void * arg)1372 void *obs_hotkey_thread(void *arg)
1373 {
1374 	UNUSED_PARAMETER(arg);
1375 
1376 	os_set_thread_name("libobs: hotkey thread");
1377 
1378 	const char *hotkey_thread_name =
1379 		profile_store_name(obs_get_profiler_name_store(),
1380 				   "obs_hotkey_thread(%g" NBSP "ms)", 25.);
1381 	profile_register_root(hotkey_thread_name, (uint64_t)25000000);
1382 
1383 	while (os_event_timedwait(obs->hotkeys.stop_event, 25) == ETIMEDOUT) {
1384 		if (!lock())
1385 			continue;
1386 
1387 		profile_start(hotkey_thread_name);
1388 		query_hotkeys();
1389 		profile_end(hotkey_thread_name);
1390 
1391 		unlock();
1392 
1393 		profile_reenable_thread();
1394 	}
1395 	return NULL;
1396 }
1397 
obs_hotkey_trigger_routed_callback(obs_hotkey_id id,bool pressed)1398 void obs_hotkey_trigger_routed_callback(obs_hotkey_id id, bool pressed)
1399 {
1400 	if (!lock())
1401 		return;
1402 
1403 	if (!obs->hotkeys.reroute_hotkeys)
1404 		goto unlock;
1405 
1406 	size_t idx;
1407 	if (!find_id(id, &idx))
1408 		goto unlock;
1409 
1410 	obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx];
1411 	hotkey->func(hotkey->data, id, hotkey, pressed);
1412 
1413 unlock:
1414 	unlock();
1415 }
1416 
obs_hotkey_set_callback_routing_func(obs_hotkey_callback_router_func func,void * data)1417 void obs_hotkey_set_callback_routing_func(obs_hotkey_callback_router_func func,
1418 					  void *data)
1419 {
1420 	if (!lock())
1421 		return;
1422 
1423 	obs->hotkeys.router_func = func;
1424 	obs->hotkeys.router_func_data = data;
1425 	unlock();
1426 }
1427 
obs_hotkey_enable_callback_rerouting(bool enable)1428 void obs_hotkey_enable_callback_rerouting(bool enable)
1429 {
1430 	if (!lock())
1431 		return;
1432 
1433 	obs->hotkeys.reroute_hotkeys = enable;
1434 	unlock();
1435 }
1436 
obs_set_key_translation(obs_key_t key,const char * translation)1437 static void obs_set_key_translation(obs_key_t key, const char *translation)
1438 {
1439 	bfree(obs->hotkeys.translations[key]);
1440 	obs->hotkeys.translations[key] = NULL;
1441 
1442 	if (translation)
1443 		obs->hotkeys.translations[key] = bstrdup(translation);
1444 }
1445 
obs_hotkeys_set_translations_s(struct obs_hotkeys_translations * translations,size_t size)1446 void obs_hotkeys_set_translations_s(
1447 	struct obs_hotkeys_translations *translations, size_t size)
1448 {
1449 #define ADD_TRANSLATION(key_name, var_name) \
1450 	if (t.var_name)                     \
1451 		obs_set_key_translation(key_name, t.var_name);
1452 
1453 	struct obs_hotkeys_translations t = {0};
1454 	struct dstr numpad = {0};
1455 	struct dstr mouse = {0};
1456 	struct dstr button = {0};
1457 
1458 	if (!translations) {
1459 		return;
1460 	}
1461 
1462 	memcpy(&t, translations, (size < sizeof(t)) ? size : sizeof(t));
1463 
1464 	ADD_TRANSLATION(OBS_KEY_INSERT, insert);
1465 	ADD_TRANSLATION(OBS_KEY_DELETE, del);
1466 	ADD_TRANSLATION(OBS_KEY_HOME, home);
1467 	ADD_TRANSLATION(OBS_KEY_END, end);
1468 	ADD_TRANSLATION(OBS_KEY_PAGEUP, page_up);
1469 	ADD_TRANSLATION(OBS_KEY_PAGEDOWN, page_down);
1470 	ADD_TRANSLATION(OBS_KEY_NUMLOCK, num_lock);
1471 	ADD_TRANSLATION(OBS_KEY_SCROLLLOCK, scroll_lock);
1472 	ADD_TRANSLATION(OBS_KEY_CAPSLOCK, caps_lock);
1473 	ADD_TRANSLATION(OBS_KEY_BACKSPACE, backspace);
1474 	ADD_TRANSLATION(OBS_KEY_TAB, tab);
1475 	ADD_TRANSLATION(OBS_KEY_PRINT, print);
1476 	ADD_TRANSLATION(OBS_KEY_PAUSE, pause);
1477 	ADD_TRANSLATION(OBS_KEY_SHIFT, shift);
1478 	ADD_TRANSLATION(OBS_KEY_ALT, alt);
1479 	ADD_TRANSLATION(OBS_KEY_CONTROL, control);
1480 	ADD_TRANSLATION(OBS_KEY_META, meta);
1481 	ADD_TRANSLATION(OBS_KEY_MENU, menu);
1482 	ADD_TRANSLATION(OBS_KEY_SPACE, space);
1483 	ADD_TRANSLATION(OBS_KEY_ESCAPE, escape);
1484 #ifdef __APPLE__
1485 	const char *numpad_str = t.apple_keypad_num;
1486 	ADD_TRANSLATION(OBS_KEY_NUMSLASH, apple_keypad_divide);
1487 	ADD_TRANSLATION(OBS_KEY_NUMASTERISK, apple_keypad_multiply);
1488 	ADD_TRANSLATION(OBS_KEY_NUMMINUS, apple_keypad_minus);
1489 	ADD_TRANSLATION(OBS_KEY_NUMPLUS, apple_keypad_plus);
1490 	ADD_TRANSLATION(OBS_KEY_NUMPERIOD, apple_keypad_decimal);
1491 	ADD_TRANSLATION(OBS_KEY_NUMEQUAL, apple_keypad_equal);
1492 #else
1493 	const char *numpad_str = t.numpad_num;
1494 	ADD_TRANSLATION(OBS_KEY_NUMSLASH, numpad_divide);
1495 	ADD_TRANSLATION(OBS_KEY_NUMASTERISK, numpad_multiply);
1496 	ADD_TRANSLATION(OBS_KEY_NUMMINUS, numpad_minus);
1497 	ADD_TRANSLATION(OBS_KEY_NUMPLUS, numpad_plus);
1498 	ADD_TRANSLATION(OBS_KEY_NUMPERIOD, numpad_decimal);
1499 #endif
1500 
1501 	if (numpad_str) {
1502 		dstr_copy(&numpad, numpad_str);
1503 		dstr_depad(&numpad);
1504 
1505 		if (dstr_find(&numpad, "%1") == NULL) {
1506 			dstr_cat(&numpad, " %1");
1507 		}
1508 
1509 #define ADD_NUMPAD_NUM(idx)                \
1510 	dstr_copy_dstr(&button, &numpad);  \
1511 	dstr_replace(&button, "%1", #idx); \
1512 	obs_set_key_translation(OBS_KEY_NUM##idx, button.array)
1513 
1514 		ADD_NUMPAD_NUM(0);
1515 		ADD_NUMPAD_NUM(1);
1516 		ADD_NUMPAD_NUM(2);
1517 		ADD_NUMPAD_NUM(3);
1518 		ADD_NUMPAD_NUM(4);
1519 		ADD_NUMPAD_NUM(5);
1520 		ADD_NUMPAD_NUM(6);
1521 		ADD_NUMPAD_NUM(7);
1522 		ADD_NUMPAD_NUM(8);
1523 		ADD_NUMPAD_NUM(9);
1524 	}
1525 
1526 	if (t.mouse_num) {
1527 		dstr_copy(&mouse, t.mouse_num);
1528 		dstr_depad(&mouse);
1529 
1530 		if (dstr_find(&mouse, "%1") == NULL) {
1531 			dstr_cat(&mouse, " %1");
1532 		}
1533 
1534 #define ADD_MOUSE_NUM(idx)                 \
1535 	dstr_copy_dstr(&button, &mouse);   \
1536 	dstr_replace(&button, "%1", #idx); \
1537 	obs_set_key_translation(OBS_KEY_MOUSE##idx, button.array)
1538 
1539 		ADD_MOUSE_NUM(1);
1540 		ADD_MOUSE_NUM(2);
1541 		ADD_MOUSE_NUM(3);
1542 		ADD_MOUSE_NUM(4);
1543 		ADD_MOUSE_NUM(5);
1544 		ADD_MOUSE_NUM(6);
1545 		ADD_MOUSE_NUM(7);
1546 		ADD_MOUSE_NUM(8);
1547 		ADD_MOUSE_NUM(9);
1548 		ADD_MOUSE_NUM(10);
1549 		ADD_MOUSE_NUM(11);
1550 		ADD_MOUSE_NUM(12);
1551 		ADD_MOUSE_NUM(13);
1552 		ADD_MOUSE_NUM(14);
1553 		ADD_MOUSE_NUM(15);
1554 		ADD_MOUSE_NUM(16);
1555 		ADD_MOUSE_NUM(17);
1556 		ADD_MOUSE_NUM(18);
1557 		ADD_MOUSE_NUM(19);
1558 		ADD_MOUSE_NUM(20);
1559 		ADD_MOUSE_NUM(21);
1560 		ADD_MOUSE_NUM(22);
1561 		ADD_MOUSE_NUM(23);
1562 		ADD_MOUSE_NUM(24);
1563 		ADD_MOUSE_NUM(25);
1564 		ADD_MOUSE_NUM(26);
1565 		ADD_MOUSE_NUM(27);
1566 		ADD_MOUSE_NUM(28);
1567 		ADD_MOUSE_NUM(29);
1568 	}
1569 
1570 	dstr_free(&numpad);
1571 	dstr_free(&mouse);
1572 	dstr_free(&button);
1573 }
1574 
obs_get_hotkey_translation(obs_key_t key,const char * def)1575 const char *obs_get_hotkey_translation(obs_key_t key, const char *def)
1576 {
1577 	if (key == OBS_KEY_NONE) {
1578 		return NULL;
1579 	}
1580 
1581 	return obs->hotkeys.translations[key] ? obs->hotkeys.translations[key]
1582 					      : def;
1583 }
1584 
obs_hotkey_update_atomic(obs_hotkey_atomic_update_func func,void * data)1585 void obs_hotkey_update_atomic(obs_hotkey_atomic_update_func func, void *data)
1586 {
1587 	if (!lock())
1588 		return;
1589 
1590 	func(data);
1591 
1592 	unlock();
1593 }
1594 
obs_hotkeys_set_audio_hotkeys_translations(const char * mute,const char * unmute,const char * push_to_mute,const char * push_to_talk)1595 void obs_hotkeys_set_audio_hotkeys_translations(const char *mute,
1596 						const char *unmute,
1597 						const char *push_to_mute,
1598 						const char *push_to_talk)
1599 {
1600 #define SET_T(n)               \
1601 	bfree(obs->hotkeys.n); \
1602 	obs->hotkeys.n = bstrdup(n)
1603 	SET_T(mute);
1604 	SET_T(unmute);
1605 	SET_T(push_to_mute);
1606 	SET_T(push_to_talk);
1607 #undef SET_T
1608 }
1609 
obs_hotkeys_set_sceneitem_hotkeys_translations(const char * show,const char * hide)1610 void obs_hotkeys_set_sceneitem_hotkeys_translations(const char *show,
1611 						    const char *hide)
1612 {
1613 #define SET_T(n)                           \
1614 	bfree(obs->hotkeys.sceneitem_##n); \
1615 	obs->hotkeys.sceneitem_##n = bstrdup(n)
1616 	SET_T(show);
1617 	SET_T(hide);
1618 #undef SET_T
1619 }
1620