obs-studio/libobs/obs-hotkey.c
jp9000 f53df7da64 clang-format: Apply formatting
Code submissions have continually suffered from formatting
inconsistencies that constantly have to be addressed.  Using
clang-format simplifies this by making code formatting more consistent,
and allows automation of the code formatting so that maintainers can
focus more on the code itself instead of code formatting.
2019-06-23 23:49:10 -07:00

1618 lines
38 KiB
C

/******************************************************************************
Copyright (C) 2014-2015 by Ruwen Hahn <palana@stunned.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <inttypes.h>
#include "obs-internal.h"
static inline bool lock(void)
{
if (!obs)
return false;
pthread_mutex_lock(&obs->hotkeys.mutex);
return true;
}
static inline void unlock(void)
{
pthread_mutex_unlock(&obs->hotkeys.mutex);
}
obs_hotkey_id obs_hotkey_get_id(const obs_hotkey_t *key)
{
return key->id;
}
const char *obs_hotkey_get_name(const obs_hotkey_t *key)
{
return key->name;
}
const char *obs_hotkey_get_description(const obs_hotkey_t *key)
{
return key->description;
}
obs_hotkey_registerer_t obs_hotkey_get_registerer_type(const obs_hotkey_t *key)
{
return key->registerer_type;
}
void *obs_hotkey_get_registerer(const obs_hotkey_t *key)
{
return key->registerer;
}
obs_hotkey_id obs_hotkey_get_pair_partner_id(const obs_hotkey_t *key)
{
return key->pair_partner_id;
}
obs_key_combination_t
obs_hotkey_binding_get_key_combination(obs_hotkey_binding_t *binding)
{
return binding->key;
}
obs_hotkey_id obs_hotkey_binding_get_hotkey_id(obs_hotkey_binding_t *binding)
{
return binding->hotkey_id;
}
obs_hotkey_t *obs_hotkey_binding_get_hotkey(obs_hotkey_binding_t *binding)
{
return binding->hotkey;
}
static inline bool find_id(obs_hotkey_id id, size_t *idx);
void obs_hotkey_set_name(obs_hotkey_id id, const char *name)
{
size_t idx;
if (!find_id(id, &idx))
return;
obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx];
bfree(hotkey->name);
hotkey->name = bstrdup(name);
}
void obs_hotkey_set_description(obs_hotkey_id id, const char *desc)
{
size_t idx;
if (!find_id(id, &idx))
return;
obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx];
bfree(hotkey->description);
hotkey->description = bstrdup(desc);
}
static inline bool find_pair_id(obs_hotkey_pair_id id, size_t *idx);
void obs_hotkey_pair_set_names(obs_hotkey_pair_id id, const char *name0,
const char *name1)
{
size_t idx;
obs_hotkey_pair_t pair;
if (!find_pair_id(id, &idx))
return;
pair = obs->hotkeys.hotkey_pairs.array[idx];
obs_hotkey_set_name(pair.id[0], name0);
obs_hotkey_set_name(pair.id[1], name1);
}
void obs_hotkey_pair_set_descriptions(obs_hotkey_pair_id id, const char *desc0,
const char *desc1)
{
size_t idx;
obs_hotkey_pair_t pair;
if (!find_pair_id(id, &idx))
return;
pair = obs->hotkeys.hotkey_pairs.array[idx];
obs_hotkey_set_description(pair.id[0], desc0);
obs_hotkey_set_description(pair.id[1], desc1);
}
static void hotkey_signal(const char *signal, obs_hotkey_t *hotkey)
{
calldata_t data;
calldata_init(&data);
calldata_set_ptr(&data, "key", hotkey);
signal_handler_signal(obs->hotkeys.signals, signal, &data);
calldata_free(&data);
}
static inline void fixup_pointers(void);
static inline void load_bindings(obs_hotkey_t *hotkey, obs_data_array_t *data);
static inline void context_add_hotkey(struct obs_context_data *context,
obs_hotkey_id id)
{
da_push_back(context->hotkeys, &id);
}
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)
{
if ((obs->hotkeys.next_id + 1) == OBS_INVALID_HOTKEY_ID)
blog(LOG_WARNING, "obs-hotkey: Available hotkey ids exhausted");
obs_hotkey_t *base_addr = obs->hotkeys.hotkeys.array;
obs_hotkey_id result = obs->hotkeys.next_id++;
obs_hotkey_t *hotkey = da_push_back_new(obs->hotkeys.hotkeys);
hotkey->id = result;
hotkey->name = bstrdup(name);
hotkey->description = bstrdup(description);
hotkey->func = func;
hotkey->data = data;
hotkey->registerer_type = type;
hotkey->registerer = registerer;
hotkey->pair_partner_id = OBS_INVALID_HOTKEY_PAIR_ID;
if (context) {
obs_data_array_t *data =
obs_data_get_array(context->hotkey_data, name);
load_bindings(hotkey, data);
obs_data_array_release(data);
context_add_hotkey(context, result);
}
if (base_addr != obs->hotkeys.hotkeys.array)
fixup_pointers();
hotkey_signal("hotkey_register", hotkey);
return result;
}
obs_hotkey_id obs_hotkey_register_frontend(const char *name,
const char *description,
obs_hotkey_func func, void *data)
{
if (!lock())
return OBS_INVALID_HOTKEY_ID;
obs_hotkey_id id = obs_hotkey_register_internal(
OBS_HOTKEY_REGISTERER_FRONTEND, NULL, NULL, name, description,
func, data);
unlock();
return id;
}
obs_hotkey_id obs_hotkey_register_encoder(obs_encoder_t *encoder,
const char *name,
const char *description,
obs_hotkey_func func, void *data)
{
if (!encoder || !lock())
return OBS_INVALID_HOTKEY_ID;
obs_hotkey_id id = obs_hotkey_register_internal(
OBS_HOTKEY_REGISTERER_ENCODER,
obs_encoder_get_weak_encoder(encoder), &encoder->context, name,
description, func, data);
unlock();
return id;
}
obs_hotkey_id obs_hotkey_register_output(obs_output_t *output, const char *name,
const char *description,
obs_hotkey_func func, void *data)
{
if (!output || !lock())
return OBS_INVALID_HOTKEY_ID;
obs_hotkey_id id = obs_hotkey_register_internal(
OBS_HOTKEY_REGISTERER_OUTPUT,
obs_output_get_weak_output(output), &output->context, name,
description, func, data);
unlock();
return id;
}
obs_hotkey_id obs_hotkey_register_service(obs_service_t *service,
const char *name,
const char *description,
obs_hotkey_func func, void *data)
{
if (!service || !lock())
return OBS_INVALID_HOTKEY_ID;
obs_hotkey_id id = obs_hotkey_register_internal(
OBS_HOTKEY_REGISTERER_SERVICE,
obs_service_get_weak_service(service), &service->context, name,
description, func, data);
unlock();
return id;
}
obs_hotkey_id obs_hotkey_register_source(obs_source_t *source, const char *name,
const char *description,
obs_hotkey_func func, void *data)
{
if (!source || source->context.private || !lock())
return OBS_INVALID_HOTKEY_ID;
obs_hotkey_id id = obs_hotkey_register_internal(
OBS_HOTKEY_REGISTERER_SOURCE,
obs_source_get_weak_source(source), &source->context, name,
description, func, data);
unlock();
return id;
}
static inline void fixup_pair_pointers(void);
static obs_hotkey_pair_t *create_hotkey_pair(struct obs_context_data *context,
obs_hotkey_active_func func0,
obs_hotkey_active_func func1,
void *data0, void *data1)
{
if ((obs->hotkeys.next_pair_id + 1) == OBS_INVALID_HOTKEY_PAIR_ID)
blog(LOG_WARNING, "obs-hotkey: Available hotkey pair ids "
"exhausted");
obs_hotkey_pair_t *base_addr = obs->hotkeys.hotkey_pairs.array;
obs_hotkey_pair_t *pair = da_push_back_new(obs->hotkeys.hotkey_pairs);
pair->pair_id = obs->hotkeys.next_pair_id++;
pair->func[0] = func0;
pair->func[1] = func1;
pair->id[0] = OBS_INVALID_HOTKEY_ID;
pair->id[1] = OBS_INVALID_HOTKEY_ID;
pair->data[0] = data0;
pair->data[1] = data1;
if (context)
da_push_back(context->hotkey_pairs, &pair->pair_id);
if (base_addr != obs->hotkeys.hotkey_pairs.array)
fixup_pair_pointers();
return pair;
}
static void obs_hotkey_pair_first_func(void *data, obs_hotkey_id id,
obs_hotkey_t *hotkey, bool pressed)
{
UNUSED_PARAMETER(id);
obs_hotkey_pair_t *pair = data;
if (pair->pressed1)
return;
if (pair->pressed0 && !pressed)
pair->pressed0 = false;
else if (pair->func[0](pair->data[0], pair->pair_id, hotkey, pressed))
pair->pressed0 = pressed;
}
static void obs_hotkey_pair_second_func(void *data, obs_hotkey_id id,
obs_hotkey_t *hotkey, bool pressed)
{
UNUSED_PARAMETER(id);
obs_hotkey_pair_t *pair = data;
if (pair->pressed0)
return;
if (pair->pressed1 && !pressed)
pair->pressed1 = false;
else if (pair->func[1](pair->data[0], pair->pair_id, hotkey, pressed))
pair->pressed1 = pressed;
}
static inline bool find_id(obs_hotkey_id id, size_t *idx);
static obs_hotkey_pair_id 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)
{
if (!lock())
return OBS_INVALID_HOTKEY_PAIR_ID;
obs_hotkey_pair_t *pair =
create_hotkey_pair(context, func0, func1, data0, data1);
pair->id[0] = obs_hotkey_register_internal(type, weak_ref(registerer),
context, name0, description0,
obs_hotkey_pair_first_func,
pair);
pair->id[1] = obs_hotkey_register_internal(type, weak_ref(registerer),
context, name1, description1,
obs_hotkey_pair_second_func,
pair);
size_t idx;
if (find_id(pair->id[0], &idx))
obs->hotkeys.hotkeys.array[idx].pair_partner_id = pair->id[1];
if (find_id(pair->id[1], &idx))
obs->hotkeys.hotkeys.array[idx].pair_partner_id = pair->id[0];
obs_hotkey_pair_id id = pair->pair_id;
unlock();
return id;
}
static inline void *obs_id_(void *id_)
{
return id_;
}
obs_hotkey_pair_id 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)
{
return register_hotkey_pair_internal(OBS_HOTKEY_REGISTERER_FRONTEND,
NULL, obs_id_, NULL, name0,
description0, name1, description1,
func0, func1, data0, data1);
}
static inline void *weak_encoder_ref(void *ref)
{
return obs_encoder_get_weak_encoder(ref);
}
obs_hotkey_pair_id 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)
{
if (!encoder)
return OBS_INVALID_HOTKEY_PAIR_ID;
return register_hotkey_pair_internal(OBS_HOTKEY_REGISTERER_ENCODER,
encoder, weak_encoder_ref,
&encoder->context, name0,
description0, name1, description1,
func0, func1, data0, data1);
}
static inline void *weak_output_ref(void *ref)
{
return obs_output_get_weak_output(ref);
}
obs_hotkey_pair_id 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)
{
if (!output)
return OBS_INVALID_HOTKEY_PAIR_ID;
return register_hotkey_pair_internal(OBS_HOTKEY_REGISTERER_OUTPUT,
output, weak_output_ref,
&output->context, name0,
description0, name1, description1,
func0, func1, data0, data1);
}
static inline void *weak_service_ref(void *ref)
{
return obs_service_get_weak_service(ref);
}
obs_hotkey_pair_id 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)
{
if (!service)
return OBS_INVALID_HOTKEY_PAIR_ID;
return register_hotkey_pair_internal(OBS_HOTKEY_REGISTERER_SERVICE,
service, weak_service_ref,
&service->context, name0,
description0, name1, description1,
func0, func1, data0, data1);
}
static inline void *weak_source_ref(void *ref)
{
return obs_source_get_weak_source(ref);
}
obs_hotkey_pair_id 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)
{
if (!source)
return OBS_INVALID_HOTKEY_PAIR_ID;
return register_hotkey_pair_internal(OBS_HOTKEY_REGISTERER_SOURCE,
source, weak_source_ref,
&source->context, name0,
description0, name1, description1,
func0, func1, data0, data1);
}
typedef bool (*obs_hotkey_internal_enum_func)(void *data, size_t idx,
obs_hotkey_t *hotkey);
static inline void enum_hotkeys(obs_hotkey_internal_enum_func func, void *data)
{
const size_t num = obs->hotkeys.hotkeys.num;
obs_hotkey_t *array = obs->hotkeys.hotkeys.array;
for (size_t i = 0; i < num; i++) {
if (!func(data, i, &array[i]))
break;
}
}
typedef bool (*obs_hotkey_pair_internal_enum_func)(size_t idx,
obs_hotkey_pair_t *pair,
void *data);
static inline void enum_hotkey_pairs(obs_hotkey_pair_internal_enum_func func,
void *data)
{
const size_t num = obs->hotkeys.hotkey_pairs.num;
obs_hotkey_pair_t *array = obs->hotkeys.hotkey_pairs.array;
for (size_t i = 0; i < num; i++) {
if (!func(i, &array[i], data))
break;
}
}
typedef bool (*obs_hotkey_binding_internal_enum_func)(
void *data, size_t idx, obs_hotkey_binding_t *binding);
static inline void enum_bindings(obs_hotkey_binding_internal_enum_func func,
void *data)
{
const size_t num = obs->hotkeys.bindings.num;
obs_hotkey_binding_t *array = obs->hotkeys.bindings.array;
for (size_t i = 0; i < num; i++) {
if (!func(data, i, &array[i]))
break;
}
}
struct obs_hotkey_internal_find_forward {
obs_hotkey_id id;
bool found;
size_t idx;
};
static inline bool find_id_helper(void *data, size_t idx, obs_hotkey_t *hotkey)
{
struct obs_hotkey_internal_find_forward *find = data;
if (hotkey->id != find->id)
return true;
find->idx = idx;
find->found = true;
return false;
}
static inline bool find_id(obs_hotkey_id id, size_t *idx)
{
struct obs_hotkey_internal_find_forward find = {id, false, 0};
enum_hotkeys(find_id_helper, &find);
*idx = find.idx;
return find.found;
}
static inline bool pointer_fixup_func(void *data, size_t idx,
obs_hotkey_binding_t *binding)
{
UNUSED_PARAMETER(idx);
UNUSED_PARAMETER(data);
size_t idx_;
if (!find_id(binding->hotkey_id, &idx_)) {
bcrash("obs-hotkey: Could not find hotkey id '%" PRIuMAX "' "
"for binding '%s' (modifiers 0x%x)",
(uintmax_t)binding->hotkey_id,
obs_key_to_name(binding->key.key),
binding->key.modifiers);
binding->hotkey = NULL;
return true;
}
binding->hotkey = &obs->hotkeys.hotkeys.array[idx_];
return true;
}
static inline void fixup_pointers(void)
{
enum_bindings(pointer_fixup_func, NULL);
}
struct obs_hotkey_internal_find_pair_forward {
obs_hotkey_pair_id id;
bool found;
size_t idx;
};
static inline bool find_pair_id_helper(size_t idx, obs_hotkey_pair_t *pair,
void *data)
{
struct obs_hotkey_internal_find_pair_forward *find = data;
if (pair->pair_id != find->id)
return true;
find->idx = idx;
find->found = true;
return false;
}
static inline bool find_pair_id(obs_hotkey_pair_id id, size_t *idx)
{
struct obs_hotkey_internal_find_pair_forward find = {id, false, 0};
enum_hotkey_pairs(find_pair_id_helper, &find);
*idx = find.idx;
return find.found;
}
static inline bool pair_pointer_fixup_func(size_t idx, obs_hotkey_pair_t *pair,
void *data)
{
UNUSED_PARAMETER(idx);
UNUSED_PARAMETER(data);
if (find_id(pair->id[0], &idx))
obs->hotkeys.hotkeys.array[idx].data = pair;
if (find_id(pair->id[1], &idx))
obs->hotkeys.hotkeys.array[idx].data = pair;
return true;
}
static inline void fixup_pair_pointers(void)
{
enum_hotkey_pairs(pair_pointer_fixup_func, NULL);
}
static inline void enum_context_hotkeys(struct obs_context_data *context,
obs_hotkey_internal_enum_func func,
void *data)
{
const size_t num = context->hotkeys.num;
const obs_hotkey_id *array = context->hotkeys.array;
obs_hotkey_t *hotkey_array = obs->hotkeys.hotkeys.array;
for (size_t i = 0; i < num; i++) {
size_t idx;
if (!find_id(array[i], &idx))
continue;
if (!func(data, idx, &hotkey_array[idx]))
break;
}
}
static inline void load_modifier(uint32_t *modifiers, obs_data_t *data,
const char *name, uint32_t flag)
{
if (obs_data_get_bool(data, name))
*modifiers |= flag;
}
static inline void create_binding(obs_hotkey_t *hotkey,
obs_key_combination_t combo)
{
obs_hotkey_binding_t *binding = da_push_back_new(obs->hotkeys.bindings);
if (!binding)
return;
binding->key = combo;
binding->hotkey_id = hotkey->id;
binding->hotkey = hotkey;
}
static inline void load_binding(obs_hotkey_t *hotkey, obs_data_t *data)
{
if (!hotkey || !data)
return;
obs_key_combination_t combo = {0};
uint32_t *modifiers = &combo.modifiers;
load_modifier(modifiers, data, "shift", INTERACT_SHIFT_KEY);
load_modifier(modifiers, data, "control", INTERACT_CONTROL_KEY);
load_modifier(modifiers, data, "alt", INTERACT_ALT_KEY);
load_modifier(modifiers, data, "command", INTERACT_COMMAND_KEY);
combo.key = obs_key_from_name(obs_data_get_string(data, "key"));
if (!modifiers &&
(combo.key == OBS_KEY_NONE || combo.key >= OBS_KEY_LAST_VALUE))
return;
create_binding(hotkey, combo);
}
static inline void load_bindings(obs_hotkey_t *hotkey, obs_data_array_t *data)
{
const size_t count = obs_data_array_count(data);
for (size_t i = 0; i < count; i++) {
obs_data_t *item = obs_data_array_item(data, i);
load_binding(hotkey, item);
obs_data_release(item);
}
hotkey_signal("hotkey_bindings_changed", hotkey);
}
static inline void remove_bindings(obs_hotkey_id id);
void obs_hotkey_load_bindings(obs_hotkey_id id,
obs_key_combination_t *combinations, size_t num)
{
size_t idx;
if (!lock())
return;
if (find_id(id, &idx)) {
obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx];
remove_bindings(id);
for (size_t i = 0; i < num; i++)
create_binding(hotkey, combinations[i]);
hotkey_signal("hotkey_bindings_changed", hotkey);
}
unlock();
}
void obs_hotkey_load(obs_hotkey_id id, obs_data_array_t *data)
{
size_t idx;
if (!lock())
return;
if (find_id(id, &idx)) {
remove_bindings(id);
load_bindings(&obs->hotkeys.hotkeys.array[idx], data);
}
unlock();
}
static inline bool enum_load_bindings(void *data, size_t idx,
obs_hotkey_t *hotkey)
{
UNUSED_PARAMETER(idx);
obs_data_array_t *hotkey_data = obs_data_get_array(data, hotkey->name);
if (!hotkey_data)
return true;
load_bindings(hotkey, hotkey_data);
obs_data_array_release(hotkey_data);
return true;
}
void obs_hotkeys_load_encoder(obs_encoder_t *encoder, obs_data_t *hotkeys)
{
if (!encoder || !hotkeys)
return;
if (!lock())
return;
enum_context_hotkeys(&encoder->context, enum_load_bindings, hotkeys);
unlock();
}
void obs_hotkeys_load_output(obs_output_t *output, obs_data_t *hotkeys)
{
if (!output || !hotkeys)
return;
if (!lock())
return;
enum_context_hotkeys(&output->context, enum_load_bindings, hotkeys);
unlock();
}
void obs_hotkeys_load_service(obs_service_t *service, obs_data_t *hotkeys)
{
if (!service || !hotkeys)
return;
if (!lock())
return;
enum_context_hotkeys(&service->context, enum_load_bindings, hotkeys);
unlock();
}
void obs_hotkeys_load_source(obs_source_t *source, obs_data_t *hotkeys)
{
if (!source || !hotkeys)
return;
if (!lock())
return;
enum_context_hotkeys(&source->context, enum_load_bindings, hotkeys);
unlock();
}
void obs_hotkey_pair_load(obs_hotkey_pair_id id, obs_data_array_t *data0,
obs_data_array_t *data1)
{
if ((!data0 && !data1) || !lock())
return;
size_t idx;
if (!find_pair_id(id, &idx))
goto unlock;
obs_hotkey_pair_t *pair = &obs->hotkeys.hotkey_pairs.array[idx];
if (find_id(pair->id[0], &idx)) {
remove_bindings(pair->id[0]);
load_bindings(&obs->hotkeys.hotkeys.array[idx], data0);
}
if (find_id(pair->id[1], &idx)) {
remove_bindings(pair->id[1]);
load_bindings(&obs->hotkeys.hotkeys.array[idx], data1);
}
unlock:
unlock();
}
static inline void save_modifier(uint32_t modifiers, obs_data_t *data,
const char *name, uint32_t flag)
{
if ((modifiers & flag) == flag)
obs_data_set_bool(data, name, true);
}
struct save_bindings_helper_t {
obs_data_array_t *array;
obs_hotkey_t *hotkey;
};
static inline bool save_bindings_helper(void *data, size_t idx,
obs_hotkey_binding_t *binding)
{
UNUSED_PARAMETER(idx);
struct save_bindings_helper_t *h = data;
if (h->hotkey->id != binding->hotkey_id)
return true;
obs_data_t *hotkey = obs_data_create();
uint32_t modifiers = binding->key.modifiers;
save_modifier(modifiers, hotkey, "shift", INTERACT_SHIFT_KEY);
save_modifier(modifiers, hotkey, "control", INTERACT_CONTROL_KEY);
save_modifier(modifiers, hotkey, "alt", INTERACT_ALT_KEY);
save_modifier(modifiers, hotkey, "command", INTERACT_COMMAND_KEY);
obs_data_set_string(hotkey, "key", obs_key_to_name(binding->key.key));
obs_data_array_push_back(h->array, hotkey);
obs_data_release(hotkey);
return true;
}
static inline obs_data_array_t *save_hotkey(obs_hotkey_t *hotkey)
{
obs_data_array_t *data = obs_data_array_create();
struct save_bindings_helper_t arg = {data, hotkey};
enum_bindings(save_bindings_helper, &arg);
return data;
}
obs_data_array_t *obs_hotkey_save(obs_hotkey_id id)
{
size_t idx;
obs_data_array_t *result = NULL;
if (!lock())
return result;
if (find_id(id, &idx))
result = save_hotkey(&obs->hotkeys.hotkeys.array[idx]);
unlock();
return result;
}
void obs_hotkey_pair_save(obs_hotkey_pair_id id, obs_data_array_t **p_data0,
obs_data_array_t **p_data1)
{
if ((!p_data0 && !p_data1) || !lock())
return;
size_t idx;
if (!find_pair_id(id, &idx))
goto unlock;
obs_hotkey_pair_t *pair = &obs->hotkeys.hotkey_pairs.array[idx];
if (p_data0 && find_id(pair->id[0], &idx)) {
*p_data0 = save_hotkey(&obs->hotkeys.hotkeys.array[idx]);
}
if (p_data1 && find_id(pair->id[1], &idx)) {
*p_data1 = save_hotkey(&obs->hotkeys.hotkeys.array[idx]);
}
unlock:
unlock();
}
static inline bool enum_save_hotkey(void *data, size_t idx,
obs_hotkey_t *hotkey)
{
UNUSED_PARAMETER(idx);
obs_data_array_t *hotkey_data = save_hotkey(hotkey);
obs_data_set_array(data, hotkey->name, hotkey_data);
obs_data_array_release(hotkey_data);
return true;
}
static inline obs_data_t *save_context_hotkeys(struct obs_context_data *context)
{
if (!context->hotkeys.num)
return NULL;
obs_data_t *result = obs_data_create();
enum_context_hotkeys(context, enum_save_hotkey, result);
return result;
}
obs_data_t *obs_hotkeys_save_encoder(obs_encoder_t *encoder)
{
obs_data_t *result = NULL;
if (!lock())
return result;
result = save_context_hotkeys(&encoder->context);
unlock();
return result;
}
obs_data_t *obs_hotkeys_save_output(obs_output_t *output)
{
obs_data_t *result = NULL;
if (!lock())
return result;
result = save_context_hotkeys(&output->context);
unlock();
return result;
}
obs_data_t *obs_hotkeys_save_service(obs_service_t *service)
{
obs_data_t *result = NULL;
if (!lock())
return result;
result = save_context_hotkeys(&service->context);
unlock();
return result;
}
obs_data_t *obs_hotkeys_save_source(obs_source_t *source)
{
obs_data_t *result = NULL;
if (!lock())
return result;
result = save_context_hotkeys(&source->context);
unlock();
return result;
}
struct binding_find_data {
obs_hotkey_id id;
size_t *idx;
bool found;
};
static inline bool binding_finder(void *data, size_t idx,
obs_hotkey_binding_t *binding)
{
struct binding_find_data *find = data;
if (binding->hotkey_id != find->id)
return true;
*find->idx = idx;
find->found = true;
return false;
}
static inline bool find_binding(obs_hotkey_id id, size_t *idx)
{
struct binding_find_data data = {id, idx, false};
enum_bindings(binding_finder, &data);
return data.found;
}
static inline void release_pressed_binding(obs_hotkey_binding_t *binding);
static inline void remove_bindings(obs_hotkey_id id)
{
size_t idx;
while (find_binding(id, &idx)) {
obs_hotkey_binding_t *binding =
&obs->hotkeys.bindings.array[idx];
if (binding->pressed)
release_pressed_binding(binding);
da_erase(obs->hotkeys.bindings, idx);
}
}
static void release_registerer(obs_hotkey_t *hotkey)
{
switch (hotkey->registerer_type) {
case OBS_HOTKEY_REGISTERER_FRONTEND:
break;
case OBS_HOTKEY_REGISTERER_ENCODER:
obs_weak_encoder_release(hotkey->registerer);
break;
case OBS_HOTKEY_REGISTERER_OUTPUT:
obs_weak_output_release(hotkey->registerer);
break;
case OBS_HOTKEY_REGISTERER_SERVICE:
obs_weak_service_release(hotkey->registerer);
break;
case OBS_HOTKEY_REGISTERER_SOURCE:
obs_weak_source_release(hotkey->registerer);
break;
}
hotkey->registerer = NULL;
}
static inline bool unregister_hotkey(obs_hotkey_id id)
{
if (id >= obs->hotkeys.next_id)
return false;
size_t idx;
if (!find_id(id, &idx))
return false;
obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx];
hotkey_signal("hotkey_unregister", hotkey);
release_registerer(hotkey);
bfree(hotkey->name);
bfree(hotkey->description);
if (hotkey->registerer_type == OBS_HOTKEY_REGISTERER_SOURCE)
obs_weak_source_release(hotkey->registerer);
da_erase(obs->hotkeys.hotkeys, idx);
remove_bindings(id);
return obs->hotkeys.hotkeys.num >= idx;
}
static inline bool unregister_hotkey_pair(obs_hotkey_pair_id id)
{
if (id >= obs->hotkeys.next_pair_id)
return false;
size_t idx;
if (!find_pair_id(id, &idx))
return false;
obs_hotkey_pair_t *pair = &obs->hotkeys.hotkey_pairs.array[idx];
bool need_fixup = unregister_hotkey(pair->id[0]);
need_fixup = unregister_hotkey(pair->id[1]) || need_fixup;
if (need_fixup)
fixup_pointers();
da_erase(obs->hotkeys.hotkey_pairs, idx);
return obs->hotkeys.hotkey_pairs.num >= idx;
}
void obs_hotkey_unregister(obs_hotkey_id id)
{
if (!lock())
return;
if (unregister_hotkey(id))
fixup_pointers();
unlock();
}
void obs_hotkey_pair_unregister(obs_hotkey_pair_id id)
{
if (!lock())
return;
if (unregister_hotkey_pair(id))
fixup_pair_pointers();
unlock();
}
static void context_release_hotkeys(struct obs_context_data *context)
{
if (!context->hotkeys.num)
goto cleanup;
bool need_fixup = false;
for (size_t i = 0; i < context->hotkeys.num; i++)
need_fixup = unregister_hotkey(context->hotkeys.array[i]) ||
need_fixup;
if (need_fixup)
fixup_pointers();
cleanup:
da_free(context->hotkeys);
}
static void context_release_hotkey_pairs(struct obs_context_data *context)
{
if (!context->hotkey_pairs.num)
goto cleanup;
bool need_fixup = false;
for (size_t i = 0; i < context->hotkey_pairs.num; i++)
need_fixup = unregister_hotkey_pair(
context->hotkey_pairs.array[i]) ||
need_fixup;
if (need_fixup)
fixup_pair_pointers();
cleanup:
da_free(context->hotkey_pairs);
}
void obs_hotkeys_context_release(struct obs_context_data *context)
{
if (!lock())
return;
context_release_hotkeys(context);
context_release_hotkey_pairs(context);
obs_data_release(context->hotkey_data);
unlock();
}
void obs_hotkeys_free(void)
{
const size_t num = obs->hotkeys.hotkeys.num;
obs_hotkey_t *hotkeys = obs->hotkeys.hotkeys.array;
for (size_t i = 0; i < num; i++) {
bfree(hotkeys[i].name);
bfree(hotkeys[i].description);
release_registerer(&hotkeys[i]);
}
da_free(obs->hotkeys.bindings);
da_free(obs->hotkeys.hotkeys);
da_free(obs->hotkeys.hotkey_pairs);
for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) {
if (obs->hotkeys.translations[i]) {
bfree(obs->hotkeys.translations[i]);
obs->hotkeys.translations[i] = NULL;
}
}
}
struct obs_hotkey_internal_enum_forward {
obs_hotkey_enum_func func;
void *data;
};
static inline bool enum_hotkey(void *data, size_t idx, obs_hotkey_t *hotkey)
{
UNUSED_PARAMETER(idx);
struct obs_hotkey_internal_enum_forward *forward = data;
return forward->func(forward->data, hotkey->id, hotkey);
}
void obs_enum_hotkeys(obs_hotkey_enum_func func, void *data)
{
struct obs_hotkey_internal_enum_forward forwarder = {func, data};
if (!lock())
return;
enum_hotkeys(enum_hotkey, &forwarder);
unlock();
}
void obs_enum_hotkey_bindings(obs_hotkey_binding_enum_func func, void *data)
{
if (!lock())
return;
enum_bindings(func, data);
unlock();
}
static inline bool modifiers_match(obs_hotkey_binding_t *binding,
uint32_t modifiers_, bool strict_modifiers)
{
uint32_t modifiers = binding->key.modifiers;
return !modifiers ||
(!strict_modifiers && (modifiers & modifiers_) == modifiers) ||
(strict_modifiers && modifiers == modifiers_);
}
static inline bool is_pressed(obs_key_t key)
{
return obs_hotkeys_platform_is_pressed(obs->hotkeys.platform_context,
key);
}
static inline void press_released_binding(obs_hotkey_binding_t *binding)
{
binding->pressed = true;
obs_hotkey_t *hotkey = binding->hotkey;
if (hotkey->pressed++)
return;
if (!obs->hotkeys.reroute_hotkeys)
hotkey->func(hotkey->data, hotkey->id, hotkey, true);
else if (obs->hotkeys.router_func)
obs->hotkeys.router_func(obs->hotkeys.router_func_data,
hotkey->id, true);
}
static inline void release_pressed_binding(obs_hotkey_binding_t *binding)
{
binding->pressed = false;
obs_hotkey_t *hotkey = binding->hotkey;
if (--hotkey->pressed)
return;
if (!obs->hotkeys.reroute_hotkeys)
hotkey->func(hotkey->data, hotkey->id, hotkey, false);
else if (obs->hotkeys.router_func)
obs->hotkeys.router_func(obs->hotkeys.router_func_data,
hotkey->id, false);
}
static inline void handle_binding(obs_hotkey_binding_t *binding,
uint32_t modifiers, bool no_press,
bool strict_modifiers, bool *pressed)
{
bool modifiers_match_ =
modifiers_match(binding, modifiers, strict_modifiers);
bool modifiers_only = binding->key.key == OBS_KEY_NONE;
if (!binding->key.modifiers)
binding->modifiers_match = true;
if (modifiers_only)
pressed = &modifiers_only;
if (!binding->key.modifiers && modifiers_only)
goto reset;
if ((!binding->modifiers_match && !modifiers_only) || !modifiers_match_)
goto reset;
if ((pressed && !*pressed) ||
(!pressed && !is_pressed(binding->key.key)))
goto reset;
if (binding->pressed || no_press)
return;
press_released_binding(binding);
return;
reset:
binding->modifiers_match = modifiers_match_;
if (!binding->pressed)
return;
release_pressed_binding(binding);
}
struct obs_hotkey_internal_inject {
obs_key_combination_t hotkey;
bool pressed;
bool strict_modifiers;
};
static inline bool inject_hotkey(void *data, size_t idx,
obs_hotkey_binding_t *binding)
{
UNUSED_PARAMETER(idx);
struct obs_hotkey_internal_inject *event = data;
if (modifiers_match(binding, event->hotkey.modifiers,
event->strict_modifiers)) {
bool pressed = binding->key.key == event->hotkey.key &&
event->pressed;
handle_binding(binding, event->hotkey.modifiers, false,
event->strict_modifiers, &pressed);
}
return true;
}
void obs_hotkey_inject_event(obs_key_combination_t hotkey, bool pressed)
{
if (!lock())
return;
struct obs_hotkey_internal_inject event = {
{hotkey.modifiers, hotkey.key},
pressed,
obs->hotkeys.strict_modifiers,
};
enum_bindings(inject_hotkey, &event);
unlock();
}
void obs_hotkey_enable_background_press(bool enable)
{
if (!lock())
return;
obs->hotkeys.thread_disable_press = !enable;
unlock();
}
void obs_hotkey_enable_strict_modifiers(bool enable)
{
if (!lock())
return;
obs->hotkeys.strict_modifiers = enable;
unlock();
}
struct obs_query_hotkeys_helper {
uint32_t modifiers;
bool no_press;
bool strict_modifiers;
};
static inline bool query_hotkey(void *data, size_t idx,
obs_hotkey_binding_t *binding)
{
UNUSED_PARAMETER(idx);
struct obs_query_hotkeys_helper *param =
(struct obs_query_hotkeys_helper *)data;
handle_binding(binding, param->modifiers, param->no_press,
param->strict_modifiers, NULL);
return true;
}
static inline void query_hotkeys()
{
uint32_t modifiers = 0;
if (is_pressed(OBS_KEY_SHIFT))
modifiers |= INTERACT_SHIFT_KEY;
if (is_pressed(OBS_KEY_CONTROL))
modifiers |= INTERACT_CONTROL_KEY;
if (is_pressed(OBS_KEY_ALT))
modifiers |= INTERACT_ALT_KEY;
if (is_pressed(OBS_KEY_META))
modifiers |= INTERACT_COMMAND_KEY;
struct obs_query_hotkeys_helper param = {
modifiers,
obs->hotkeys.thread_disable_press,
obs->hotkeys.strict_modifiers,
};
enum_bindings(query_hotkey, &param);
}
#define NBSP "\xC2\xA0"
void *obs_hotkey_thread(void *arg)
{
UNUSED_PARAMETER(arg);
const char *hotkey_thread_name =
profile_store_name(obs_get_profiler_name_store(),
"obs_hotkey_thread(%g" NBSP "ms)", 25.);
profile_register_root(hotkey_thread_name, (uint64_t)25000000);
while (os_event_timedwait(obs->hotkeys.stop_event, 25) == ETIMEDOUT) {
if (!lock())
continue;
profile_start(hotkey_thread_name);
query_hotkeys();
profile_end(hotkey_thread_name);
unlock();
profile_reenable_thread();
}
return NULL;
}
void obs_hotkey_trigger_routed_callback(obs_hotkey_id id, bool pressed)
{
if (!lock())
return;
if (!obs->hotkeys.reroute_hotkeys)
goto unlock;
size_t idx;
if (!find_id(id, &idx))
goto unlock;
obs_hotkey_t *hotkey = &obs->hotkeys.hotkeys.array[idx];
hotkey->func(hotkey->data, id, hotkey, pressed);
unlock:
unlock();
}
void obs_hotkey_set_callback_routing_func(obs_hotkey_callback_router_func func,
void *data)
{
if (!lock())
return;
obs->hotkeys.router_func = func;
obs->hotkeys.router_func_data = data;
unlock();
}
void obs_hotkey_enable_callback_rerouting(bool enable)
{
if (!lock())
return;
obs->hotkeys.reroute_hotkeys = enable;
unlock();
}
static void obs_set_key_translation(obs_key_t key, const char *translation)
{
bfree(obs->hotkeys.translations[key]);
obs->hotkeys.translations[key] = NULL;
if (translation)
obs->hotkeys.translations[key] = bstrdup(translation);
}
void obs_hotkeys_set_translations_s(
struct obs_hotkeys_translations *translations, size_t size)
{
#define ADD_TRANSLATION(key_name, var_name) \
if (t.var_name) \
obs_set_key_translation(key_name, t.var_name);
struct obs_hotkeys_translations t = {0};
struct dstr numpad = {0};
struct dstr mouse = {0};
struct dstr button = {0};
if (!translations) {
return;
}
memcpy(&t, translations, (size < sizeof(t)) ? size : sizeof(t));
ADD_TRANSLATION(OBS_KEY_INSERT, insert);
ADD_TRANSLATION(OBS_KEY_DELETE, del);
ADD_TRANSLATION(OBS_KEY_HOME, home);
ADD_TRANSLATION(OBS_KEY_END, end);
ADD_TRANSLATION(OBS_KEY_PAGEUP, page_up);
ADD_TRANSLATION(OBS_KEY_PAGEDOWN, page_down);
ADD_TRANSLATION(OBS_KEY_NUMLOCK, num_lock);
ADD_TRANSLATION(OBS_KEY_SCROLLLOCK, scroll_lock);
ADD_TRANSLATION(OBS_KEY_CAPSLOCK, caps_lock);
ADD_TRANSLATION(OBS_KEY_BACKSPACE, backspace);
ADD_TRANSLATION(OBS_KEY_TAB, tab);
ADD_TRANSLATION(OBS_KEY_PRINT, print);
ADD_TRANSLATION(OBS_KEY_PAUSE, pause);
ADD_TRANSLATION(OBS_KEY_SHIFT, shift);
ADD_TRANSLATION(OBS_KEY_ALT, alt);
ADD_TRANSLATION(OBS_KEY_CONTROL, control);
ADD_TRANSLATION(OBS_KEY_META, meta);
ADD_TRANSLATION(OBS_KEY_MENU, menu);
ADD_TRANSLATION(OBS_KEY_SPACE, space);
ADD_TRANSLATION(OBS_KEY_ESCAPE, escape);
#ifdef __APPLE__
const char *numpad_str = t.apple_keypad_num;
ADD_TRANSLATION(OBS_KEY_NUMSLASH, apple_keypad_divide);
ADD_TRANSLATION(OBS_KEY_NUMASTERISK, apple_keypad_multiply);
ADD_TRANSLATION(OBS_KEY_NUMMINUS, apple_keypad_minus);
ADD_TRANSLATION(OBS_KEY_NUMPLUS, apple_keypad_plus);
ADD_TRANSLATION(OBS_KEY_NUMPERIOD, apple_keypad_decimal);
ADD_TRANSLATION(OBS_KEY_NUMEQUAL, apple_keypad_equal);
#else
const char *numpad_str = t.numpad_num;
ADD_TRANSLATION(OBS_KEY_NUMSLASH, numpad_divide);
ADD_TRANSLATION(OBS_KEY_NUMASTERISK, numpad_multiply);
ADD_TRANSLATION(OBS_KEY_NUMMINUS, numpad_minus);
ADD_TRANSLATION(OBS_KEY_NUMPLUS, numpad_plus);
ADD_TRANSLATION(OBS_KEY_NUMPERIOD, numpad_decimal);
#endif
if (numpad_str) {
dstr_copy(&numpad, numpad_str);
dstr_depad(&numpad);
if (dstr_find(&numpad, "%1") == NULL) {
dstr_cat(&numpad, " %1");
}
#define ADD_NUMPAD_NUM(idx) \
dstr_copy_dstr(&button, &numpad); \
dstr_replace(&button, "%1", #idx); \
obs_set_key_translation(OBS_KEY_NUM##idx, button.array)
ADD_NUMPAD_NUM(0);
ADD_NUMPAD_NUM(1);
ADD_NUMPAD_NUM(2);
ADD_NUMPAD_NUM(3);
ADD_NUMPAD_NUM(4);
ADD_NUMPAD_NUM(5);
ADD_NUMPAD_NUM(6);
ADD_NUMPAD_NUM(7);
ADD_NUMPAD_NUM(8);
ADD_NUMPAD_NUM(9);
}
if (t.mouse_num) {
dstr_copy(&mouse, t.mouse_num);
dstr_depad(&mouse);
if (dstr_find(&mouse, "%1") == NULL) {
dstr_cat(&mouse, " %1");
}
#define ADD_MOUSE_NUM(idx) \
dstr_copy_dstr(&button, &mouse); \
dstr_replace(&button, "%1", #idx); \
obs_set_key_translation(OBS_KEY_MOUSE##idx, button.array)
ADD_MOUSE_NUM(1);
ADD_MOUSE_NUM(2);
ADD_MOUSE_NUM(3);
ADD_MOUSE_NUM(4);
ADD_MOUSE_NUM(5);
ADD_MOUSE_NUM(6);
ADD_MOUSE_NUM(7);
ADD_MOUSE_NUM(8);
ADD_MOUSE_NUM(9);
ADD_MOUSE_NUM(10);
ADD_MOUSE_NUM(11);
ADD_MOUSE_NUM(12);
ADD_MOUSE_NUM(13);
ADD_MOUSE_NUM(14);
ADD_MOUSE_NUM(15);
ADD_MOUSE_NUM(16);
ADD_MOUSE_NUM(17);
ADD_MOUSE_NUM(18);
ADD_MOUSE_NUM(19);
ADD_MOUSE_NUM(20);
ADD_MOUSE_NUM(21);
ADD_MOUSE_NUM(22);
ADD_MOUSE_NUM(23);
ADD_MOUSE_NUM(24);
ADD_MOUSE_NUM(25);
ADD_MOUSE_NUM(26);
ADD_MOUSE_NUM(27);
ADD_MOUSE_NUM(28);
ADD_MOUSE_NUM(29);
}
dstr_free(&numpad);
dstr_free(&mouse);
dstr_free(&button);
}
const char *obs_get_hotkey_translation(obs_key_t key, const char *def)
{
if (key == OBS_KEY_NONE) {
return NULL;
}
return obs->hotkeys.translations[key] ? obs->hotkeys.translations[key]
: def;
}
void obs_hotkey_update_atomic(obs_hotkey_atomic_update_func func, void *data)
{
if (!lock())
return;
func(data);
unlock();
}
void obs_hotkeys_set_audio_hotkeys_translations(const char *mute,
const char *unmute,
const char *push_to_mute,
const char *push_to_talk)
{
#define SET_T(n) \
bfree(obs->hotkeys.n); \
obs->hotkeys.n = bstrdup(n)
SET_T(mute);
SET_T(unmute);
SET_T(push_to_mute);
SET_T(push_to_talk);
#undef SET_T
}
void obs_hotkeys_set_sceneitem_hotkeys_translations(const char *show,
const char *hide)
{
#define SET_T(n) \
bfree(obs->hotkeys.sceneitem_##n); \
obs->hotkeys.sceneitem_##n = bstrdup(n)
SET_T(show);
SET_T(hide);
#undef SET_T
}