From 5ad553d06d5022129d735c6416f7f0e35232a58d Mon Sep 17 00:00:00 2001 From: Palana Date: Sat, 1 Nov 2014 21:41:17 +0100 Subject: [PATCH 01/18] libobs: Add global hotkey support --- libobs/CMakeLists.txt | 4 + libobs/obs-cocoa.c | 46 ++ libobs/obs-encoder.c | 2 +- libobs/obs-hotkey-name-map.c | 411 ++++++++++ libobs/obs-hotkey.c | 1497 ++++++++++++++++++++++++++++++++++ libobs/obs-hotkey.h | 281 +++++++ libobs/obs-hotkeys.h | 477 +++++++++++ libobs/obs-internal.h | 96 ++- libobs/obs-nix.c | 51 ++ libobs/obs-output.c | 2 +- libobs/obs-service.c | 2 +- libobs/obs-source.c | 2 +- libobs/obs-windows.c | 52 ++ libobs/obs.c | 101 ++- libobs/obs.h | 1 + 15 files changed, 3015 insertions(+), 10 deletions(-) create mode 100644 libobs/obs-hotkey-name-map.c create mode 100644 libobs/obs-hotkey.c create mode 100644 libobs/obs-hotkey.h create mode 100644 libobs/obs-hotkeys.h diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index 99817f758..0a5af7111 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -216,6 +216,8 @@ set(libobs_libobs_SOURCES obs.c obs-properties.c obs-data.c + obs-hotkey.c + obs-hotkey-name-map.c obs-module.c obs-display.c obs-view.c @@ -233,6 +235,8 @@ set(libobs_libobs_HEADERS obs-properties.h obs-data.h obs-interaction.h + obs-hotkey.h + obs-hotkeys.h obs-module.h obs-scene.h obs-source.h diff --git a/libobs/obs-cocoa.c b/libobs/obs-cocoa.c index 9fbbe5a6a..19d7271e1 100644 --- a/libobs/obs-cocoa.c +++ b/libobs/obs-cocoa.c @@ -182,3 +182,49 @@ void log_system_info(void) log_os(); log_kernel_version(); } + + + + +struct obs_hotkeys_platform { +}; + +void obs_key_to_str(obs_key_t key, struct dstr *str) +{ + UNUSED_PARAMETER(key); + UNUSED_PARAMETER(str); +} + +void obs_key_combination_to_str(obs_key_combination_t key, struct dstr *str) +{ + UNUSED_PARAMETER(key); + UNUSED_PARAMETER(str); +} + +obs_key_t obs_key_from_virtual_key(int code) +{ + UNUSED_PARAMETER(code); + + return OBS_KEY_NONE; +} + +bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) +{ + UNUSED_PARAMETER(hotkeys); + + return true; +} + +void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) +{ + UNUSED_PARAMETER(hotkeys); +} + +bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *plat, + obs_key_t key) +{ + UNUSED_PARAMETER(plat); + UNUSED_PARAMETER(key); + + return false; +} diff --git a/libobs/obs-encoder.c b/libobs/obs-encoder.c index 1b7cff220..2cb815622 100644 --- a/libobs/obs-encoder.c +++ b/libobs/obs-encoder.c @@ -42,7 +42,7 @@ static bool init_encoder(struct obs_encoder *encoder, const char *name, pthread_mutex_init_value(&encoder->callbacks_mutex); pthread_mutex_init_value(&encoder->outputs_mutex); - if (!obs_context_data_init(&encoder->context, settings, name)) + if (!obs_context_data_init(&encoder->context, settings, name, NULL)) return false; if (pthread_mutex_init(&encoder->callbacks_mutex, NULL) != 0) return false; diff --git a/libobs/obs-hotkey-name-map.c b/libobs/obs-hotkey-name-map.c new file mode 100644 index 000000000..3f80f1d28 --- /dev/null +++ b/libobs/obs-hotkey-name-map.c @@ -0,0 +1,411 @@ +/****************************************************************************** + Copyright (C) 2014 by Ruwen Hahn + + 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 . +******************************************************************************/ + +#include +#include + +#include +#include +#include + +#include "obs-internal.h" + +struct obs_hotkey_name_map_edge; +struct obs_hotkey_name_map_node; +struct obs_hotkey_name_map; + +typedef struct obs_hotkey_name_map_edge obs_hotkey_name_map_edge_t; +typedef struct obs_hotkey_name_map_node obs_hotkey_name_map_node_t; +typedef struct obs_hotkey_name_map obs_hotkey_name_map_t; + +struct obs_hotkey_name_map_node { + bool is_leaf; + union { + int val; + DARRAY(obs_hotkey_name_map_edge_t) children; + }; +}; + +struct obs_hotkey_name_map { + obs_hotkey_name_map_node_t root; + obs_hotkey_name_map_node_t *leaves; + size_t num_leaves; + size_t next_leaf; +}; + +struct obs_hotkey_name_map_edge_prefix { + uint8_t prefix_len; + char *prefix; +}; + +#define NAME_MAP_COMPRESS_LENGTH \ + (sizeof(struct obs_hotkey_name_map_edge_prefix) - sizeof(uint8_t)) + +struct obs_hotkey_name_map_edge { + union { + struct { + uint8_t prefix_len; + char *prefix; + }; + struct { + uint8_t compressed_len; + char compressed_prefix[NAME_MAP_COMPRESS_LENGTH]; + }; + }; + struct obs_hotkey_name_map_node *node; +}; + + +static inline obs_hotkey_name_map_node_t *new_node(void) +{ + return bzalloc(sizeof(obs_hotkey_name_map_node_t)); +} + +static inline obs_hotkey_name_map_node_t *new_leaf(void) +{ + obs_hotkey_name_map_t *name_map = obs->hotkeys.name_map; + obs_hotkey_name_map_node_t *node = + &name_map->leaves[name_map->next_leaf]; + name_map->next_leaf += 1; + + node->is_leaf = true; + return node; +} + +static inline char *get_prefix(obs_hotkey_name_map_edge_t *e) +{ + return e->prefix_len >= NAME_MAP_COMPRESS_LENGTH ? + e->prefix : e->compressed_prefix; +} + +static void set_prefix(obs_hotkey_name_map_edge_t *e, const char *prefix, + size_t l) +{ + assert(e->prefix_len == 0); + + e->compressed_len = (uint8_t)l; + if (l < NAME_MAP_COMPRESS_LENGTH) + strncpy(e->compressed_prefix, prefix, l); + else + e->prefix = bstrdup_n(prefix, l); +} + +static obs_hotkey_name_map_edge_t *add_leaf(obs_hotkey_name_map_node_t *node, + const char *key, size_t l, int v) +{ + obs_hotkey_name_map_edge_t *e = da_push_back_new(node->children); + + set_prefix(e, key, l); + + e->node = new_leaf(); + e->node->val = v; + + return e; +} + +static void shrink_prefix(obs_hotkey_name_map_edge_t *e, size_t l) +{ + bool old_comp = e->prefix_len < NAME_MAP_COMPRESS_LENGTH; + bool new_comp = l < NAME_MAP_COMPRESS_LENGTH; + + char *str = get_prefix(e); + + e->prefix_len = (uint8_t)l; + if (get_prefix(e) != str) + strncpy(get_prefix(e), str, l); + else + str[l] = 0; + + if (!old_comp && new_comp) + bfree(str); +} + +static void connect(obs_hotkey_name_map_edge_t *e, + obs_hotkey_name_map_node_t *n) +{ + e->node = n; +} + +static void reduce_edge(obs_hotkey_name_map_edge_t *e, const char *key, + size_t l, int v) +{ + const char *str = get_prefix(e), *str_ = key; + size_t common_length = 0; + while (*str == *str_) { + common_length += 1; + str += 1; + str_ += 1; + } + + obs_hotkey_name_map_node_t *new_node_ = new_node(); + obs_hotkey_name_map_edge_t *tail = + da_push_back_new(new_node_->children); + + connect(tail, e->node); + set_prefix(tail, str, e->prefix_len - common_length); + + add_leaf(new_node_, str_, l - common_length, v); + + connect(e, new_node_); + shrink_prefix(e, common_length); +} + +enum obs_hotkey_name_map_edge_compare_result { + RES_MATCHES, + RES_NO_MATCH, + RES_COMMON_PREFIX, + RES_PREFIX_MATCHES, +}; + +static enum obs_hotkey_name_map_edge_compare_result compare_prefix( + obs_hotkey_name_map_edge_t *edge, const char *key, size_t l) +{ + uint8_t pref_len = edge->prefix_len; + const char *str = get_prefix(edge); + size_t i = 0; + + for (; i < l && i < pref_len; i++) + if (str[i] != key[i]) + break; + + + if (i != 0 && pref_len == i) + return l == i ? RES_MATCHES : RES_PREFIX_MATCHES; + if (i != 0) + return pref_len == i ? RES_PREFIX_MATCHES : RES_COMMON_PREFIX; + return RES_NO_MATCH; +} + +static void insert(obs_hotkey_name_map_edge_t *edge, + obs_hotkey_name_map_node_t *node, + const char *key, size_t l, int v) +{ + if (node->is_leaf && l > 0) { + obs_hotkey_name_map_node_t *new_node_ = new_node(); + connect(edge, new_node_); + + obs_hotkey_name_map_edge_t *edge = + da_push_back_new(new_node_->children); + connect(edge, node); + add_leaf(new_node_, key, l, v); + return; + } + + if (node->is_leaf && l == 0) { + node->val = v; + return; + } + + for (size_t i = 0; i < node->children.num; i++) { + obs_hotkey_name_map_edge_t *e = &node->children.array[i]; + + switch (compare_prefix(e, key, l)) { + case RES_NO_MATCH: + continue; + + case RES_MATCHES: + case RES_PREFIX_MATCHES: + insert(e, e->node, key + e->prefix_len, + l - e->prefix_len, v); + return; + + case RES_COMMON_PREFIX: + reduce_edge(e, key, l, v); + return; + } + } + + add_leaf(node, key, l, v); +} + +static void obs_hotkey_name_map_insert(obs_hotkey_name_map_t *trie, + const char *key, int v) +{ + if (!trie || !key) + return; + + insert(NULL, &trie->root, key, strlen(key), v); +} + +static bool obs_hotkey_name_map_lookup(obs_hotkey_name_map_t *trie, + const char *key, int *v) +{ + if (!trie || !key) + return false; + + size_t len = strlen(key); + obs_hotkey_name_map_node_t *n = &trie->root; + + size_t i = 0; + for (; i < n->children.num;) { + obs_hotkey_name_map_edge_t *e = &n->children.array[i]; + + switch (compare_prefix(e, key, len)) { + case RES_NO_MATCH: + i++; + continue; + + case RES_COMMON_PREFIX: + return false; + + case RES_PREFIX_MATCHES: + key += e->prefix_len; + len -= e->prefix_len; + n = e->node; + i = 0; + continue; + + case RES_MATCHES: + n = e->node; + if (!n->is_leaf) { + for (size_t j = 0; j < n->children.num; j++) { + if (n->children.array[j].prefix_len) + continue; + + if (v) *v = + n->children.array[j].node->val; + return true; + } + return false; + } + + if (v) *v = n->val; + return true; + } + } + + return false; +} + +static void show_node(obs_hotkey_name_map_node_t *node, int in) +{ + if (node->is_leaf) { + printf(": % 3d\n", node->val); + return; + } + + printf("\n"); + for (int i = 0; i < in; i += 2) + printf("| "); + printf("%zu:\n", node->children.num); + + for (size_t i = 0; i < node->children.num; i++) { + for (int i = 0; i < in; i += 2) + printf("| "); + printf("\\ "); + + obs_hotkey_name_map_edge_t *e = &node->children.array[i]; + printf("%s", get_prefix(e)); + show_node(e->node, in+2); + } +} + +void trie_print_size(obs_hotkey_name_map_t *trie) +{ + show_node(&trie->root, 0); +} + +static const char* obs_key_names[] = { +#define OBS_HOTKEY(x) #x, +#include "obs-hotkeys.h" +#undef OBS_HOTKEY +}; + +const char* obs_key_to_name(obs_key_t key) +{ + if (key >= OBS_KEY_LAST_VALUE) { + blog(LOG_ERROR, "obs-hotkey.c: queried unknown key " + "with code %d", (int)key); + return ""; + } + + return obs_key_names[key]; +} + +static obs_key_t obs_key_from_name_fallback(const char *name) +{ +#define OBS_HOTKEY(x) if (strcmp(#x, name) == 0) return x; +#include "obs-hotkeys.h" +#undef OBS_HOTKEY + return OBS_KEY_NONE; +} + +static void init_name_map(void) +{ + obs->hotkeys.name_map = bzalloc(sizeof(struct obs_hotkey_name_map)); + obs_hotkey_name_map_t *name_map = obs->hotkeys.name_map; + +#define OBS_HOTKEY(x) name_map->num_leaves += 1; +#include "obs-hotkeys.h" +#undef OBS_HOTKEY + + size_t size = sizeof(obs_hotkey_name_map_node_t) * name_map->num_leaves; + name_map->leaves = bzalloc(size); + +#define OBS_HOTKEY(x) obs_hotkey_name_map_insert(name_map, #x, x); +#include "obs-hotkeys.h" +#undef OBS_HOTKEY +} + +obs_key_t obs_key_from_name(const char *name) +{ + if (!obs) + return obs_key_from_name_fallback(name); + + if (pthread_once(&obs->hotkeys.name_map_init_token, init_name_map)) + return obs_key_from_name_fallback(name); + + int v = 0; + if (obs_hotkey_name_map_lookup(obs->hotkeys.name_map, name, &v)) + return v; + + return OBS_KEY_NONE; +} + +static void free_node(obs_hotkey_name_map_node_t *node, bool release); + +static void free_edge(obs_hotkey_name_map_edge_t *edge) +{ + free_node(edge->node, true); + + if (edge->prefix_len < NAME_MAP_COMPRESS_LENGTH) + return; + + bfree(get_prefix(edge)); +} + +static void free_node(obs_hotkey_name_map_node_t *node, bool release) +{ + if (!node->is_leaf) { + for (size_t i = 0; i < node->children.num; i++) + free_edge(&node->children.array[i]); + + da_free(node->children); + } + + if (release && !node->is_leaf) bfree(node); +} + +void obs_hotkey_name_map_free(void) +{ + if (!obs || !obs->hotkeys.name_map) + return; + + free_node(&obs->hotkeys.name_map->root, false); + bfree(obs->hotkeys.name_map->leaves); + bfree(obs->hotkeys.name_map); +} diff --git a/libobs/obs-hotkey.c b/libobs/obs-hotkey.c new file mode 100644 index 000000000..d6507c161 --- /dev/null +++ b/libobs/obs-hotkey.c @@ -0,0 +1,1497 @@ +/****************************************************************************** + Copyright (C) 2014-2015 by Ruwen Hahn + + 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 . +******************************************************************************/ + +#include + +#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 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 || !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; +} + +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 : 1; + bool strict_modifiers : 1; +}; + +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 : 1; + bool strict_modifiers : 1; +}; + +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, ¶m); +} + +void *obs_hotkey_thread(void *arg) +{ + UNUSED_PARAMETER(arg); + while (os_event_timedwait(obs->hotkeys.stop_event, 25) == ETIMEDOUT) { + if (!lock()) + continue; + + query_hotkeys(); + unlock(); + } + 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); +#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(); +} diff --git a/libobs/obs-hotkey.h b/libobs/obs-hotkey.h new file mode 100644 index 000000000..8b5f20262 --- /dev/null +++ b/libobs/obs-hotkey.h @@ -0,0 +1,281 @@ +/****************************************************************************** + Copyright (C) 2014-2015 by Ruwen Hahn + + 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 . +******************************************************************************/ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +typedef size_t obs_hotkey_id; +#define OBS_INVALID_HOTKEY_ID (~(obs_hotkey_id)0) +typedef size_t obs_hotkey_pair_id; +#define OBS_INVALID_HOTKEY_PAIR_ID (~(obs_hotkey_pair_id)0) + +enum obs_key { +#define OBS_HOTKEY(x) x, +#include "obs-hotkeys.h" +#undef OBS_HOTKEY + OBS_KEY_LAST_VALUE //not an actual key +}; +typedef enum obs_key obs_key_t; + +struct obs_key_combination { + uint32_t modifiers; + obs_key_t key; +}; +typedef struct obs_key_combination obs_key_combination_t; + +typedef struct obs_hotkey obs_hotkey_t; +typedef struct obs_hotkey_binding obs_hotkey_binding_t; + +enum obs_hotkey_registerer_type { + OBS_HOTKEY_REGISTERER_FRONTEND, + OBS_HOTKEY_REGISTERER_SOURCE, + OBS_HOTKEY_REGISTERER_OUTPUT, + OBS_HOTKEY_REGISTERER_ENCODER, + OBS_HOTKEY_REGISTERER_SERVICE, +}; +typedef enum obs_hotkey_registerer_type obs_hotkey_registerer_t; + +EXPORT obs_hotkey_id obs_hotkey_get_id(const obs_hotkey_t *key); +EXPORT const char *obs_hotkey_get_name(const obs_hotkey_t *key); +EXPORT const char *obs_hotkey_get_description(const obs_hotkey_t *key); +EXPORT obs_hotkey_registerer_t obs_hotkey_get_registerer_type( + const obs_hotkey_t *key); +EXPORT void *obs_hotkey_get_registerer(const obs_hotkey_t *key); +EXPORT obs_hotkey_id obs_hotkey_get_pair_partner_id(const obs_hotkey_t *key); + + +EXPORT obs_key_combination_t obs_hotkey_binding_get_key_combination( + obs_hotkey_binding_t *binding); +EXPORT obs_hotkey_id obs_hotkey_binding_get_hotkey_id( + obs_hotkey_binding_t *binding); +EXPORT obs_hotkey_t *obs_hotkey_binding_get_hotkey( + obs_hotkey_binding_t *binding); + +struct obs_hotkeys_translations { + const char *insert; + const char *del; + const char *home; + const char *end; + const char *page_up; + const char *page_down; + const char *num_lock; + const char *scroll_lock; + const char *caps_lock; + const char *backspace; + const char *tab; + const char *print; + const char *pause; + const char *left; + const char *right; + const char *up; + const char *down; + const char *shift; + const char *alt; + const char *control; + const char *meta; /* windows/super key */ + const char *menu; + const char *space; + const char *numpad_num; /* For example, "Numpad %1" */ + const char *numpad_divide; + const char *numpad_multiply; + const char *numpad_minus; + const char *numpad_plus; + const char *numpad_decimal; + const char *apple_keypad_num; /* For example, "%1 (Keypad)" */ + const char *apple_keypad_divide; + const char *apple_keypad_multiply; + const char *apple_keypad_minus; + const char *apple_keypad_plus; + const char *apple_keypad_decimal; + const char *apple_keypad_equal; + const char *mouse_num; /* For example, "Mouse %1" */ +}; + +/* This function is an optional way to provide translations for specific keys + * that may not have translations. If the operating system can provide + * translations for these keys, it will use the operating system's translation + * over these translations. If no translations are specified, it will use + * the default english translations for that specific operating system. */ +EXPORT void obs_hotkeys_set_translations_s( + struct obs_hotkeys_translations *translations, size_t size); + +#define obs_hotkeys_set_translations(translations) \ + obs_hotkeys_set_translations_s(translations, \ + sizeof(struct obs_hotkeys_translations)) + + +/* registering hotkeys (giving hotkeys a name and a function) */ + +typedef void (*obs_hotkey_func)(void *data, + obs_hotkey_id id, obs_hotkey_t *hotkey, bool pressed); + +EXPORT obs_hotkey_id obs_hotkey_register_frontend(const char *name, + const char *description, obs_hotkey_func func, void *data); + +EXPORT obs_hotkey_id obs_hotkey_register_encoder(obs_encoder_t *encoder, + const char *name, const char *description, + obs_hotkey_func func, void *data); + +EXPORT obs_hotkey_id obs_hotkey_register_output(obs_output_t *output, + const char *name, const char *description, + obs_hotkey_func func, void *data); + +EXPORT obs_hotkey_id obs_hotkey_register_service(obs_service_t *service, + const char *name, const char *description, + obs_hotkey_func func, void *data); + +EXPORT obs_hotkey_id obs_hotkey_register_source(obs_source_t *source, + const char *name, const char *description, + obs_hotkey_func func, void *data); + +typedef bool (*obs_hotkey_active_func)(void *data, + obs_hotkey_pair_id id, obs_hotkey_t *hotkey, bool pressed); + +EXPORT 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); + +EXPORT 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); + +EXPORT 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); + +EXPORT 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); + +EXPORT 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); + +EXPORT void obs_hotkey_unregister(obs_hotkey_id id); + +EXPORT void obs_hotkey_pair_unregister(obs_hotkey_pair_id id); + +/* loading hotkeys (associating a hotkey with a physical key and modifiers) */ + +EXPORT void obs_hotkey_load_bindings(obs_hotkey_id id, + obs_key_combination_t *combinations, size_t num); + +EXPORT void obs_hotkey_load(obs_hotkey_id id, obs_data_array_t *data); + +EXPORT void obs_hotkeys_load_encoder(obs_encoder_t *encoder, + obs_data_t *hotkeys); + +EXPORT void obs_hotkeys_load_output(obs_output_t *output, obs_data_t *hotkeys); + +EXPORT void obs_hotkeys_load_service(obs_service_t *service, + obs_data_t *hotkeys); + +EXPORT void obs_hotkeys_load_source(obs_source_t *source, obs_data_t *hotkeys); + +EXPORT void obs_hotkey_pair_load(obs_hotkey_pair_id id, obs_data_array_t *data0, + obs_data_array_t *data1); + + +EXPORT obs_data_array_t *obs_hotkey_save(obs_hotkey_id id); + +EXPORT obs_data_t *obs_hotkeys_save_encoder(obs_encoder_t *encoder); + +EXPORT obs_data_t *obs_hotkeys_save_output(obs_output_t *output); + +EXPORT obs_data_t *obs_hotkeys_save_service(obs_service_t *service); + +EXPORT obs_data_t *obs_hotkeys_save_source(obs_source_t *source); + +/* enumerating hotkeys */ + +typedef bool (*obs_hotkey_enum_func)(void *data, + obs_hotkey_id id, obs_hotkey_t *key); + +EXPORT void obs_enum_hotkeys(obs_hotkey_enum_func func, void *data); + +/* enumerating bindings */ + +typedef bool (*obs_hotkey_binding_enum_func)(void *data, + size_t idx, obs_hotkey_binding_t* binding); + +EXPORT void obs_enum_hotkey_bindings(obs_hotkey_binding_enum_func func, + void *data); + +/* hotkey event control */ + +EXPORT void obs_hotkey_inject_event(obs_key_combination_t hotkey, bool pressed); + +EXPORT void obs_hotkey_enable_background_press(bool enable); + +EXPORT void obs_hotkey_enable_strict_modifiers(bool enable); + +/* hotkey callback routing (trigger callbacks through e.g. a UI thread) */ + +typedef void (*obs_hotkey_callback_router_func)(void *data, + obs_hotkey_id id, bool pressed); + +EXPORT void obs_hotkey_set_callback_routing_func(obs_hotkey_callback_router_func + func, void *data); + +EXPORT void obs_hotkey_trigger_routed_callback(obs_hotkey_id id, bool pressed); + +/* hotkey callbacks won't be processed if callback rerouting is enabled and no + * router func is set */ +EXPORT void obs_hotkey_enable_callback_rerouting(bool enable); + +/* misc */ + +typedef void (*obs_hotkey_atomic_update_func)(void *); +EXPORT void obs_hotkey_update_atomic(obs_hotkey_atomic_update_func func, + void *data); + +struct dstr; +EXPORT void obs_key_to_str(obs_key_t key, struct dstr *str); +EXPORT void obs_key_combination_to_str(obs_key_combination_t key, + struct dstr *str); + +EXPORT obs_key_t obs_key_from_virtual_key(int code); +EXPORT int obs_key_to_virtual_key(obs_key_t key); + +EXPORT const char *obs_key_to_name(obs_key_t key); +EXPORT obs_key_t obs_key_from_name(const char *name); + +inline bool obs_key_combination_is_empty(obs_key_combination_t combo) +{ + return !combo.modifiers && combo.key == OBS_KEY_NONE; +} + +#ifdef __cplusplus +} +#endif diff --git a/libobs/obs-hotkeys.h b/libobs/obs-hotkeys.h new file mode 100644 index 000000000..74b5e9e25 --- /dev/null +++ b/libobs/obs-hotkeys.h @@ -0,0 +1,477 @@ +OBS_HOTKEY(OBS_KEY_NONE) + +OBS_HOTKEY(OBS_KEY_RETURN) +OBS_HOTKEY(OBS_KEY_ENTER) +OBS_HOTKEY(OBS_KEY_ESCAPE) +OBS_HOTKEY(OBS_KEY_TAB) +OBS_HOTKEY(OBS_KEY_BACKTAB) +OBS_HOTKEY(OBS_KEY_BACKSPACE) +OBS_HOTKEY(OBS_KEY_INSERT) +OBS_HOTKEY(OBS_KEY_DELETE) +OBS_HOTKEY(OBS_KEY_PAUSE) +OBS_HOTKEY(OBS_KEY_PRINT) +OBS_HOTKEY(OBS_KEY_SYSREQ) +OBS_HOTKEY(OBS_KEY_CLEAR) +OBS_HOTKEY(OBS_KEY_HOME) +OBS_HOTKEY(OBS_KEY_END) +OBS_HOTKEY(OBS_KEY_LEFT) +OBS_HOTKEY(OBS_KEY_UP) +OBS_HOTKEY(OBS_KEY_RIGHT) +OBS_HOTKEY(OBS_KEY_DOWN) +OBS_HOTKEY(OBS_KEY_PAGEUP) +OBS_HOTKEY(OBS_KEY_PAGEDOWN) + +OBS_HOTKEY(OBS_KEY_SHIFT) +OBS_HOTKEY(OBS_KEY_CONTROL) +OBS_HOTKEY(OBS_KEY_META) +OBS_HOTKEY(OBS_KEY_ALT) +OBS_HOTKEY(OBS_KEY_ALTGR) +OBS_HOTKEY(OBS_KEY_CAPSLOCK) +OBS_HOTKEY(OBS_KEY_NUMLOCK) +OBS_HOTKEY(OBS_KEY_SCROLLLOCK) + +OBS_HOTKEY(OBS_KEY_F1) +OBS_HOTKEY(OBS_KEY_F2) +OBS_HOTKEY(OBS_KEY_F3) +OBS_HOTKEY(OBS_KEY_F4) +OBS_HOTKEY(OBS_KEY_F5) +OBS_HOTKEY(OBS_KEY_F6) +OBS_HOTKEY(OBS_KEY_F7) +OBS_HOTKEY(OBS_KEY_F8) +OBS_HOTKEY(OBS_KEY_F9) +OBS_HOTKEY(OBS_KEY_F10) +OBS_HOTKEY(OBS_KEY_F11) +OBS_HOTKEY(OBS_KEY_F12) +OBS_HOTKEY(OBS_KEY_F13) +OBS_HOTKEY(OBS_KEY_F14) +OBS_HOTKEY(OBS_KEY_F15) +OBS_HOTKEY(OBS_KEY_F16) +OBS_HOTKEY(OBS_KEY_F17) +OBS_HOTKEY(OBS_KEY_F18) +OBS_HOTKEY(OBS_KEY_F19) +OBS_HOTKEY(OBS_KEY_F20) +OBS_HOTKEY(OBS_KEY_F21) +OBS_HOTKEY(OBS_KEY_F22) +OBS_HOTKEY(OBS_KEY_F23) +OBS_HOTKEY(OBS_KEY_F24) +OBS_HOTKEY(OBS_KEY_F25) +OBS_HOTKEY(OBS_KEY_F26) +OBS_HOTKEY(OBS_KEY_F27) +OBS_HOTKEY(OBS_KEY_F28) +OBS_HOTKEY(OBS_KEY_F29) +OBS_HOTKEY(OBS_KEY_F30) +OBS_HOTKEY(OBS_KEY_F31) +OBS_HOTKEY(OBS_KEY_F32) +OBS_HOTKEY(OBS_KEY_F33) +OBS_HOTKEY(OBS_KEY_F34) +OBS_HOTKEY(OBS_KEY_F35) + +OBS_HOTKEY(OBS_KEY_MENU) +OBS_HOTKEY(OBS_KEY_HYPER_L) +OBS_HOTKEY(OBS_KEY_HYPER_R) +OBS_HOTKEY(OBS_KEY_HELP) +OBS_HOTKEY(OBS_KEY_DIRECTION_L) +OBS_HOTKEY(OBS_KEY_DIRECTION_R) + +OBS_HOTKEY(OBS_KEY_SPACE) +OBS_HOTKEY(OBS_KEY_EXCLAM) +OBS_HOTKEY(OBS_KEY_QUOTEDBL) +OBS_HOTKEY(OBS_KEY_NUMBERSIGN) +OBS_HOTKEY(OBS_KEY_DOLLAR) +OBS_HOTKEY(OBS_KEY_PERCENT) +OBS_HOTKEY(OBS_KEY_AMPERSAND) +OBS_HOTKEY(OBS_KEY_APOSTROPHE) +OBS_HOTKEY(OBS_KEY_PARENLEFT) +OBS_HOTKEY(OBS_KEY_PARENRIGHT) +OBS_HOTKEY(OBS_KEY_ASTERISK) +OBS_HOTKEY(OBS_KEY_PLUS) +OBS_HOTKEY(OBS_KEY_COMMA) +OBS_HOTKEY(OBS_KEY_MINUS) +OBS_HOTKEY(OBS_KEY_PERIOD) +OBS_HOTKEY(OBS_KEY_SLASH) +OBS_HOTKEY(OBS_KEY_0) +OBS_HOTKEY(OBS_KEY_1) +OBS_HOTKEY(OBS_KEY_2) +OBS_HOTKEY(OBS_KEY_3) +OBS_HOTKEY(OBS_KEY_4) +OBS_HOTKEY(OBS_KEY_5) +OBS_HOTKEY(OBS_KEY_6) +OBS_HOTKEY(OBS_KEY_7) +OBS_HOTKEY(OBS_KEY_8) +OBS_HOTKEY(OBS_KEY_9) +OBS_HOTKEY(OBS_KEY_NUMEQUAL) +OBS_HOTKEY(OBS_KEY_NUMASTERISK) +OBS_HOTKEY(OBS_KEY_NUMPLUS) +OBS_HOTKEY(OBS_KEY_NUMCOMMA) +OBS_HOTKEY(OBS_KEY_NUMMINUS) +OBS_HOTKEY(OBS_KEY_NUMPERIOD) +OBS_HOTKEY(OBS_KEY_NUMSLASH) +OBS_HOTKEY(OBS_KEY_NUM0) +OBS_HOTKEY(OBS_KEY_NUM1) +OBS_HOTKEY(OBS_KEY_NUM2) +OBS_HOTKEY(OBS_KEY_NUM3) +OBS_HOTKEY(OBS_KEY_NUM4) +OBS_HOTKEY(OBS_KEY_NUM5) +OBS_HOTKEY(OBS_KEY_NUM6) +OBS_HOTKEY(OBS_KEY_NUM7) +OBS_HOTKEY(OBS_KEY_NUM8) +OBS_HOTKEY(OBS_KEY_NUM9) +OBS_HOTKEY(OBS_KEY_COLON) +OBS_HOTKEY(OBS_KEY_SEMICOLON) +OBS_HOTKEY(OBS_KEY_QUOTE) +OBS_HOTKEY(OBS_KEY_LESS) +OBS_HOTKEY(OBS_KEY_EQUAL) +OBS_HOTKEY(OBS_KEY_GREATER) +OBS_HOTKEY(OBS_KEY_QUESTION) +OBS_HOTKEY(OBS_KEY_AT) +OBS_HOTKEY(OBS_KEY_A) +OBS_HOTKEY(OBS_KEY_B) +OBS_HOTKEY(OBS_KEY_C) +OBS_HOTKEY(OBS_KEY_D) +OBS_HOTKEY(OBS_KEY_E) +OBS_HOTKEY(OBS_KEY_F) +OBS_HOTKEY(OBS_KEY_G) +OBS_HOTKEY(OBS_KEY_H) +OBS_HOTKEY(OBS_KEY_I) +OBS_HOTKEY(OBS_KEY_J) +OBS_HOTKEY(OBS_KEY_K) +OBS_HOTKEY(OBS_KEY_L) +OBS_HOTKEY(OBS_KEY_M) +OBS_HOTKEY(OBS_KEY_N) +OBS_HOTKEY(OBS_KEY_O) +OBS_HOTKEY(OBS_KEY_P) +OBS_HOTKEY(OBS_KEY_Q) +OBS_HOTKEY(OBS_KEY_R) +OBS_HOTKEY(OBS_KEY_S) +OBS_HOTKEY(OBS_KEY_T) +OBS_HOTKEY(OBS_KEY_U) +OBS_HOTKEY(OBS_KEY_V) +OBS_HOTKEY(OBS_KEY_W) +OBS_HOTKEY(OBS_KEY_X) +OBS_HOTKEY(OBS_KEY_Y) +OBS_HOTKEY(OBS_KEY_Z) +OBS_HOTKEY(OBS_KEY_BRACKETLEFT) +OBS_HOTKEY(OBS_KEY_BACKSLASH) +OBS_HOTKEY(OBS_KEY_BRACKETRIGHT) +OBS_HOTKEY(OBS_KEY_ASCIICIRCUM) +OBS_HOTKEY(OBS_KEY_UNDERSCORE) +OBS_HOTKEY(OBS_KEY_QUOTELEFT) +OBS_HOTKEY(OBS_KEY_BRACELEFT) +OBS_HOTKEY(OBS_KEY_BAR) +OBS_HOTKEY(OBS_KEY_BRACERIGHT) +OBS_HOTKEY(OBS_KEY_ASCIITILDE) +OBS_HOTKEY(OBS_KEY_NOBREAKSPACE) +OBS_HOTKEY(OBS_KEY_EXCLAMDOWN) +OBS_HOTKEY(OBS_KEY_CENT) +OBS_HOTKEY(OBS_KEY_STERLING) +OBS_HOTKEY(OBS_KEY_CURRENCY) +OBS_HOTKEY(OBS_KEY_YEN) +OBS_HOTKEY(OBS_KEY_BROKENBAR) +OBS_HOTKEY(OBS_KEY_SECTION) +OBS_HOTKEY(OBS_KEY_DIAERESIS) +OBS_HOTKEY(OBS_KEY_COPYRIGHT) +OBS_HOTKEY(OBS_KEY_ORDFEMININE) +OBS_HOTKEY(OBS_KEY_GUILLEMOTLEFT) +OBS_HOTKEY(OBS_KEY_NOTSIGN) +OBS_HOTKEY(OBS_KEY_HYPHEN) +OBS_HOTKEY(OBS_KEY_REGISTERED) +OBS_HOTKEY(OBS_KEY_MACRON) +OBS_HOTKEY(OBS_KEY_DEGREE) +OBS_HOTKEY(OBS_KEY_PLUSMINUS) +OBS_HOTKEY(OBS_KEY_TWOSUPERIOR) +OBS_HOTKEY(OBS_KEY_THREESUPERIOR) +OBS_HOTKEY(OBS_KEY_ACUTE) +OBS_HOTKEY(OBS_KEY_MU) +OBS_HOTKEY(OBS_KEY_PARAGRAPH) +OBS_HOTKEY(OBS_KEY_PERIODCENTERED) +OBS_HOTKEY(OBS_KEY_CEDILLA) +OBS_HOTKEY(OBS_KEY_ONESUPERIOR) +OBS_HOTKEY(OBS_KEY_MASCULINE) +OBS_HOTKEY(OBS_KEY_GUILLEMOTRIGHT) +OBS_HOTKEY(OBS_KEY_ONEQUARTER) +OBS_HOTKEY(OBS_KEY_ONEHALF) +OBS_HOTKEY(OBS_KEY_THREEQUARTERS) +OBS_HOTKEY(OBS_KEY_QUESTIONDOWN) +OBS_HOTKEY(OBS_KEY_AGRAVE) +OBS_HOTKEY(OBS_KEY_AACUTE) +OBS_HOTKEY(OBS_KEY_ACIRCUMFLEX) +OBS_HOTKEY(OBS_KEY_ATILDE) +OBS_HOTKEY(OBS_KEY_ADIAERESIS) +OBS_HOTKEY(OBS_KEY_ARING) +OBS_HOTKEY(OBS_KEY_AE) +OBS_HOTKEY(OBS_KEY_CCEDILLA) +OBS_HOTKEY(OBS_KEY_EGRAVE) +OBS_HOTKEY(OBS_KEY_EACUTE) +OBS_HOTKEY(OBS_KEY_ECIRCUMFLEX) +OBS_HOTKEY(OBS_KEY_EDIAERESIS) +OBS_HOTKEY(OBS_KEY_IGRAVE) +OBS_HOTKEY(OBS_KEY_IACUTE) +OBS_HOTKEY(OBS_KEY_ICIRCUMFLEX) +OBS_HOTKEY(OBS_KEY_IDIAERESIS) +OBS_HOTKEY(OBS_KEY_ETH) +OBS_HOTKEY(OBS_KEY_NTILDE) +OBS_HOTKEY(OBS_KEY_OGRAVE) +OBS_HOTKEY(OBS_KEY_OACUTE) +OBS_HOTKEY(OBS_KEY_OCIRCUMFLEX) +OBS_HOTKEY(OBS_KEY_OTILDE) +OBS_HOTKEY(OBS_KEY_ODIAERESIS) +OBS_HOTKEY(OBS_KEY_MULTIPLY) +OBS_HOTKEY(OBS_KEY_OOBLIQUE) +OBS_HOTKEY(OBS_KEY_UGRAVE) +OBS_HOTKEY(OBS_KEY_UACUTE) +OBS_HOTKEY(OBS_KEY_UCIRCUMFLEX) +OBS_HOTKEY(OBS_KEY_UDIAERESIS) +OBS_HOTKEY(OBS_KEY_YACUTE) +OBS_HOTKEY(OBS_KEY_THORN) +OBS_HOTKEY(OBS_KEY_SSHARP) +OBS_HOTKEY(OBS_KEY_DIVISION) +OBS_HOTKEY(OBS_KEY_YDIAERESIS) +OBS_HOTKEY(OBS_KEY_MULTI_KEY) +OBS_HOTKEY(OBS_KEY_CODEINPUT) +OBS_HOTKEY(OBS_KEY_SINGLECANDIDATE) +OBS_HOTKEY(OBS_KEY_MULTIPLECANDIDATE) +OBS_HOTKEY(OBS_KEY_PREVIOUSCANDIDATE) +OBS_HOTKEY(OBS_KEY_MODE_SWITCH) +OBS_HOTKEY(OBS_KEY_KANJI) +OBS_HOTKEY(OBS_KEY_MUHENKAN) +OBS_HOTKEY(OBS_KEY_HENKAN) +OBS_HOTKEY(OBS_KEY_ROMAJI) +OBS_HOTKEY(OBS_KEY_HIRAGANA) +OBS_HOTKEY(OBS_KEY_KATAKANA) +OBS_HOTKEY(OBS_KEY_HIRAGANA_KATAKANA) +OBS_HOTKEY(OBS_KEY_ZENKAKU) +OBS_HOTKEY(OBS_KEY_HANKAKU) +OBS_HOTKEY(OBS_KEY_ZENKAKU_HANKAKU) +OBS_HOTKEY(OBS_KEY_TOUROKU) +OBS_HOTKEY(OBS_KEY_MASSYO) +OBS_HOTKEY(OBS_KEY_KANA_LOCK) +OBS_HOTKEY(OBS_KEY_KANA_SHIFT) +OBS_HOTKEY(OBS_KEY_EISU_SHIFT) +OBS_HOTKEY(OBS_KEY_EISU_TOGGLE) +OBS_HOTKEY(OBS_KEY_HANGUL) +OBS_HOTKEY(OBS_KEY_HANGUL_START) +OBS_HOTKEY(OBS_KEY_HANGUL_END) +OBS_HOTKEY(OBS_KEY_HANGUL_HANJA) +OBS_HOTKEY(OBS_KEY_HANGUL_JAMO) +OBS_HOTKEY(OBS_KEY_HANGUL_ROMAJA) +OBS_HOTKEY(OBS_KEY_HANGUL_JEONJA) +OBS_HOTKEY(OBS_KEY_HANGUL_BANJA) +OBS_HOTKEY(OBS_KEY_HANGUL_PREHANJA) +OBS_HOTKEY(OBS_KEY_HANGUL_POSTHANJA) +OBS_HOTKEY(OBS_KEY_HANGUL_SPECIAL) +OBS_HOTKEY(OBS_KEY_DEAD_GRAVE) +OBS_HOTKEY(OBS_KEY_DEAD_ACUTE) +OBS_HOTKEY(OBS_KEY_DEAD_CIRCUMFLEX) +OBS_HOTKEY(OBS_KEY_DEAD_TILDE) +OBS_HOTKEY(OBS_KEY_DEAD_MACRON) +OBS_HOTKEY(OBS_KEY_DEAD_BREVE) +OBS_HOTKEY(OBS_KEY_DEAD_ABOVEDOT) +OBS_HOTKEY(OBS_KEY_DEAD_DIAERESIS) +OBS_HOTKEY(OBS_KEY_DEAD_ABOVERING) +OBS_HOTKEY(OBS_KEY_DEAD_DOUBLEACUTE) +OBS_HOTKEY(OBS_KEY_DEAD_CARON) +OBS_HOTKEY(OBS_KEY_DEAD_CEDILLA) +OBS_HOTKEY(OBS_KEY_DEAD_OGONEK) +OBS_HOTKEY(OBS_KEY_DEAD_IOTA) +OBS_HOTKEY(OBS_KEY_DEAD_VOICED_SOUND) +OBS_HOTKEY(OBS_KEY_DEAD_SEMIVOICED_SOUND) +OBS_HOTKEY(OBS_KEY_DEAD_BELOWDOT) +OBS_HOTKEY(OBS_KEY_DEAD_HOOK) +OBS_HOTKEY(OBS_KEY_DEAD_HORN) +OBS_HOTKEY(OBS_KEY_BACK) +OBS_HOTKEY(OBS_KEY_FORWARD) +OBS_HOTKEY(OBS_KEY_STOP) +OBS_HOTKEY(OBS_KEY_REFRESH) +OBS_HOTKEY(OBS_KEY_VOLUMEDOWN) +OBS_HOTKEY(OBS_KEY_VOLUMEMUTE) +OBS_HOTKEY(OBS_KEY_VOLUMEUP) +OBS_HOTKEY(OBS_KEY_BASSBOOST) +OBS_HOTKEY(OBS_KEY_BASSUP) +OBS_HOTKEY(OBS_KEY_BASSDOWN) +OBS_HOTKEY(OBS_KEY_TREBLEUP) +OBS_HOTKEY(OBS_KEY_TREBLEDOWN) +OBS_HOTKEY(OBS_KEY_MEDIAPLAY) +OBS_HOTKEY(OBS_KEY_MEDIASTOP) +OBS_HOTKEY(OBS_KEY_MEDIAPREVIOUS) +OBS_HOTKEY(OBS_KEY_MEDIANEXT) +OBS_HOTKEY(OBS_KEY_MEDIARECORD) +OBS_HOTKEY(OBS_KEY_MEDIAPAUSE) +OBS_HOTKEY(OBS_KEY_MEDIATOGGLEPLAYPAUSE) +OBS_HOTKEY(OBS_KEY_HOMEPAGE) +OBS_HOTKEY(OBS_KEY_FAVORITES) +OBS_HOTKEY(OBS_KEY_SEARCH) +OBS_HOTKEY(OBS_KEY_STANDBY) +OBS_HOTKEY(OBS_KEY_OPENURL) +OBS_HOTKEY(OBS_KEY_LAUNCHMAIL) +OBS_HOTKEY(OBS_KEY_LAUNCHMEDIA) +OBS_HOTKEY(OBS_KEY_LAUNCH0) +OBS_HOTKEY(OBS_KEY_LAUNCH1) +OBS_HOTKEY(OBS_KEY_LAUNCH2) +OBS_HOTKEY(OBS_KEY_LAUNCH3) +OBS_HOTKEY(OBS_KEY_LAUNCH4) +OBS_HOTKEY(OBS_KEY_LAUNCH5) +OBS_HOTKEY(OBS_KEY_LAUNCH6) +OBS_HOTKEY(OBS_KEY_LAUNCH7) +OBS_HOTKEY(OBS_KEY_LAUNCH8) +OBS_HOTKEY(OBS_KEY_LAUNCH9) +OBS_HOTKEY(OBS_KEY_LAUNCHA) +OBS_HOTKEY(OBS_KEY_LAUNCHB) +OBS_HOTKEY(OBS_KEY_LAUNCHC) +OBS_HOTKEY(OBS_KEY_LAUNCHD) +OBS_HOTKEY(OBS_KEY_LAUNCHE) +OBS_HOTKEY(OBS_KEY_LAUNCHF) +OBS_HOTKEY(OBS_KEY_LAUNCHG) +OBS_HOTKEY(OBS_KEY_LAUNCHH) +OBS_HOTKEY(OBS_KEY_MONBRIGHTNESSUP) +OBS_HOTKEY(OBS_KEY_MONBRIGHTNESSDOWN) +OBS_HOTKEY(OBS_KEY_KEYBOARDLIGHTONOFF) +OBS_HOTKEY(OBS_KEY_KEYBOARDBRIGHTNESSUP) +OBS_HOTKEY(OBS_KEY_KEYBOARDBRIGHTNESSDOWN) +OBS_HOTKEY(OBS_KEY_POWEROFF) +OBS_HOTKEY(OBS_KEY_WAKEUP) +OBS_HOTKEY(OBS_KEY_EJECT) +OBS_HOTKEY(OBS_KEY_SCREENSAVER) +OBS_HOTKEY(OBS_KEY_WWW) +OBS_HOTKEY(OBS_KEY_MEMO) +OBS_HOTKEY(OBS_KEY_LIGHTBULB) +OBS_HOTKEY(OBS_KEY_SHOP) +OBS_HOTKEY(OBS_KEY_HISTORY) +OBS_HOTKEY(OBS_KEY_ADDFAVORITE) +OBS_HOTKEY(OBS_KEY_HOTLINKS) +OBS_HOTKEY(OBS_KEY_BRIGHTNESSADJUST) +OBS_HOTKEY(OBS_KEY_FINANCE) +OBS_HOTKEY(OBS_KEY_COMMUNITY) +OBS_HOTKEY(OBS_KEY_AUDIOREWIND) +OBS_HOTKEY(OBS_KEY_BACKFORWARD) +OBS_HOTKEY(OBS_KEY_APPLICATIONLEFT) +OBS_HOTKEY(OBS_KEY_APPLICATIONRIGHT) +OBS_HOTKEY(OBS_KEY_BOOK) +OBS_HOTKEY(OBS_KEY_CD) +OBS_HOTKEY(OBS_KEY_CALCULATOR) +OBS_HOTKEY(OBS_KEY_TODOLIST) +OBS_HOTKEY(OBS_KEY_CLEARGRAB) +OBS_HOTKEY(OBS_KEY_CLOSE) +OBS_HOTKEY(OBS_KEY_COPY) +OBS_HOTKEY(OBS_KEY_CUT) +OBS_HOTKEY(OBS_KEY_DISPLAY) +OBS_HOTKEY(OBS_KEY_DOS) +OBS_HOTKEY(OBS_KEY_DOCUMENTS) +OBS_HOTKEY(OBS_KEY_EXCEL) +OBS_HOTKEY(OBS_KEY_EXPLORER) +OBS_HOTKEY(OBS_KEY_GAME) +OBS_HOTKEY(OBS_KEY_GO) +OBS_HOTKEY(OBS_KEY_ITOUCH) +OBS_HOTKEY(OBS_KEY_LOGOFF) +OBS_HOTKEY(OBS_KEY_MARKET) +OBS_HOTKEY(OBS_KEY_MEETING) +OBS_HOTKEY(OBS_KEY_MENUKB) +OBS_HOTKEY(OBS_KEY_MENUPB) +OBS_HOTKEY(OBS_KEY_MYSITES) +OBS_HOTKEY(OBS_KEY_NEWS) +OBS_HOTKEY(OBS_KEY_OFFICEHOME) +OBS_HOTKEY(OBS_KEY_OPTION) +OBS_HOTKEY(OBS_KEY_PASTE) +OBS_HOTKEY(OBS_KEY_PHONE) +OBS_HOTKEY(OBS_KEY_CALENDAR) +OBS_HOTKEY(OBS_KEY_REPLY) +OBS_HOTKEY(OBS_KEY_RELOAD) +OBS_HOTKEY(OBS_KEY_ROTATEWINDOWS) +OBS_HOTKEY(OBS_KEY_ROTATIONPB) +OBS_HOTKEY(OBS_KEY_ROTATIONKB) +OBS_HOTKEY(OBS_KEY_SAVE) +OBS_HOTKEY(OBS_KEY_SEND) +OBS_HOTKEY(OBS_KEY_SPELL) +OBS_HOTKEY(OBS_KEY_SPLITSCREEN) +OBS_HOTKEY(OBS_KEY_SUPPORT) +OBS_HOTKEY(OBS_KEY_TASKPANE) +OBS_HOTKEY(OBS_KEY_TERMINAL) +OBS_HOTKEY(OBS_KEY_TOOLS) +OBS_HOTKEY(OBS_KEY_TRAVEL) +OBS_HOTKEY(OBS_KEY_VIDEO) +OBS_HOTKEY(OBS_KEY_WORD) +OBS_HOTKEY(OBS_KEY_XFER) +OBS_HOTKEY(OBS_KEY_ZOOMIN) +OBS_HOTKEY(OBS_KEY_ZOOMOUT) +OBS_HOTKEY(OBS_KEY_AWAY) +OBS_HOTKEY(OBS_KEY_MESSENGER) +OBS_HOTKEY(OBS_KEY_WEBCAM) +OBS_HOTKEY(OBS_KEY_MAILFORWARD) +OBS_HOTKEY(OBS_KEY_PICTURES) +OBS_HOTKEY(OBS_KEY_MUSIC) +OBS_HOTKEY(OBS_KEY_BATTERY) +OBS_HOTKEY(OBS_KEY_BLUETOOTH) +OBS_HOTKEY(OBS_KEY_WLAN) +OBS_HOTKEY(OBS_KEY_UWB) +OBS_HOTKEY(OBS_KEY_AUDIOFORWARD) +OBS_HOTKEY(OBS_KEY_AUDIOREPEAT) +OBS_HOTKEY(OBS_KEY_AUDIORANDOMPLAY) +OBS_HOTKEY(OBS_KEY_SUBTITLE) +OBS_HOTKEY(OBS_KEY_AUDIOCYCLETRACK) +OBS_HOTKEY(OBS_KEY_TIME) +OBS_HOTKEY(OBS_KEY_HIBERNATE) +OBS_HOTKEY(OBS_KEY_VIEW) +OBS_HOTKEY(OBS_KEY_TOPMENU) +OBS_HOTKEY(OBS_KEY_POWERDOWN) +OBS_HOTKEY(OBS_KEY_SUSPEND) +OBS_HOTKEY(OBS_KEY_CONTRASTADJUST) +OBS_HOTKEY(OBS_KEY_MEDIALAST) +OBS_HOTKEY(OBS_KEY_CALL) +OBS_HOTKEY(OBS_KEY_CAMERA) +OBS_HOTKEY(OBS_KEY_CAMERAFOCUS) +OBS_HOTKEY(OBS_KEY_CONTEXT1) +OBS_HOTKEY(OBS_KEY_CONTEXT2) +OBS_HOTKEY(OBS_KEY_CONTEXT3) +OBS_HOTKEY(OBS_KEY_CONTEXT4) +OBS_HOTKEY(OBS_KEY_FLIP) +OBS_HOTKEY(OBS_KEY_HANGUP) +OBS_HOTKEY(OBS_KEY_NO) +OBS_HOTKEY(OBS_KEY_SELECT) +OBS_HOTKEY(OBS_KEY_YES) +OBS_HOTKEY(OBS_KEY_TOGGLECALLHANGUP) +OBS_HOTKEY(OBS_KEY_VOICEDIAL) +OBS_HOTKEY(OBS_KEY_LASTNUMBERREDIAL) +OBS_HOTKEY(OBS_KEY_EXECUTE) +OBS_HOTKEY(OBS_KEY_PRINTER) +OBS_HOTKEY(OBS_KEY_PLAY) +OBS_HOTKEY(OBS_KEY_SLEEP) +OBS_HOTKEY(OBS_KEY_ZOOM) +OBS_HOTKEY(OBS_KEY_CANCEL) + + +#ifndef OBS_MOUSE_BUTTON +#define OBS_MOUSE_BUTTON(x) OBS_HOTKEY(x) +#define OBS_MOUSE_BUTTON_DEFAULT 1 +#endif +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE1) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE2) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE3) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE4) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE5) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE6) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE7) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE8) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE9) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE10) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE11) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE12) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE13) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE14) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE15) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE16) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE17) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE18) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE19) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE20) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE21) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE22) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE23) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE24) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE25) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE26) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE27) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE28) +OBS_MOUSE_BUTTON(OBS_KEY_MOUSE29) +#ifdef OBS_MOUSE_BUTTON_DEFAULT +#undef OBS_MOUSE_BUTTON +#undef OBS_MOUSE_BUTTON_DEFAULT +#endif diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 482c9a32a..4665b9bd4 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -95,6 +95,65 @@ static inline bool check_path(const char *data, const char *path, } +/* ------------------------------------------------------------------------- */ +/* hotkeys */ + +struct obs_hotkey { + obs_hotkey_id id; + char *name; + char *description; + + obs_hotkey_func func; + void *data; + int pressed; + + obs_hotkey_registerer_t registerer_type; + void *registerer; + + obs_hotkey_id pair_partner_id; +}; + +struct obs_hotkey_pair { + obs_hotkey_pair_id pair_id; + obs_hotkey_id id[2]; + obs_hotkey_active_func func[2]; + bool pressed0 : 1; + bool pressed1 : 1; + void *data[2]; +}; + +typedef struct obs_hotkey_pair obs_hotkey_pair_t; + +typedef struct obs_hotkeys_platform obs_hotkeys_platform_t; + +void *obs_hotkey_thread(void *param); + +struct obs_core_hotkeys; +bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys); +void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys); +bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *context, + obs_key_t key); + +const char *obs_get_hotkey_translation(obs_key_t key, const char *def); + +struct obs_context_data; +void obs_hotkeys_context_release(struct obs_context_data *context); + +void obs_hotkeys_free(void); + +struct obs_hotkey_binding { + obs_key_combination_t key; + bool pressed : 1; + bool modifiers_match : 1; + + obs_hotkey_id hotkey_id; + obs_hotkey_t *hotkey; +}; + +struct obs_hotkey_name_map; +void obs_hotkey_name_map_free(void); + + /* ------------------------------------------------------------------------- */ /* views */ @@ -213,6 +272,35 @@ struct obs_core_data { volatile bool valid; }; +/* user hotkeys */ +struct obs_core_hotkeys { + pthread_mutex_t mutex; + DARRAY(obs_hotkey_t) hotkeys; + obs_hotkey_id next_id; + DARRAY(obs_hotkey_pair_t) hotkey_pairs; + obs_hotkey_pair_id next_pair_id; + + pthread_t hotkey_thread; + bool hotkey_thread_initialized; + os_event_t *stop_event; + bool thread_disable_press : 1; + bool strict_modifiers : 1; + bool reroute_hotkeys : 1; + DARRAY(obs_hotkey_binding_t) bindings; + + obs_hotkey_callback_router_func router_func; + void *router_func_data; + + obs_hotkeys_platform_t *platform_context; + + pthread_once_t name_map_init_token; + struct obs_hotkey_name_map *name_map; + + signal_handler_t *signals; + + char *translations[OBS_KEY_LAST_VALUE]; +}; + struct obs_core { struct obs_module *first_module; DARRAY(struct obs_module_path) module_paths; @@ -236,6 +324,7 @@ struct obs_core { struct obs_core_video video; struct obs_core_audio audio; struct obs_core_data data; + struct obs_core_hotkeys hotkeys; }; extern struct obs_core *obs; @@ -253,6 +342,10 @@ struct obs_context_data { signal_handler_t *signals; proc_handler_t *procs; + DARRAY(obs_hotkey_id) hotkeys; + DARRAY(obs_hotkey_pair_id) hotkey_pairs; + obs_data_t *hotkey_data; + DARRAY(char*) rename_cache; pthread_mutex_t rename_cache_mutex; @@ -264,7 +357,8 @@ struct obs_context_data { extern bool obs_context_data_init( struct obs_context_data *context, obs_data_t *settings, - const char *name); + const char *name, + obs_data_t *hotkey_data); extern void obs_context_data_free(struct obs_context_data *context); extern void obs_context_data_insert(struct obs_context_data *context, diff --git a/libobs/obs-nix.c b/libobs/obs-nix.c index 40cc78be1..749fc6987 100644 --- a/libobs/obs-nix.c +++ b/libobs/obs-nix.c @@ -227,3 +227,54 @@ void log_system_info(void) log_distribution_info(); #endif } + + +struct obs_hotkeys_platform { +}; + +bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) +{ + UNUSED_PARAMETER(hotkeys); + + return true; +} + +void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) +{ + UNUSED_PARAMETER(hotkeys); +} + +bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *context, + obs_key_t key) +{ + UNUSED_PARAMETER(context); + UNUSED_PARAMETER(key); + + return false; +} + +void obs_key_to_str(obs_key_t key, struct dstr *str) +{ + UNUSED_PARAMETER(key); + UNUSED_PARAMETER(str); +} + +void obs_key_combination_to_str(obs_key_combination_t key, struct dstr *str) +{ + UNUSED_PARAMETER(key); + UNUSED_PARAMETER(str); +} + +obs_key_t obs_key_from_virtual_key(int code) +{ + UNUSED_PARAMETER(code); + + return OBS_KEY_NONE; +} + +int obs_key_to_virtual_key(obs_key_t key) +{ + UNUSED_PARAMETER(key); + + return 0; +} diff --git a/libobs/obs-output.c b/libobs/obs-output.c index 724ef97e2..2994143f6 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -49,7 +49,7 @@ static const char *output_signals[] = { static bool init_output_handlers(struct obs_output *output, const char *name, obs_data_t *settings) { - if (!obs_context_data_init(&output->context, settings, name)) + if (!obs_context_data_init(&output->context, settings, name, NULL)) return false; signal_handler_add_array(output->context.signals, output_signals); diff --git a/libobs/obs-service.c b/libobs/obs-service.c index 7b63cf4eb..0b41e5335 100644 --- a/libobs/obs-service.c +++ b/libobs/obs-service.c @@ -46,7 +46,7 @@ obs_service_t *obs_service_create(const char *id, const char *name, service = bzalloc(sizeof(struct obs_service)); - if (!obs_context_data_init(&service->context, settings, name)) { + if (!obs_context_data_init(&service->context, settings, name, NULL)) { bfree(service); return NULL; } diff --git a/libobs/obs-source.c b/libobs/obs-source.c index 666dbf89c..888328981 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -96,7 +96,7 @@ static const char *source_signals[] = { bool obs_source_init_context(struct obs_source *source, obs_data_t *settings, const char *name) { - if (!obs_context_data_init(&source->context, settings, name)) + if (!obs_context_data_init(&source->context, settings, name, NULL)) return false; return signal_handler_add_array(source->context.signals, diff --git a/libobs/obs-windows.c b/libobs/obs-windows.c index 2a780d682..c4a2e1425 100644 --- a/libobs/obs-windows.c +++ b/libobs/obs-windows.c @@ -194,3 +194,55 @@ void log_system_info(void) log_available_memory(); log_windows_version(); } + + +struct obs_hotkeys_platform { + bool blank; +}; + +bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) +{ + UNUSED_PARAMETER(hotkeys); + + return true; +} + +void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) +{ + UNUSED_PARAMETER(hotkeys); +} + +bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *context, + obs_key_t key) +{ + UNUSED_PARAMETER(context); + UNUSED_PARAMETER(key); + + return false; +} + +void obs_key_to_str(obs_key_t key, struct dstr *str) +{ + UNUSED_PARAMETER(key); + UNUSED_PARAMETER(str); +} + +void obs_key_combination_to_str(obs_key_combination_t key, struct dstr *str) +{ + UNUSED_PARAMETER(key); + UNUSED_PARAMETER(str); +} + +obs_key_t obs_key_from_virtual_key(int code) +{ + UNUSED_PARAMETER(code); + + return OBS_KEY_NONE; +} + +int obs_key_to_virtual_key(obs_key_t key) +{ + UNUSED_PARAMETER(key); + + return 0; +} diff --git a/libobs/obs.c b/libobs/obs.c index 142d1bbd0..cd69ac848 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -609,6 +609,11 @@ static const char *obs_signals[] = { "void channel_change(int channel, in out ptr source, ptr prev_source)", "void master_volume(in out float volume)", + "void hotkey_layout_change()", + "void hotkey_register(ptr hotkey)", + "void hotkey_unregister(ptr hotkey)", + "void hotkey_bindings_changed(ptr hotkey)", + NULL }; @@ -625,6 +630,69 @@ static inline bool obs_init_handlers(void) return signal_handler_add_array(obs->signals, obs_signals); } +static pthread_once_t obs_pthread_once_init_token = PTHREAD_ONCE_INIT; +static inline bool obs_init_hotkeys(void) +{ + struct obs_core_hotkeys *hotkeys = &obs->hotkeys; + pthread_mutexattr_t attr; + bool success = false; + + assert(hotkeys != NULL); + + da_init(hotkeys->hotkeys); + hotkeys->signals = obs->signals; + hotkeys->name_map_init_token = obs_pthread_once_init_token; + + if (!obs_hotkeys_platform_init(hotkeys)) + return false; + + if (pthread_mutexattr_init(&attr) != 0) + return false; + if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0) + goto fail; + if (pthread_mutex_init(&hotkeys->mutex, &attr) != 0) + goto fail; + + if (os_event_init(&hotkeys->stop_event, OS_EVENT_TYPE_MANUAL) != 0) + goto fail; + if (pthread_create(&hotkeys->hotkey_thread, NULL, + obs_hotkey_thread, NULL)) + goto fail; + + hotkeys->hotkey_thread_initialized = true; + + success = true; + +fail: + pthread_mutexattr_destroy(&attr); + return success; +} + +static inline void stop_hotkeys(void) +{ + struct obs_core_hotkeys *hotkeys = &obs->hotkeys; + void *thread_ret; + + if (hotkeys->hotkey_thread_initialized) { + os_event_signal(hotkeys->stop_event); + pthread_join(hotkeys->hotkey_thread, &thread_ret); + hotkeys->hotkey_thread_initialized = false; + } + + os_event_destroy(hotkeys->stop_event); + obs_hotkeys_free(); +} + +static inline void obs_free_hotkeys(void) +{ + struct obs_core_hotkeys *hotkeys = &obs->hotkeys; + + obs_hotkey_name_map_free(); + + obs_hotkeys_platform_free(hotkeys); + pthread_mutex_destroy(&hotkeys->mutex); +} + extern const struct obs_source_info scene_info; extern void log_system_info(void); @@ -639,6 +707,8 @@ static bool obs_init(const char *locale) return false; if (!obs_init_handlers()) return false; + if (!obs_init_hotkeys()) + return false; obs->locale = bstrdup(locale); obs_register_source(&scene_info); @@ -687,9 +757,11 @@ void obs_shutdown(void) da_free(obs->modeless_ui_callbacks); stop_video(); + stop_hotkeys(); obs_free_data(); obs_free_video(); + obs_free_hotkeys(); obs_free_graphics(); obs_free_audio(); proc_handler_destroy(obs->procs); @@ -1347,6 +1419,7 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data, const char *name = obs_data_get_string(source_data, "name"); const char *id = obs_data_get_string(source_data, "id"); obs_data_t *settings = obs_data_get_obj(source_data, "settings"); + obs_data_t *hotkeys = obs_data_get_obj(source_data, "hotkeys"); double volume; int64_t sync; uint32_t flags; @@ -1354,6 +1427,10 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data, source = obs_source_create(type, id, name, settings); + obs_data_release(source->context.hotkey_data); + source->context.hotkey_data = hotkeys; + obs_hotkeys_load_source(source, hotkeys); + obs_data_set_default_double(source_data, "volume", 1.0); volume = obs_data_get_double(source_data, "volume"); obs_source_set_volume(source, (float)volume); @@ -1439,6 +1516,8 @@ obs_data_t *obs_save_source(obs_source_t *source) obs_data_array_t *filters = obs_data_array_create(); obs_data_t *source_data = obs_data_create(); obs_data_t *settings = obs_source_get_settings(source); + obs_data_t *hotkey_data = source->context.hotkey_data; + obs_data_t *hotkeys; float volume = obs_source_get_volume(source); uint32_t mixers = obs_source_get_audio_mixers(source); int64_t sync = obs_source_get_sync_offset(source); @@ -1449,6 +1528,13 @@ obs_data_t *obs_save_source(obs_source_t *source) bool muted = obs_source_muted(source); obs_source_save(source); + hotkeys = obs_hotkeys_save_source(source); + + if (hotkeys) { + obs_data_release(hotkey_data); + source->context.hotkey_data = hotkeys; + hotkey_data = hotkeys; + } obs_data_set_string(source_data, "name", name); obs_data_set_string(source_data, "id", id); @@ -1459,6 +1545,7 @@ obs_data_t *obs_save_source(obs_source_t *source) obs_data_set_double(source_data, "volume", volume); obs_data_set_bool (source_data, "enabled", enabled); obs_data_set_bool (source_data, "muted", muted); + obs_data_set_obj (source_data, "hotkeys", hotkey_data); pthread_mutex_lock(&source->filter_mutex); @@ -1522,7 +1609,8 @@ static inline char *dup_name(const char *name) static inline bool obs_context_data_init_wrap( struct obs_context_data *context, obs_data_t *settings, - const char *name) + const char *name, + obs_data_t *hotkey_data) { assert(context); memset(context, 0, sizeof(*context)); @@ -1539,17 +1627,19 @@ static inline bool obs_context_data_init_wrap( if (!context->procs) return false; - context->name = dup_name(name); - context->settings = obs_data_newref(settings); + context->name = dup_name(name); + context->settings = obs_data_newref(settings); + context->hotkey_data = obs_data_newref(hotkey_data); return true; } bool obs_context_data_init( struct obs_context_data *context, obs_data_t *settings, - const char *name) + const char *name, + obs_data_t *hotkey_data) { - if (obs_context_data_init_wrap(context, settings, name)) { + if (obs_context_data_init_wrap(context, settings, name, hotkey_data)) { return true; } else { obs_context_data_free(context); @@ -1559,6 +1649,7 @@ bool obs_context_data_init( void obs_context_data_free(struct obs_context_data *context) { + obs_hotkeys_context_release(context); signal_handler_destroy(context->signals); proc_handler_destroy(context->procs); obs_data_release(context->settings); diff --git a/libobs/obs.h b/libobs/obs.h index 230895709..690b3aa06 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -72,6 +72,7 @@ typedef struct obs_weak_service obs_weak_service_t; #include "obs-output.h" #include "obs-service.h" #include "obs-audio-controls.h" +#include "obs-hotkey.h" /** * @file From f37e72205a1283643c5a63a1c27ef82fb93f9165 Mon Sep 17 00:00:00 2001 From: Palana Date: Sat, 1 Nov 2014 21:41:17 +0100 Subject: [PATCH 02/18] libobs: Add OSX hotkey implementation --- libobs/CMakeLists.txt | 17 +- libobs/obs-cocoa.c | 1115 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 1121 insertions(+), 11 deletions(-) diff --git a/libobs/CMakeLists.txt b/libobs/CMakeLists.txt index 0a5af7111..16f82dcee 100644 --- a/libobs/CMakeLists.txt +++ b/libobs/CMakeLists.txt @@ -69,8 +69,23 @@ elseif(APPLE) mark_as_advanced(COCOA) include_directories(${COCOA}) + find_library(APPKIT AppKit) + mark_as_advanced(APPKIT) + include_directories(${APPKIT}) + + find_library(IOKIT IOKit) + mark_as_advanced(IOKIT) + include_directories(${IOKIT}) + + find_library(CARBON Carbon) + mark_as_advanced(CARBON) + include_directories(${CARBON}) + set(libobs_PLATFORM_DEPS - ${COCOA}) + ${COCOA} + ${APPKIT} + ${IOKIT} + ${CARBON}) elseif(UNIX) set(libobs_PLATFORM_SOURCES obs-nix.c diff --git a/libobs/obs-cocoa.c b/libobs/obs-cocoa.c index 19d7271e1..27871357a 100644 --- a/libobs/obs-cocoa.c +++ b/libobs/obs-cocoa.c @@ -25,6 +25,9 @@ #include #include +#include +#include +#include const char *get_module_extension(void) { @@ -184,47 +187,1139 @@ void log_system_info(void) } +static bool dstr_from_cfstring(struct dstr *str, CFStringRef ref) +{ + CFIndex length = CFStringGetLength(ref); + CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, + kCFStringEncodingUTF8); + dstr_reserve(str, max_size); + if (!CFStringGetCString(ref, str->array, max_size, + kCFStringEncodingUTF8)) + return false; + + str->len = strlen(str->array); + return true; +} struct obs_hotkeys_platform { + volatile long refs; + CFDataRef layout_data; + UCKeyboardLayout *layout; + IOHIDManagerRef manager; + DARRAY(IOHIDElementRef) keys[OBS_KEY_LAST_VALUE]; }; +static void hotkeys_retain(struct obs_hotkeys_platform *plat) +{ + os_atomic_inc_long(&plat->refs); +} + +static inline void free_hotkeys_platform(obs_hotkeys_platform_t *plat); +static void hotkeys_release(struct obs_hotkeys_platform *plat) +{ + if (os_atomic_dec_long(&plat->refs) == -1) + free_hotkeys_platform(plat); +} + +#define INVALID_KEY 0xff + +int obs_key_to_virtual_key(obs_key_t code) +{ + switch (code) { + case OBS_KEY_A: return kVK_ANSI_A; + case OBS_KEY_B: return kVK_ANSI_B; + case OBS_KEY_C: return kVK_ANSI_C; + case OBS_KEY_D: return kVK_ANSI_D; + case OBS_KEY_E: return kVK_ANSI_E; + case OBS_KEY_F: return kVK_ANSI_F; + case OBS_KEY_G: return kVK_ANSI_G; + case OBS_KEY_H: return kVK_ANSI_H; + case OBS_KEY_I: return kVK_ANSI_I; + case OBS_KEY_J: return kVK_ANSI_J; + case OBS_KEY_K: return kVK_ANSI_K; + case OBS_KEY_L: return kVK_ANSI_L; + case OBS_KEY_M: return kVK_ANSI_M; + case OBS_KEY_N: return kVK_ANSI_N; + case OBS_KEY_O: return kVK_ANSI_O; + case OBS_KEY_P: return kVK_ANSI_P; + case OBS_KEY_Q: return kVK_ANSI_Q; + case OBS_KEY_R: return kVK_ANSI_R; + case OBS_KEY_S: return kVK_ANSI_S; + case OBS_KEY_T: return kVK_ANSI_T; + case OBS_KEY_U: return kVK_ANSI_U; + case OBS_KEY_V: return kVK_ANSI_V; + case OBS_KEY_W: return kVK_ANSI_W; + case OBS_KEY_X: return kVK_ANSI_X; + case OBS_KEY_Y: return kVK_ANSI_Y; + case OBS_KEY_Z: return kVK_ANSI_Z; + + case OBS_KEY_1: return kVK_ANSI_1; + case OBS_KEY_2: return kVK_ANSI_2; + case OBS_KEY_3: return kVK_ANSI_3; + case OBS_KEY_4: return kVK_ANSI_4; + case OBS_KEY_5: return kVK_ANSI_5; + case OBS_KEY_6: return kVK_ANSI_6; + case OBS_KEY_7: return kVK_ANSI_7; + case OBS_KEY_8: return kVK_ANSI_8; + case OBS_KEY_9: return kVK_ANSI_9; + case OBS_KEY_0: return kVK_ANSI_0; + + case OBS_KEY_RETURN: return kVK_Return; + case OBS_KEY_ESCAPE: return kVK_Escape; + case OBS_KEY_BACKSPACE: return kVK_Delete; + case OBS_KEY_TAB: return kVK_Tab; + case OBS_KEY_SPACE: return kVK_Space; + case OBS_KEY_MINUS: return kVK_ANSI_Minus; + case OBS_KEY_EQUAL: return kVK_ANSI_Equal; + case OBS_KEY_BRACKETLEFT: return kVK_ANSI_LeftBracket; + case OBS_KEY_BRACKETRIGHT: return kVK_ANSI_RightBracket; + case OBS_KEY_BACKSLASH: return kVK_ANSI_Backslash; + case OBS_KEY_SEMICOLON: return kVK_ANSI_Semicolon; + case OBS_KEY_QUOTE: return kVK_ANSI_Quote; + case OBS_KEY_DEAD_GRAVE: return kVK_ANSI_Grave; + case OBS_KEY_COMMA: return kVK_ANSI_Comma; + case OBS_KEY_PERIOD: return kVK_ANSI_Period; + case OBS_KEY_SLASH: return kVK_ANSI_Slash; + case OBS_KEY_CAPSLOCK: return kVK_CapsLock; + case OBS_KEY_SECTION: return kVK_ISO_Section; + + case OBS_KEY_F1: return kVK_F1; + case OBS_KEY_F2: return kVK_F2; + case OBS_KEY_F3: return kVK_F3; + case OBS_KEY_F4: return kVK_F4; + case OBS_KEY_F5: return kVK_F5; + case OBS_KEY_F6: return kVK_F6; + case OBS_KEY_F7: return kVK_F7; + case OBS_KEY_F8: return kVK_F8; + case OBS_KEY_F9: return kVK_F9; + case OBS_KEY_F10: return kVK_F10; + case OBS_KEY_F11: return kVK_F11; + case OBS_KEY_F12: return kVK_F12; + + case OBS_KEY_HELP: return kVK_Help; + case OBS_KEY_HOME: return kVK_Home; + case OBS_KEY_PAGEUP: return kVK_PageUp; + case OBS_KEY_DELETE: return kVK_ForwardDelete; + case OBS_KEY_END: return kVK_End; + case OBS_KEY_PAGEDOWN: return kVK_PageDown; + + case OBS_KEY_RIGHT: return kVK_RightArrow; + case OBS_KEY_LEFT: return kVK_LeftArrow; + case OBS_KEY_DOWN: return kVK_DownArrow; + case OBS_KEY_UP: return kVK_UpArrow; + + case OBS_KEY_CLEAR: return kVK_ANSI_KeypadClear; + case OBS_KEY_NUMSLASH: return kVK_ANSI_KeypadDivide; + case OBS_KEY_NUMASTERISK: return kVK_ANSI_KeypadMultiply; + case OBS_KEY_NUMMINUS: return kVK_ANSI_KeypadMinus; + case OBS_KEY_NUMPLUS: return kVK_ANSI_KeypadPlus; + case OBS_KEY_ENTER: return kVK_ANSI_KeypadEnter; + + case OBS_KEY_NUM1: return kVK_ANSI_Keypad1; + case OBS_KEY_NUM2: return kVK_ANSI_Keypad2; + case OBS_KEY_NUM3: return kVK_ANSI_Keypad3; + case OBS_KEY_NUM4: return kVK_ANSI_Keypad4; + case OBS_KEY_NUM5: return kVK_ANSI_Keypad5; + case OBS_KEY_NUM6: return kVK_ANSI_Keypad6; + case OBS_KEY_NUM7: return kVK_ANSI_Keypad7; + case OBS_KEY_NUM8: return kVK_ANSI_Keypad8; + case OBS_KEY_NUM9: return kVK_ANSI_Keypad9; + case OBS_KEY_NUM0: return kVK_ANSI_Keypad0; + + case OBS_KEY_NUMPERIOD: return kVK_ANSI_KeypadDecimal; + case OBS_KEY_NUMEQUAL: return kVK_ANSI_KeypadEquals; + + case OBS_KEY_F13: return kVK_F13; + case OBS_KEY_F14: return kVK_F14; + case OBS_KEY_F15: return kVK_F15; + case OBS_KEY_F16: return kVK_F16; + case OBS_KEY_F17: return kVK_F17; + case OBS_KEY_F18: return kVK_F18; + case OBS_KEY_F19: return kVK_F19; + case OBS_KEY_F20: return kVK_F20; + + case OBS_KEY_CONTROL: return kVK_Control; + case OBS_KEY_SHIFT: return kVK_Shift; + case OBS_KEY_ALT: return kVK_Option; + case OBS_KEY_META: return kVK_Command; + //case OBS_KEY_CONTROL: return kVK_RightControl; + //case OBS_KEY_SHIFT: return kVK_RightShift; + //case OBS_KEY_ALT: return kVK_RightOption; + //case OBS_KEY_META: return 0x36; + + case OBS_KEY_NONE: + case OBS_KEY_LAST_VALUE: + default: + break; + } + return INVALID_KEY; +} + +static bool localized_key_to_str(obs_key_t key, struct dstr *str) +{ +#define MAP_KEY(k, s) case k: \ + dstr_copy(str, obs_get_hotkey_translation(k, s)); \ + return true + +#define MAP_BUTTON(i) case OBS_KEY_MOUSE ## i: \ + dstr_copy(str, obs_get_hotkey_translation(key, "Mouse " #i)); \ + return true + + switch (key) { + MAP_KEY(OBS_KEY_SPACE, "Space"); + MAP_KEY(OBS_KEY_NUMEQUAL, "= (Keypad)"); + MAP_KEY(OBS_KEY_NUMASTERISK, "* (Keypad)"); + MAP_KEY(OBS_KEY_NUMPLUS, "+ (Keypad)"); + MAP_KEY(OBS_KEY_NUMMINUS, "- (Keypad)"); + MAP_KEY(OBS_KEY_NUMPERIOD, ". (Keypad)"); + MAP_KEY(OBS_KEY_NUMSLASH, "/ (Keypad)"); + MAP_KEY(OBS_KEY_NUM0, "0 (Keypad)"); + MAP_KEY(OBS_KEY_NUM1, "1 (Keypad)"); + MAP_KEY(OBS_KEY_NUM2, "2 (Keypad)"); + MAP_KEY(OBS_KEY_NUM3, "3 (Keypad)"); + MAP_KEY(OBS_KEY_NUM4, "4 (Keypad)"); + MAP_KEY(OBS_KEY_NUM5, "5 (Keypad)"); + MAP_KEY(OBS_KEY_NUM6, "6 (Keypad)"); + MAP_KEY(OBS_KEY_NUM7, "7 (Keypad)"); + MAP_KEY(OBS_KEY_NUM8, "8 (Keypad)"); + MAP_KEY(OBS_KEY_NUM9, "9 (Keypad)"); + + MAP_BUTTON(1); + MAP_BUTTON(2); + MAP_BUTTON(3); + MAP_BUTTON(4); + MAP_BUTTON(5); + MAP_BUTTON(6); + MAP_BUTTON(7); + MAP_BUTTON(8); + MAP_BUTTON(9); + MAP_BUTTON(10); + MAP_BUTTON(11); + MAP_BUTTON(12); + MAP_BUTTON(13); + MAP_BUTTON(14); + MAP_BUTTON(15); + MAP_BUTTON(16); + MAP_BUTTON(17); + MAP_BUTTON(18); + MAP_BUTTON(19); + MAP_BUTTON(20); + MAP_BUTTON(21); + MAP_BUTTON(22); + MAP_BUTTON(23); + MAP_BUTTON(24); + MAP_BUTTON(25); + MAP_BUTTON(26); + MAP_BUTTON(27); + MAP_BUTTON(28); + MAP_BUTTON(29); + default: break; + } +#undef MAP_BUTTON +#undef MAP_KEY + + return false; +} + +static bool code_to_str(int code, struct dstr *str) +{ +#define MAP_GLYPH(c, g) \ + case c: dstr_from_wcs(str, (wchar_t[]){g, 0}); return true +#define MAP_STR(c, s) case c: dstr_copy(str, s); return true + switch (code) { + MAP_GLYPH(kVK_Return, 0x21A9); + MAP_GLYPH(kVK_Escape, 0x238B); + MAP_GLYPH(kVK_Delete, 0x232B); + MAP_GLYPH(kVK_Tab, 0x21e5); + MAP_GLYPH(kVK_CapsLock, 0x21EA); + MAP_GLYPH(kVK_ANSI_KeypadClear, 0x2327); + MAP_GLYPH(kVK_ANSI_KeypadEnter, 0x2305); + MAP_GLYPH(kVK_Help, 0x003F); + MAP_GLYPH(kVK_Home, 0x2196); + MAP_GLYPH(kVK_PageUp, 0x21de); + MAP_GLYPH(kVK_ForwardDelete, 0x2326); + MAP_GLYPH(kVK_End, 0x2198); + MAP_GLYPH(kVK_PageDown, 0x21df); + + MAP_GLYPH(kVK_RightArrow, 0x2192); + MAP_GLYPH(kVK_LeftArrow, 0x2190); + MAP_GLYPH(kVK_DownArrow, 0x2193); + MAP_GLYPH(kVK_UpArrow, 0x2191); + + MAP_STR (kVK_F1, "F1"); + MAP_STR (kVK_F2, "F2"); + MAP_STR (kVK_F3, "F3"); + MAP_STR (kVK_F4, "F4"); + MAP_STR (kVK_F5, "F5"); + MAP_STR (kVK_F6, "F6"); + MAP_STR (kVK_F7, "F7"); + MAP_STR (kVK_F8, "F8"); + MAP_STR (kVK_F9, "F9"); + MAP_STR (kVK_F10, "F10"); + MAP_STR (kVK_F11, "F11"); + MAP_STR (kVK_F12, "F12"); + MAP_STR (kVK_F13, "F13"); + MAP_STR (kVK_F14, "F14"); + MAP_STR (kVK_F15, "F15"); + MAP_STR (kVK_F16, "F16"); + MAP_STR (kVK_F17, "F17"); + MAP_STR (kVK_F18, "F18"); + MAP_STR (kVK_F19, "F19"); + MAP_STR (kVK_F20, "F20"); + MAP_GLYPH(kVK_Control, kControlUnicode); + MAP_GLYPH(kVK_Shift, kShiftUnicode); + MAP_GLYPH(kVK_Option, kOptionUnicode); + MAP_GLYPH(kVK_Command, kCommandUnicode); + MAP_GLYPH(kVK_RightControl, kControlUnicode); + MAP_GLYPH(kVK_RightShift, kShiftUnicode); + MAP_GLYPH(kVK_RightOption, kOptionUnicode); + } +#undef MAP_STR +#undef MAP_GLYPH + + return false; +} + void obs_key_to_str(obs_key_t key, struct dstr *str) { - UNUSED_PARAMETER(key); - UNUSED_PARAMETER(str); + if (localized_key_to_str(key, str)) + return; + + int code = obs_key_to_virtual_key(key); + if (code_to_str(code, str)) + return; + + if (code == INVALID_KEY) { + blog(LOG_ERROR, "hotkey-cocoa: Got invalid key while " + "translating key '%d' (%s)", + key, obs_key_to_name(key)); + goto err; + } + + struct obs_hotkeys_platform *plat = NULL; + + if (obs) { + pthread_mutex_lock(&obs->hotkeys.mutex); + plat = obs->hotkeys.platform_context; + hotkeys_retain(plat); + pthread_mutex_unlock(&obs->hotkeys.mutex); + } + + if (!plat) { + blog(LOG_ERROR, "hotkey-cocoa: Could not get hotkey platform " + "while translating key '%d' (%s)", + key, obs_key_to_name(key)); + goto err; + } + + const UniCharCount max_length = 16; + UInt32 dead_key_state = 0; + UniChar buffer[max_length]; + UniCharCount len = 0; + + OSStatus err = UCKeyTranslate(plat->layout, + code, + kUCKeyActionDown, + 0x104, //caps lock for upper case letters + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &dead_key_state, + max_length, + &len, + buffer); + + if (err == noErr && len <= 0 && dead_key_state) { + err = UCKeyTranslate(plat->layout, + kVK_Space, + kUCKeyActionDown, + 0x104, + LMGetKbdType(), + kUCKeyTranslateNoDeadKeysBit, + &dead_key_state, + max_length, + &len, + buffer); + } + + hotkeys_release(plat); + + if (err != noErr) { + blog(LOG_ERROR, "hotkey-cocoa: Error while translating key '%d'" + " (0x%x, %s) to string: %d", key, code, + obs_key_to_name(key), err); + goto err; + } + + if (len == 0) { + blog(LOG_ERROR, "hotkey-cocoa: Got 0 length string while " + "translating '%d' (0x%x, %s) to string", + key, code, obs_key_to_name(key)); + goto err; + } + + CFStringRef string = CFStringCreateWithCharactersNoCopy(NULL, + buffer, len, kCFAllocatorNull); + if (!string) { + blog(LOG_ERROR, "hotkey-cocoa: Could not create CFStringRef " + "while translating '%d' (0x%x, %s) to string", + key, code, obs_key_to_name(key)); + goto err; + } + + if (!dstr_from_cfstring(str, string)) { + blog(LOG_ERROR, "hotkey-cocoa: Could not translate CFStringRef " + "to CString while translating '%d' (0x%x, %s)", + key, code, obs_key_to_name(key)); + + goto release; + } + + CFRelease(string); + return; + +release: + CFRelease(string); +err: + dstr_copy(str, obs_key_to_name(key)); } +#define OBS_COCOA_MODIFIER_SIZE 7 +static void unichar_to_utf8(const UniChar *c, char *buff) +{ + CFStringRef string = CFStringCreateWithCharactersNoCopy(NULL, c, 2, + kCFAllocatorNull); + if (!string) { + blog(LOG_ERROR, "hotkey-cocoa: Could not create CFStringRef " + "while populating modifier strings"); + return; + } + + if (!CFStringGetCString(string, buff, OBS_COCOA_MODIFIER_SIZE, + kCFStringEncodingUTF8)) + blog(LOG_ERROR, "hotkey-cocoa: Error while populating " + " modifier string with glyph %d (0x%x)", + c[0], c[0]); + + CFRelease(string); +} + +static char ctrl_str[OBS_COCOA_MODIFIER_SIZE]; +static char opt_str[OBS_COCOA_MODIFIER_SIZE]; +static char shift_str[OBS_COCOA_MODIFIER_SIZE]; +static char cmd_str[OBS_COCOA_MODIFIER_SIZE]; +static void init_utf_8_strings(void) +{ + const UniChar ctrl_uni[] = {kControlUnicode, 0}; + const UniChar opt_uni[] = {kOptionUnicode, 0}; + const UniChar shift_uni[] = {kShiftUnicode, 0}; + const UniChar cmd_uni[] = {kCommandUnicode, 0}; + + unichar_to_utf8(ctrl_uni, ctrl_str); + unichar_to_utf8(opt_uni, opt_str); + unichar_to_utf8(shift_uni, shift_str); + unichar_to_utf8(cmd_uni, cmd_str); +} + +static pthread_once_t strings_token = PTHREAD_ONCE_INIT; void obs_key_combination_to_str(obs_key_combination_t key, struct dstr *str) { - UNUSED_PARAMETER(key); - UNUSED_PARAMETER(str); + struct dstr key_str = {0}; + if (key.key != OBS_KEY_NONE) + obs_key_to_str(key.key, &key_str); + + int res = pthread_once(&strings_token, init_utf_8_strings); + if (res) { + blog(LOG_ERROR, "hotkeys-cocoa: Error while translating " + "modifiers %d (0x%x)", res, res); + dstr_move(str, &key_str); + return; + } + +#define CHECK_MODIFIER(mod, str) ((key.modifiers & mod) ? str : "") + dstr_printf(str, "%s%s%s%s%s", + CHECK_MODIFIER(INTERACT_CONTROL_KEY, ctrl_str), + CHECK_MODIFIER(INTERACT_ALT_KEY, opt_str), + CHECK_MODIFIER(INTERACT_SHIFT_KEY, shift_str), + CHECK_MODIFIER(INTERACT_COMMAND_KEY, cmd_str), + key_str.len ? key_str.array : ""); +#undef CHECK_MODIFIER + + dstr_free(&key_str); +} + +static inline CFDictionaryRef copy_device_mask(UInt32 page, UInt32 usage) +{ + CFMutableDictionaryRef dict = CFDictionaryCreateMutable( + kCFAllocatorDefault, 2, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + + CFNumberRef value; + // Add the page value. + value = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &page); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsagePageKey), value); + CFRelease(value); + + // Add the usage value (which is only valid if page value exists). + value = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage); + CFDictionarySetValue(dict, CFSTR(kIOHIDDeviceUsageKey), value); + CFRelease(value); + + return dict; +} + +static CFSetRef copy_devices(obs_hotkeys_platform_t *plat, + UInt32 page, UInt32 usage) +{ + CFDictionaryRef mask = copy_device_mask(page, usage); + IOHIDManagerSetDeviceMatching(plat->manager, mask); + CFRelease(mask); + + CFSetRef devices = IOHIDManagerCopyDevices(plat->manager); + if (!devices) + return NULL; + + if (CFSetGetCount(devices) < 1) { + CFRelease(devices); + return NULL; + } + + return devices; +} + +static UInt16 usage_to_carbon(UInt32 usage) +{ + switch (usage) + { + case kHIDUsage_KeyboardErrorRollOver: return INVALID_KEY; + case kHIDUsage_KeyboardPOSTFail: return INVALID_KEY; + case kHIDUsage_KeyboardErrorUndefined: return INVALID_KEY; + + case kHIDUsage_KeyboardA: return kVK_ANSI_A; + case kHIDUsage_KeyboardB: return kVK_ANSI_B; + case kHIDUsage_KeyboardC: return kVK_ANSI_C; + case kHIDUsage_KeyboardD: return kVK_ANSI_D; + case kHIDUsage_KeyboardE: return kVK_ANSI_E; + case kHIDUsage_KeyboardF: return kVK_ANSI_F; + case kHIDUsage_KeyboardG: return kVK_ANSI_G; + case kHIDUsage_KeyboardH: return kVK_ANSI_H; + case kHIDUsage_KeyboardI: return kVK_ANSI_I; + case kHIDUsage_KeyboardJ: return kVK_ANSI_J; + case kHIDUsage_KeyboardK: return kVK_ANSI_K; + case kHIDUsage_KeyboardL: return kVK_ANSI_L; + case kHIDUsage_KeyboardM: return kVK_ANSI_M; + case kHIDUsage_KeyboardN: return kVK_ANSI_N; + case kHIDUsage_KeyboardO: return kVK_ANSI_O; + case kHIDUsage_KeyboardP: return kVK_ANSI_P; + case kHIDUsage_KeyboardQ: return kVK_ANSI_Q; + case kHIDUsage_KeyboardR: return kVK_ANSI_R; + case kHIDUsage_KeyboardS: return kVK_ANSI_S; + case kHIDUsage_KeyboardT: return kVK_ANSI_T; + case kHIDUsage_KeyboardU: return kVK_ANSI_U; + case kHIDUsage_KeyboardV: return kVK_ANSI_V; + case kHIDUsage_KeyboardW: return kVK_ANSI_W; + case kHIDUsage_KeyboardX: return kVK_ANSI_X; + case kHIDUsage_KeyboardY: return kVK_ANSI_Y; + case kHIDUsage_KeyboardZ: return kVK_ANSI_Z; + + case kHIDUsage_Keyboard1: return kVK_ANSI_1; + case kHIDUsage_Keyboard2: return kVK_ANSI_2; + case kHIDUsage_Keyboard3: return kVK_ANSI_3; + case kHIDUsage_Keyboard4: return kVK_ANSI_4; + case kHIDUsage_Keyboard5: return kVK_ANSI_5; + case kHIDUsage_Keyboard6: return kVK_ANSI_6; + case kHIDUsage_Keyboard7: return kVK_ANSI_7; + case kHIDUsage_Keyboard8: return kVK_ANSI_8; + case kHIDUsage_Keyboard9: return kVK_ANSI_9; + case kHIDUsage_Keyboard0: return kVK_ANSI_0; + + case kHIDUsage_KeyboardReturnOrEnter: return kVK_Return; + case kHIDUsage_KeyboardEscape: return kVK_Escape; + case kHIDUsage_KeyboardDeleteOrBackspace: return kVK_Delete; + case kHIDUsage_KeyboardTab: return kVK_Tab; + case kHIDUsage_KeyboardSpacebar: return kVK_Space; + case kHIDUsage_KeyboardHyphen: return kVK_ANSI_Minus; + case kHIDUsage_KeyboardEqualSign: return kVK_ANSI_Equal; + case kHIDUsage_KeyboardOpenBracket: return kVK_ANSI_LeftBracket; + case kHIDUsage_KeyboardCloseBracket: return kVK_ANSI_RightBracket; + case kHIDUsage_KeyboardBackslash: return kVK_ANSI_Backslash; + case kHIDUsage_KeyboardNonUSPound: return INVALID_KEY; + case kHIDUsage_KeyboardSemicolon: return kVK_ANSI_Semicolon; + case kHIDUsage_KeyboardQuote: return kVK_ANSI_Quote; + case kHIDUsage_KeyboardGraveAccentAndTilde: return kVK_ANSI_Grave; + case kHIDUsage_KeyboardComma: return kVK_ANSI_Comma; + case kHIDUsage_KeyboardPeriod: return kVK_ANSI_Period; + case kHIDUsage_KeyboardSlash: return kVK_ANSI_Slash; + case kHIDUsage_KeyboardCapsLock: return kVK_CapsLock; + + case kHIDUsage_KeyboardF1: return kVK_F1; + case kHIDUsage_KeyboardF2: return kVK_F2; + case kHIDUsage_KeyboardF3: return kVK_F3; + case kHIDUsage_KeyboardF4: return kVK_F4; + case kHIDUsage_KeyboardF5: return kVK_F5; + case kHIDUsage_KeyboardF6: return kVK_F6; + case kHIDUsage_KeyboardF7: return kVK_F7; + case kHIDUsage_KeyboardF8: return kVK_F8; + case kHIDUsage_KeyboardF9: return kVK_F9; + case kHIDUsage_KeyboardF10: return kVK_F10; + case kHIDUsage_KeyboardF11: return kVK_F11; + case kHIDUsage_KeyboardF12: return kVK_F12; + + case kHIDUsage_KeyboardPrintScreen: return INVALID_KEY; + case kHIDUsage_KeyboardScrollLock: return INVALID_KEY; + case kHIDUsage_KeyboardPause: return INVALID_KEY; + case kHIDUsage_KeyboardInsert: return kVK_Help; + case kHIDUsage_KeyboardHome: return kVK_Home; + case kHIDUsage_KeyboardPageUp: return kVK_PageUp; + case kHIDUsage_KeyboardDeleteForward: return kVK_ForwardDelete; + case kHIDUsage_KeyboardEnd: return kVK_End; + case kHIDUsage_KeyboardPageDown: return kVK_PageDown; + + case kHIDUsage_KeyboardRightArrow: return kVK_RightArrow; + case kHIDUsage_KeyboardLeftArrow: return kVK_LeftArrow; + case kHIDUsage_KeyboardDownArrow: return kVK_DownArrow; + case kHIDUsage_KeyboardUpArrow: return kVK_UpArrow; + + case kHIDUsage_KeypadNumLock: return kVK_ANSI_KeypadClear; + case kHIDUsage_KeypadSlash: return kVK_ANSI_KeypadDivide; + case kHIDUsage_KeypadAsterisk: return kVK_ANSI_KeypadMultiply; + case kHIDUsage_KeypadHyphen: return kVK_ANSI_KeypadMinus; + case kHIDUsage_KeypadPlus: return kVK_ANSI_KeypadPlus; + case kHIDUsage_KeypadEnter: return kVK_ANSI_KeypadEnter; + + case kHIDUsage_Keypad1: return kVK_ANSI_Keypad1; + case kHIDUsage_Keypad2: return kVK_ANSI_Keypad2; + case kHIDUsage_Keypad3: return kVK_ANSI_Keypad3; + case kHIDUsage_Keypad4: return kVK_ANSI_Keypad4; + case kHIDUsage_Keypad5: return kVK_ANSI_Keypad5; + case kHIDUsage_Keypad6: return kVK_ANSI_Keypad6; + case kHIDUsage_Keypad7: return kVK_ANSI_Keypad7; + case kHIDUsage_Keypad8: return kVK_ANSI_Keypad8; + case kHIDUsage_Keypad9: return kVK_ANSI_Keypad9; + case kHIDUsage_Keypad0: return kVK_ANSI_Keypad0; + + case kHIDUsage_KeypadPeriod: return kVK_ANSI_KeypadDecimal; + case kHIDUsage_KeyboardNonUSBackslash: return INVALID_KEY; + case kHIDUsage_KeyboardApplication: return kVK_F13; + case kHIDUsage_KeyboardPower: return INVALID_KEY; + case kHIDUsage_KeypadEqualSign: return kVK_ANSI_KeypadEquals; + + case kHIDUsage_KeyboardF13: return kVK_F13; + case kHIDUsage_KeyboardF14: return kVK_F14; + case kHIDUsage_KeyboardF15: return kVK_F15; + case kHIDUsage_KeyboardF16: return kVK_F16; + case kHIDUsage_KeyboardF17: return kVK_F17; + case kHIDUsage_KeyboardF18: return kVK_F18; + case kHIDUsage_KeyboardF19: return kVK_F19; + case kHIDUsage_KeyboardF20: return kVK_F20; + case kHIDUsage_KeyboardF21: return INVALID_KEY; + case kHIDUsage_KeyboardF22: return INVALID_KEY; + case kHIDUsage_KeyboardF23: return INVALID_KEY; + case kHIDUsage_KeyboardF24: return INVALID_KEY; + + case kHIDUsage_KeyboardExecute: return INVALID_KEY; + case kHIDUsage_KeyboardHelp: return INVALID_KEY; + case kHIDUsage_KeyboardMenu: return 0x7F; + case kHIDUsage_KeyboardSelect: return kVK_ANSI_KeypadEnter; + case kHIDUsage_KeyboardStop: return INVALID_KEY; + case kHIDUsage_KeyboardAgain: return INVALID_KEY; + case kHIDUsage_KeyboardUndo: return INVALID_KEY; + case kHIDUsage_KeyboardCut: return INVALID_KEY; + case kHIDUsage_KeyboardCopy: return INVALID_KEY; + case kHIDUsage_KeyboardPaste: return INVALID_KEY; + case kHIDUsage_KeyboardFind: return INVALID_KEY; + + case kHIDUsage_KeyboardMute: return kVK_Mute; + case kHIDUsage_KeyboardVolumeUp: return kVK_VolumeUp; + case kHIDUsage_KeyboardVolumeDown: return kVK_VolumeDown; + + case kHIDUsage_KeyboardLockingCapsLock: return INVALID_KEY; + case kHIDUsage_KeyboardLockingNumLock: return INVALID_KEY; + case kHIDUsage_KeyboardLockingScrollLock: return INVALID_KEY; + + case kHIDUsage_KeypadComma: return INVALID_KEY; + case kHIDUsage_KeypadEqualSignAS400: return INVALID_KEY; + case kHIDUsage_KeyboardInternational1: return INVALID_KEY; + case kHIDUsage_KeyboardInternational2: return INVALID_KEY; + case kHIDUsage_KeyboardInternational3: return INVALID_KEY; + case kHIDUsage_KeyboardInternational4: return INVALID_KEY; + case kHIDUsage_KeyboardInternational5: return INVALID_KEY; + case kHIDUsage_KeyboardInternational6: return INVALID_KEY; + case kHIDUsage_KeyboardInternational7: return INVALID_KEY; + case kHIDUsage_KeyboardInternational8: return INVALID_KEY; + case kHIDUsage_KeyboardInternational9: return INVALID_KEY; + + case kHIDUsage_KeyboardLANG1: return INVALID_KEY; + case kHIDUsage_KeyboardLANG2: return INVALID_KEY; + case kHIDUsage_KeyboardLANG3: return INVALID_KEY; + case kHIDUsage_KeyboardLANG4: return INVALID_KEY; + case kHIDUsage_KeyboardLANG5: return INVALID_KEY; + case kHIDUsage_KeyboardLANG6: return INVALID_KEY; + case kHIDUsage_KeyboardLANG7: return INVALID_KEY; + case kHIDUsage_KeyboardLANG8: return INVALID_KEY; + case kHIDUsage_KeyboardLANG9: return INVALID_KEY; + + case kHIDUsage_KeyboardAlternateErase: return INVALID_KEY; + case kHIDUsage_KeyboardSysReqOrAttention: return INVALID_KEY; + case kHIDUsage_KeyboardCancel: return INVALID_KEY; + case kHIDUsage_KeyboardClear: return INVALID_KEY; + case kHIDUsage_KeyboardPrior: return INVALID_KEY; + case kHIDUsage_KeyboardReturn: return INVALID_KEY; + case kHIDUsage_KeyboardSeparator: return INVALID_KEY; + case kHIDUsage_KeyboardOut: return INVALID_KEY; + case kHIDUsage_KeyboardOper: return INVALID_KEY; + case kHIDUsage_KeyboardClearOrAgain: return INVALID_KEY; + case kHIDUsage_KeyboardCrSelOrProps: return INVALID_KEY; + case kHIDUsage_KeyboardExSel: return INVALID_KEY; + + /* 0xa5-0xdf Reserved */ + + case kHIDUsage_KeyboardLeftControl: return kVK_Control; + case kHIDUsage_KeyboardLeftShift: return kVK_Shift; + case kHIDUsage_KeyboardLeftAlt: return kVK_Option; + case kHIDUsage_KeyboardLeftGUI: return kVK_Command; + case kHIDUsage_KeyboardRightControl: return kVK_RightControl; + case kHIDUsage_KeyboardRightShift: return kVK_RightShift; + case kHIDUsage_KeyboardRightAlt: return kVK_RightOption; + case kHIDUsage_KeyboardRightGUI: return 0x36; //?? + + /* 0xe8-0xffff Reserved */ + + case kHIDUsage_Keyboard_Reserved: return INVALID_KEY; + default: return INVALID_KEY; + } + return INVALID_KEY; } obs_key_t obs_key_from_virtual_key(int code) { - UNUSED_PARAMETER(code); + switch (code) { + case kVK_ANSI_A: return OBS_KEY_A; + case kVK_ANSI_B: return OBS_KEY_B; + case kVK_ANSI_C: return OBS_KEY_C; + case kVK_ANSI_D: return OBS_KEY_D; + case kVK_ANSI_E: return OBS_KEY_E; + case kVK_ANSI_F: return OBS_KEY_F; + case kVK_ANSI_G: return OBS_KEY_G; + case kVK_ANSI_H: return OBS_KEY_H; + case kVK_ANSI_I: return OBS_KEY_I; + case kVK_ANSI_J: return OBS_KEY_J; + case kVK_ANSI_K: return OBS_KEY_K; + case kVK_ANSI_L: return OBS_KEY_L; + case kVK_ANSI_M: return OBS_KEY_M; + case kVK_ANSI_N: return OBS_KEY_N; + case kVK_ANSI_O: return OBS_KEY_O; + case kVK_ANSI_P: return OBS_KEY_P; + case kVK_ANSI_Q: return OBS_KEY_Q; + case kVK_ANSI_R: return OBS_KEY_R; + case kVK_ANSI_S: return OBS_KEY_S; + case kVK_ANSI_T: return OBS_KEY_T; + case kVK_ANSI_U: return OBS_KEY_U; + case kVK_ANSI_V: return OBS_KEY_V; + case kVK_ANSI_W: return OBS_KEY_W; + case kVK_ANSI_X: return OBS_KEY_X; + case kVK_ANSI_Y: return OBS_KEY_Y; + case kVK_ANSI_Z: return OBS_KEY_Z; + case kVK_ANSI_1: return OBS_KEY_1; + case kVK_ANSI_2: return OBS_KEY_2; + case kVK_ANSI_3: return OBS_KEY_3; + case kVK_ANSI_4: return OBS_KEY_4; + case kVK_ANSI_5: return OBS_KEY_5; + case kVK_ANSI_6: return OBS_KEY_6; + case kVK_ANSI_7: return OBS_KEY_7; + case kVK_ANSI_8: return OBS_KEY_8; + case kVK_ANSI_9: return OBS_KEY_9; + case kVK_ANSI_0: return OBS_KEY_0; + + case kVK_Return: return OBS_KEY_RETURN; + case kVK_Escape: return OBS_KEY_ESCAPE; + case kVK_Delete: return OBS_KEY_BACKSPACE; + case kVK_Tab: return OBS_KEY_TAB; + case kVK_Space: return OBS_KEY_SPACE; + case kVK_ANSI_Minus: return OBS_KEY_MINUS; + case kVK_ANSI_Equal: return OBS_KEY_EQUAL; + case kVK_ANSI_LeftBracket: return OBS_KEY_BRACKETLEFT; + case kVK_ANSI_RightBracket: return OBS_KEY_BRACKETRIGHT; + case kVK_ANSI_Backslash: return OBS_KEY_BACKSLASH; + case kVK_ANSI_Semicolon: return OBS_KEY_SEMICOLON; + case kVK_ANSI_Quote: return OBS_KEY_QUOTE; + case kVK_ANSI_Grave: return OBS_KEY_DEAD_GRAVE; + case kVK_ANSI_Comma: return OBS_KEY_COMMA; + case kVK_ANSI_Period: return OBS_KEY_PERIOD; + case kVK_ANSI_Slash: return OBS_KEY_SLASH; + case kVK_CapsLock: return OBS_KEY_CAPSLOCK; + case kVK_ISO_Section: return OBS_KEY_SECTION; + + case kVK_F1: return OBS_KEY_F1; + case kVK_F2: return OBS_KEY_F2; + case kVK_F3: return OBS_KEY_F3; + case kVK_F4: return OBS_KEY_F4; + case kVK_F5: return OBS_KEY_F5; + case kVK_F6: return OBS_KEY_F6; + case kVK_F7: return OBS_KEY_F7; + case kVK_F8: return OBS_KEY_F8; + case kVK_F9: return OBS_KEY_F9; + case kVK_F10: return OBS_KEY_F10; + case kVK_F11: return OBS_KEY_F11; + case kVK_F12: return OBS_KEY_F12; + + case kVK_Help: return OBS_KEY_HELP; + case kVK_Home: return OBS_KEY_HOME; + case kVK_PageUp: return OBS_KEY_PAGEUP; + case kVK_ForwardDelete: return OBS_KEY_DELETE; + case kVK_End: return OBS_KEY_END; + case kVK_PageDown: return OBS_KEY_PAGEDOWN; + + case kVK_RightArrow: return OBS_KEY_RIGHT; + case kVK_LeftArrow: return OBS_KEY_LEFT; + case kVK_DownArrow: return OBS_KEY_DOWN; + case kVK_UpArrow: return OBS_KEY_UP; + + case kVK_ANSI_KeypadClear: return OBS_KEY_CLEAR; + case kVK_ANSI_KeypadDivide: return OBS_KEY_NUMSLASH; + case kVK_ANSI_KeypadMultiply: return OBS_KEY_NUMASTERISK; + case kVK_ANSI_KeypadMinus: return OBS_KEY_NUMMINUS; + case kVK_ANSI_KeypadPlus: return OBS_KEY_NUMPLUS; + case kVK_ANSI_KeypadEnter: return OBS_KEY_ENTER; + + case kVK_ANSI_Keypad1: return OBS_KEY_NUM1; + case kVK_ANSI_Keypad2: return OBS_KEY_NUM2; + case kVK_ANSI_Keypad3: return OBS_KEY_NUM3; + case kVK_ANSI_Keypad4: return OBS_KEY_NUM4; + case kVK_ANSI_Keypad5: return OBS_KEY_NUM5; + case kVK_ANSI_Keypad6: return OBS_KEY_NUM6; + case kVK_ANSI_Keypad7: return OBS_KEY_NUM7; + case kVK_ANSI_Keypad8: return OBS_KEY_NUM8; + case kVK_ANSI_Keypad9: return OBS_KEY_NUM9; + case kVK_ANSI_Keypad0: return OBS_KEY_NUM0; + + case kVK_ANSI_KeypadDecimal: return OBS_KEY_NUMPERIOD; + case kVK_ANSI_KeypadEquals: return OBS_KEY_NUMEQUAL; + + case kVK_F13: return OBS_KEY_F13; + case kVK_F14: return OBS_KEY_F14; + case kVK_F15: return OBS_KEY_F15; + case kVK_F16: return OBS_KEY_F16; + case kVK_F17: return OBS_KEY_F17; + case kVK_F18: return OBS_KEY_F18; + case kVK_F19: return OBS_KEY_F19; + case kVK_F20: return OBS_KEY_F20; + + case kVK_Control: return OBS_KEY_CONTROL; + case kVK_Shift: return OBS_KEY_SHIFT; + case kVK_Option: return OBS_KEY_ALT; + case kVK_Command: return OBS_KEY_META; + case kVK_RightControl: return OBS_KEY_CONTROL; + case kVK_RightShift: return OBS_KEY_SHIFT; + case kVK_RightOption: return OBS_KEY_ALT; + case 0x36: return OBS_KEY_META; + + case kVK_Function: + case kVK_Mute: + case kVK_VolumeDown: + case kVK_VolumeUp: + break; + } return OBS_KEY_NONE; } +static inline void load_key(obs_hotkeys_platform_t *plat, IOHIDElementRef key) +{ + UInt32 usage_code = IOHIDElementGetUsage(key); + UInt16 carbon_code = usage_to_carbon(usage_code); + + if (carbon_code == INVALID_KEY) return; + + obs_key_t obs_key = obs_key_from_virtual_key(carbon_code); + if (obs_key == OBS_KEY_NONE) + return; + + da_push_back(plat->keys[obs_key], &key); + CFRetain(*(IOHIDElementRef*)da_end(plat->keys[obs_key])); +} + +static inline void load_keyboard(obs_hotkeys_platform_t *plat, + IOHIDDeviceRef keyboard) +{ + CFArrayRef keys = IOHIDDeviceCopyMatchingElements(keyboard, NULL, + kIOHIDOptionsTypeNone); + + if (!keys) { + blog(LOG_ERROR, "hotkeys-cocoa: Getting keyboard keys failed"); + return; + } + + CFIndex count = CFArrayGetCount(keys); + if (!count) { + blog(LOG_ERROR, "hotkeys-cocoa: Keyboard has no keys"); + CFRelease(keys); + return; + } + + for (CFIndex i = 0; i < count; i++) { + IOHIDElementRef key = + (IOHIDElementRef)CFArrayGetValueAtIndex(keys, i); + + // Skip non-matching keys elements + if (IOHIDElementGetUsagePage(key) != kHIDPage_KeyboardOrKeypad) + continue; + + load_key(plat, key); + } + + CFRelease(keys); +} + +static bool init_keyboard(obs_hotkeys_platform_t *plat) +{ + CFSetRef keyboards = copy_devices(plat, kHIDPage_GenericDesktop, + kHIDUsage_GD_Keyboard); + if (!keyboards) + return false; + + CFIndex count = CFSetGetCount(keyboards); + + CFTypeRef devices[count]; + CFSetGetValues(keyboards, devices); + + for (CFIndex i = 0; i < count; i++) + load_keyboard(plat, (IOHIDDeviceRef)devices[i]); + + CFRelease(keyboards); + return true; +} + +static inline void free_hotkeys_platform(obs_hotkeys_platform_t *plat) +{ + if (!plat) + return; + + if (plat->layout_data) { + CFRelease(plat->layout_data); + plat->layout_data = NULL; + } + + if (plat->manager) { + CFRelease(plat->manager); + plat->manager = NULL; + } + + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { + for (size_t j = 0; j < plat->keys[i].num; j++) + CFRelease(plat->keys[i].array[j]); + + da_free(plat->keys[i]); + } + + bfree(plat); +} + +static bool log_layout_name(TISInputSourceRef tis) +{ + struct dstr layout_name = {0}; + CFStringRef sid = (CFStringRef)TISGetInputSourceProperty(tis, + kTISPropertyInputSourceID); + if (!sid) { + blog(LOG_ERROR, "hotkeys-cocoa: Failed getting InputSourceID"); + return false; + } + + if (!dstr_from_cfstring(&layout_name, sid)) { + blog(LOG_ERROR, "hotkeys-cocoa: Could not convert InputSourceID" + " to CString"); + goto fail; + } + + blog(LOG_INFO, "hotkeys-cocoa: Using layout '%s'", layout_name.array); + + dstr_free(&layout_name); + return true; + +fail: + dstr_free(&layout_name); + return false; +} + +static bool init_hotkeys_platform(obs_hotkeys_platform_t **plat_) +{ + if (!plat_) + return false; + + *plat_ = bzalloc(sizeof(obs_hotkeys_platform_t)); + obs_hotkeys_platform_t *plat = *plat_; + if (!plat) { + *plat_ = NULL; + return false; + } + + TISInputSourceRef tis = TISCopyCurrentKeyboardLayoutInputSource(); + plat->layout_data = (CFDataRef)TISGetInputSourceProperty(tis, + kTISPropertyUnicodeKeyLayoutData); + + if (!plat->layout_data) { + blog(LOG_ERROR, "hotkeys-cocoa: Failed getting LayoutData"); + goto fail; + } + + log_layout_name(tis); + + CFRetain(plat->layout_data); + plat->layout = (UCKeyboardLayout*)CFDataGetBytePtr(plat->layout_data); + + plat->manager = IOHIDManagerCreate(kCFAllocatorDefault, + kIOHIDOptionsTypeNone); + + IOReturn openStatus = IOHIDManagerOpen(plat->manager, + kIOHIDOptionsTypeNone); + if (openStatus != kIOReturnSuccess) { + blog(LOG_ERROR, "hotkeys-cocoa: Failed opening HIDManager"); + goto fail; + } + + init_keyboard(plat); + + CFRelease(tis); + return true; + +fail: + CFRelease(tis); + hotkeys_release(plat); + *plat_ = NULL; + return false; +} + +static void input_method_changed(CFNotificationCenterRef nc, void *observer, + CFStringRef name, const void *object, CFDictionaryRef user_info) +{ + UNUSED_PARAMETER(nc); + UNUSED_PARAMETER(name); + UNUSED_PARAMETER(object); + UNUSED_PARAMETER(user_info); + + struct obs_core_hotkeys *hotkeys = observer; + obs_hotkeys_platform_t *new_plat; + + if (init_hotkeys_platform(&new_plat)) { + obs_hotkeys_platform_t *plat; + + pthread_mutex_lock(&hotkeys->mutex); + plat = hotkeys->platform_context; + hotkeys->platform_context = new_plat; + pthread_mutex_unlock(&hotkeys->mutex); + + calldata_t params = {0}; + signal_handler_signal(hotkeys->signals, + "hotkey_layout_change", ¶ms); + if (plat) + hotkeys_release(plat); + } +} + + bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) { - UNUSED_PARAMETER(hotkeys); + CFNotificationCenterAddObserver( + CFNotificationCenterGetDistributedCenter(), + hotkeys, input_method_changed, + kTISNotifySelectedKeyboardInputSourceChanged, NULL, + CFNotificationSuspensionBehaviorDeliverImmediately); - return true; + input_method_changed(NULL, hotkeys, NULL, NULL, NULL); + return hotkeys->platform_context != NULL; } void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) { - UNUSED_PARAMETER(hotkeys); + CFNotificationCenterRemoveEveryObserver( + CFNotificationCenterGetDistributedCenter(), + hotkeys); + + hotkeys_release(hotkeys->platform_context); +} + +typedef unsigned long NSUInteger; +static bool mouse_button_pressed(obs_key_t key, bool *pressed) +{ + int button = 0; + switch (key) { +#define MAP_BUTTON(n) case OBS_KEY_MOUSE ## n: button = n - 1; break + MAP_BUTTON(1); + MAP_BUTTON(2); + MAP_BUTTON(3); + MAP_BUTTON(4); + MAP_BUTTON(5); + MAP_BUTTON(6); + MAP_BUTTON(7); + MAP_BUTTON(8); + MAP_BUTTON(9); + MAP_BUTTON(10); + MAP_BUTTON(11); + MAP_BUTTON(12); + MAP_BUTTON(13); + MAP_BUTTON(14); + MAP_BUTTON(15); + MAP_BUTTON(16); + MAP_BUTTON(17); + MAP_BUTTON(18); + MAP_BUTTON(19); + MAP_BUTTON(20); + MAP_BUTTON(21); + MAP_BUTTON(22); + MAP_BUTTON(23); + MAP_BUTTON(24); + MAP_BUTTON(25); + MAP_BUTTON(26); + MAP_BUTTON(27); + MAP_BUTTON(28); + MAP_BUTTON(29); + break; +#undef MAP_BUTTON + + default: + return false; + } + + Class NSEvent = objc_getClass("NSEvent"); + SEL pressedMouseButtons = sel_registerName("pressedMouseButtons"); + NSUInteger buttons = (NSUInteger)objc_msgSend((id)NSEvent, + pressedMouseButtons); + + *pressed = (buttons & (1 << button)) != 0; + return true; } bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *plat, obs_key_t key) { - UNUSED_PARAMETER(plat); - UNUSED_PARAMETER(key); + bool mouse_pressed = false; + if (mouse_button_pressed(key, &mouse_pressed)) + return mouse_pressed; + + if (!plat) + return false; + + if (key >= OBS_KEY_LAST_VALUE) + return false; + + for (size_t i = 0; i < plat->keys[key].num;) { + IOHIDElementRef element = plat->keys[key].array[i]; + IOHIDValueRef value = 0; + IOHIDDeviceRef device = IOHIDElementGetDevice(element); + IOHIDDeviceGetValue(device, element, &value); + + if (!value) { + CFRelease(element); + da_erase(plat->keys[key], i); + continue; + } + + if (IOHIDValueGetIntegerValue(value) == 1) + return true; + + i += 1; + } return false; } From 3fc63f83f1cfe4a07fc87395303cc427cc169475 Mon Sep 17 00:00:00 2001 From: jp9000 Date: Sun, 19 Apr 2015 20:04:02 -0700 Subject: [PATCH 03/18] libobs: Add windows hotkey implementation --- libobs/obs-windows.c | 266 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 250 insertions(+), 16 deletions(-) diff --git a/libobs/obs-windows.c b/libobs/obs-windows.c index c4a2e1425..71f8aa46c 100644 --- a/libobs/obs-windows.c +++ b/libobs/obs-windows.c @@ -197,52 +197,286 @@ void log_system_info(void) struct obs_hotkeys_platform { - bool blank; + int vk_codes[OBS_KEY_LAST_VALUE]; }; +static int get_virtual_key(obs_key_t key) +{ + switch (key) { + case OBS_KEY_RETURN: return VK_RETURN; + case OBS_KEY_ESCAPE: return VK_ESCAPE; + case OBS_KEY_TAB: return VK_TAB; + case OBS_KEY_BACKTAB: return VK_OEM_BACKTAB; + case OBS_KEY_BACKSPACE: return VK_BACK; + case OBS_KEY_INSERT: return VK_INSERT; + case OBS_KEY_DELETE: return VK_DELETE; + case OBS_KEY_PAUSE: return VK_PAUSE; + case OBS_KEY_PRINT: return VK_SNAPSHOT; + case OBS_KEY_CLEAR: return VK_CLEAR; + case OBS_KEY_HOME: return VK_HOME; + case OBS_KEY_END: return VK_END; + case OBS_KEY_LEFT: return VK_LEFT; + case OBS_KEY_UP: return VK_UP; + case OBS_KEY_RIGHT: return VK_RIGHT; + case OBS_KEY_DOWN: return VK_DOWN; + case OBS_KEY_PAGEUP: return VK_PRIOR; + case OBS_KEY_PAGEDOWN: return VK_NEXT; + + case OBS_KEY_SHIFT: return VK_SHIFT; + case OBS_KEY_CONTROL: return VK_CONTROL; + case OBS_KEY_ALT: return VK_MENU; + case OBS_KEY_CAPSLOCK: return VK_CAPITAL; + case OBS_KEY_NUMLOCK: return VK_NUMLOCK; + case OBS_KEY_SCROLLLOCK: return VK_SCROLL; + + case OBS_KEY_F1: return VK_F1; + case OBS_KEY_F2: return VK_F2; + case OBS_KEY_F3: return VK_F3; + case OBS_KEY_F4: return VK_F4; + case OBS_KEY_F5: return VK_F5; + case OBS_KEY_F6: return VK_F6; + case OBS_KEY_F7: return VK_F7; + case OBS_KEY_F8: return VK_F8; + case OBS_KEY_F9: return VK_F9; + case OBS_KEY_F10: return VK_F10; + case OBS_KEY_F11: return VK_F11; + case OBS_KEY_F12: return VK_F12; + case OBS_KEY_F13: return VK_F13; + case OBS_KEY_F14: return VK_F14; + case OBS_KEY_F15: return VK_F15; + case OBS_KEY_F16: return VK_F16; + case OBS_KEY_F17: return VK_F17; + case OBS_KEY_F18: return VK_F18; + case OBS_KEY_F19: return VK_F19; + case OBS_KEY_F20: return VK_F20; + case OBS_KEY_F21: return VK_F21; + case OBS_KEY_F22: return VK_F22; + case OBS_KEY_F23: return VK_F23; + case OBS_KEY_F24: return VK_F24; + + case OBS_KEY_SPACE: return VK_SPACE; + + case OBS_KEY_APOSTROPHE: return VK_OEM_7; + case OBS_KEY_PLUS: return VK_OEM_PLUS; + case OBS_KEY_COMMA: return VK_OEM_COMMA; + case OBS_KEY_MINUS: return VK_OEM_MINUS; + case OBS_KEY_PERIOD: return VK_OEM_PERIOD; + case OBS_KEY_SLASH: return VK_OEM_2; + case OBS_KEY_0: return '0'; + case OBS_KEY_1: return '1'; + case OBS_KEY_2: return '2'; + case OBS_KEY_3: return '3'; + case OBS_KEY_4: return '4'; + case OBS_KEY_5: return '5'; + case OBS_KEY_6: return '6'; + case OBS_KEY_7: return '7'; + case OBS_KEY_8: return '8'; + case OBS_KEY_9: return '9'; + case OBS_KEY_NUMASTERISK: return VK_MULTIPLY; + case OBS_KEY_NUMPLUS: return VK_ADD; + case OBS_KEY_NUMMINUS: return VK_SUBTRACT; + case OBS_KEY_NUMPERIOD: return VK_DECIMAL; + case OBS_KEY_NUMSLASH: return VK_DIVIDE; + case OBS_KEY_NUM0: return VK_NUMPAD0; + case OBS_KEY_NUM1: return VK_NUMPAD1; + case OBS_KEY_NUM2: return VK_NUMPAD2; + case OBS_KEY_NUM3: return VK_NUMPAD3; + case OBS_KEY_NUM4: return VK_NUMPAD4; + case OBS_KEY_NUM5: return VK_NUMPAD5; + case OBS_KEY_NUM6: return VK_NUMPAD6; + case OBS_KEY_NUM7: return VK_NUMPAD7; + case OBS_KEY_NUM8: return VK_NUMPAD8; + case OBS_KEY_NUM9: return VK_NUMPAD9; + case OBS_KEY_SEMICOLON: return VK_OEM_1; + case OBS_KEY_A: return 'A'; + case OBS_KEY_B: return 'B'; + case OBS_KEY_C: return 'C'; + case OBS_KEY_D: return 'D'; + case OBS_KEY_E: return 'E'; + case OBS_KEY_F: return 'F'; + case OBS_KEY_G: return 'G'; + case OBS_KEY_H: return 'H'; + case OBS_KEY_I: return 'I'; + case OBS_KEY_J: return 'J'; + case OBS_KEY_K: return 'K'; + case OBS_KEY_L: return 'L'; + case OBS_KEY_M: return 'M'; + case OBS_KEY_N: return 'N'; + case OBS_KEY_O: return 'O'; + case OBS_KEY_P: return 'P'; + case OBS_KEY_Q: return 'Q'; + case OBS_KEY_R: return 'R'; + case OBS_KEY_S: return 'S'; + case OBS_KEY_T: return 'T'; + case OBS_KEY_U: return 'U'; + case OBS_KEY_V: return 'V'; + case OBS_KEY_W: return 'W'; + case OBS_KEY_X: return 'X'; + case OBS_KEY_Y: return 'Y'; + case OBS_KEY_Z: return 'Z'; + case OBS_KEY_BRACKETLEFT: return VK_OEM_4; + case OBS_KEY_BACKSLASH: return VK_OEM_5; + case OBS_KEY_BRACKETRIGHT: return VK_OEM_6; + case OBS_KEY_ASCIITILDE: return VK_OEM_3; + + case OBS_KEY_KANJI: return VK_KANJI; + case OBS_KEY_TOUROKU: return VK_OEM_FJ_TOUROKU; + case OBS_KEY_MASSYO: return VK_OEM_FJ_MASSHOU; + + case OBS_KEY_HANGUL: return VK_HANGUL; + + case OBS_KEY_MOUSE1: return VK_LBUTTON; + case OBS_KEY_MOUSE2: return VK_RBUTTON; + case OBS_KEY_MOUSE3: return VK_MBUTTON; + case OBS_KEY_MOUSE4: return VK_XBUTTON1; + case OBS_KEY_MOUSE5: return VK_XBUTTON2; + + /* TODO: Implement keys for non-US keyboards */ + default:; + } + return 0; +} + bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) { - UNUSED_PARAMETER(hotkeys); + hotkeys->platform_context = bzalloc(sizeof(obs_hotkeys_platform_t)); + + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) + hotkeys->platform_context->vk_codes[i] = get_virtual_key(i); return true; } void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) { - UNUSED_PARAMETER(hotkeys); + bfree(hotkeys->platform_context); + hotkeys->platform_context = NULL; +} + +static bool vk_down(DWORD vk) +{ + short state = GetAsyncKeyState(vk); + bool down = (state & 0x8000) != 0; + bool was_down = (state & 0x1) != 0; + return down || was_down; } bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *context, obs_key_t key) { - UNUSED_PARAMETER(context); - UNUSED_PARAMETER(key); + if (key == OBS_KEY_META) { + return vk_down(VK_LWIN) || vk_down(VK_RWIN); + } - return false; + UNUSED_PARAMETER(context); + return vk_down(obs_key_to_virtual_key(key)); } void obs_key_to_str(obs_key_t key, struct dstr *str) { - UNUSED_PARAMETER(key); - UNUSED_PARAMETER(str); -} + wchar_t name[128] = L""; + UINT scan_code; + int vk; -void obs_key_combination_to_str(obs_key_combination_t key, struct dstr *str) -{ - UNUSED_PARAMETER(key); - UNUSED_PARAMETER(str); + if (key == OBS_KEY_NONE) { + return; + + } else if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { + if (obs->hotkeys.translations[key]) { + dstr_copy(str, obs->hotkeys.translations[key]); + } else { + dstr_printf(str, "Mouse %d", + (int)(key - OBS_KEY_MOUSE1 + 1)); + } + return; + + } if (key == OBS_KEY_PAUSE) { + dstr_copy(str, obs_get_hotkey_translation(key, "Pause")); + return; + + } else if (key == OBS_KEY_META) { + dstr_copy(str, obs_get_hotkey_translation(key, "Windows")); + return; + } + + vk = obs_key_to_virtual_key(key); + scan_code = MapVirtualKey(vk, 0) << 16; + + switch (vk) { + case VK_HOME: + case VK_END: + case VK_LEFT: + case VK_UP: + case VK_RIGHT: + case VK_DOWN: + case VK_PRIOR: + case VK_NEXT: + case VK_INSERT: + case VK_DELETE: + case VK_NUMLOCK: + scan_code |= 0x01000000; + } + + if (scan_code != 0 && GetKeyNameTextW(scan_code, name, 128) != 0) { + dstr_from_wcs(str, name); + } else if (key != OBS_KEY_NONE) { + dstr_copy(str, obs_key_to_name(key)); + } } obs_key_t obs_key_from_virtual_key(int code) { - UNUSED_PARAMETER(code); + obs_hotkeys_platform_t *platform = obs->hotkeys.platform_context; + + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { + if (platform->vk_codes[i] == code) { + return (obs_key_t)i; + } + } return OBS_KEY_NONE; } int obs_key_to_virtual_key(obs_key_t key) { - UNUSED_PARAMETER(key); + if (key == OBS_KEY_META) + return VK_LWIN; - return 0; + return obs->hotkeys.platform_context->vk_codes[(int)key]; +} + +static inline void add_combo_key(obs_key_t key, struct dstr *str) +{ + struct dstr key_str = {0}; + + obs_key_to_str(key, &key_str); + + if (!dstr_is_empty(&key_str)) { + if (!dstr_is_empty(str)) { + dstr_cat(str, " + "); + } + dstr_cat_dstr(str, &key_str); + } + + dstr_free(&key_str); +} + +void obs_key_combination_to_str(obs_key_combination_t combination, + struct dstr *str) +{ + if ((combination.modifiers & INTERACT_CONTROL_KEY) != 0) { + add_combo_key(OBS_KEY_CONTROL, str); + } + if ((combination.modifiers & INTERACT_COMMAND_KEY) != 0) { + add_combo_key(OBS_KEY_META, str); + } + if ((combination.modifiers & INTERACT_ALT_KEY) != 0) { + add_combo_key(OBS_KEY_ALT, str); + } + if ((combination.modifiers & INTERACT_SHIFT_KEY) != 0) { + add_combo_key(OBS_KEY_SHIFT, str); + } + if (combination.key != OBS_KEY_NONE) { + add_combo_key(combination.key, str); + } } From d0cae31a48da092bf943439b5fd90590d942a2bb Mon Sep 17 00:00:00 2001 From: jp9000 Date: Tue, 21 Apr 2015 21:11:53 -0700 Subject: [PATCH 04/18] libobs: Add linux hotkey implementation --- libobs/obs-nix.c | 714 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 700 insertions(+), 14 deletions(-) diff --git a/libobs/obs-nix.c b/libobs/obs-nix.c index 749fc6987..e9b9814af 100644 --- a/libobs/obs-nix.c +++ b/libobs/obs-nix.c @@ -24,6 +24,11 @@ #endif #include #include +#include +#include +#include +#include +#include #include #include "util/dstr.h" #include "obs-internal.h" @@ -228,53 +233,734 @@ void log_system_info(void) #endif } +/* So here's how linux works with key mapping: + * + * First, there's a global key symbol enum (xcb_keysym_t) which has unique + * values for all possible symbols keys can have (e.g., '1' and '!' are + * different values). + * + * Then there's a key code (xcb_keycode_t), which is basically an index to the + * actual key itself on the keyboard (e.g., '1' and '!' will share the same + * value). + * + * xcb_keysym_t values should be given to libobs, and libobs will translate it + * to an obs_key_t, and although xcb_keysym_t can differ ('!' vs '1'), it will + * get the obs_key_t value that represents the actual key pressed; in other + * words it will be based on the key code rather than the key symbol. The same + * applies to checking key press states. + */ + +struct keycode_list { + DARRAY(xcb_keycode_t) list; +}; struct obs_hotkeys_platform { + Display *display; + xcb_keysym_t base_keysyms[OBS_KEY_LAST_VALUE]; + struct keycode_list keycodes[OBS_KEY_LAST_VALUE]; + xcb_keycode_t min_keycode; + xcb_keycode_t super_l_code; + xcb_keycode_t super_r_code; + + /* stores a copy of the keysym map for keycodes */ + xcb_keysym_t *keysyms; + int num_keysyms; + int syms_per_code; }; +#define MOUSE_1 (1<<16) +#define MOUSE_2 (2<<16) +#define MOUSE_3 (3<<16) +#define MOUSE_4 (4<<16) +#define MOUSE_5 (5<<16) + +static int get_keysym(obs_key_t key) +{ + switch (key) { + case OBS_KEY_RETURN: return XK_Return; + case OBS_KEY_ESCAPE: return XK_Escape; + case OBS_KEY_TAB: return XK_Tab; + case OBS_KEY_BACKSPACE: return XK_BackSpace; + case OBS_KEY_INSERT: return XK_Insert; + case OBS_KEY_DELETE: return XK_Delete; + case OBS_KEY_PAUSE: return XK_Pause; + case OBS_KEY_PRINT: return XK_Print; + case OBS_KEY_HOME: return XK_Home; + case OBS_KEY_END: return XK_End; + case OBS_KEY_LEFT: return XK_Left; + case OBS_KEY_UP: return XK_Up; + case OBS_KEY_RIGHT: return XK_Right; + case OBS_KEY_DOWN: return XK_Down; + case OBS_KEY_PAGEUP: return XK_Prior; + case OBS_KEY_PAGEDOWN: return XK_Next; + + case OBS_KEY_SHIFT: return XK_Shift_L; + case OBS_KEY_CONTROL: return XK_Control_L; + case OBS_KEY_ALT: return XK_Alt_L; + case OBS_KEY_CAPSLOCK: return XK_Caps_Lock; + case OBS_KEY_NUMLOCK: return XK_Num_Lock; + case OBS_KEY_SCROLLLOCK: return XK_Scroll_Lock; + + case OBS_KEY_F1: return XK_F1; + case OBS_KEY_F2: return XK_F2; + case OBS_KEY_F3: return XK_F3; + case OBS_KEY_F4: return XK_F4; + case OBS_KEY_F5: return XK_F5; + case OBS_KEY_F6: return XK_F6; + case OBS_KEY_F7: return XK_F7; + case OBS_KEY_F8: return XK_F8; + case OBS_KEY_F9: return XK_F9; + case OBS_KEY_F10: return XK_F10; + case OBS_KEY_F11: return XK_F11; + case OBS_KEY_F12: return XK_F12; + case OBS_KEY_F13: return XK_F13; + case OBS_KEY_F14: return XK_F14; + case OBS_KEY_F15: return XK_F15; + case OBS_KEY_F16: return XK_F16; + case OBS_KEY_F17: return XK_F17; + case OBS_KEY_F18: return XK_F18; + case OBS_KEY_F19: return XK_F19; + case OBS_KEY_F20: return XK_F20; + case OBS_KEY_F21: return XK_F21; + case OBS_KEY_F22: return XK_F22; + case OBS_KEY_F23: return XK_F23; + case OBS_KEY_F24: return XK_F24; + + case OBS_KEY_MENU: return XK_Menu; + case OBS_KEY_HYPER_L: return XK_Hyper_L; + case OBS_KEY_HYPER_R: return XK_Hyper_R; + case OBS_KEY_HELP: return XK_Help; + case OBS_KEY_SPACE: return XK_space; + + case OBS_KEY_EXCLAM: return XK_exclam; + case OBS_KEY_QUOTEDBL: return XK_quotedbl; + case OBS_KEY_NUMBERSIGN: return XK_numbersign; + case OBS_KEY_DOLLAR: return XK_dollar; + case OBS_KEY_PERCENT: return XK_percent; + case OBS_KEY_AMPERSAND: return XK_ampersand; + case OBS_KEY_APOSTROPHE: return XK_apostrophe; + case OBS_KEY_PARENLEFT: return XK_parenleft; + case OBS_KEY_PARENRIGHT: return XK_parenright; + case OBS_KEY_ASTERISK: return XK_asterisk; + case OBS_KEY_PLUS: return XK_plus; + case OBS_KEY_COMMA: return XK_comma; + case OBS_KEY_MINUS: return XK_minus; + case OBS_KEY_PERIOD: return XK_period; + case OBS_KEY_SLASH: return XK_slash; + case OBS_KEY_0: return XK_0; + case OBS_KEY_1: return XK_1; + case OBS_KEY_2: return XK_2; + case OBS_KEY_3: return XK_3; + case OBS_KEY_4: return XK_4; + case OBS_KEY_5: return XK_5; + case OBS_KEY_6: return XK_6; + case OBS_KEY_7: return XK_7; + case OBS_KEY_8: return XK_8; + case OBS_KEY_9: return XK_9; + case OBS_KEY_NUMEQUAL: return XK_KP_Equal; + case OBS_KEY_NUMASTERISK: return XK_KP_Multiply; + case OBS_KEY_NUMPLUS: return XK_KP_Add; + case OBS_KEY_NUMCOMMA: return XK_KP_Separator; + case OBS_KEY_NUMMINUS: return XK_KP_Subtract; + case OBS_KEY_NUMPERIOD: return XK_KP_Decimal; + case OBS_KEY_NUMSLASH: return XK_KP_Divide; + case OBS_KEY_NUM0: return XK_KP_0; + case OBS_KEY_NUM1: return XK_KP_1; + case OBS_KEY_NUM2: return XK_KP_2; + case OBS_KEY_NUM3: return XK_KP_3; + case OBS_KEY_NUM4: return XK_KP_4; + case OBS_KEY_NUM5: return XK_KP_5; + case OBS_KEY_NUM6: return XK_KP_6; + case OBS_KEY_NUM7: return XK_KP_7; + case OBS_KEY_NUM8: return XK_KP_8; + case OBS_KEY_NUM9: return XK_KP_9; + case OBS_KEY_COLON: return XK_colon; + case OBS_KEY_SEMICOLON: return XK_semicolon; + case OBS_KEY_LESS: return XK_less; + case OBS_KEY_EQUAL: return XK_equal; + case OBS_KEY_GREATER: return XK_greater; + case OBS_KEY_QUESTION: return XK_question; + case OBS_KEY_AT: return XK_at; + case OBS_KEY_A: return XK_A; + case OBS_KEY_B: return XK_B; + case OBS_KEY_C: return XK_C; + case OBS_KEY_D: return XK_D; + case OBS_KEY_E: return XK_E; + case OBS_KEY_F: return XK_F; + case OBS_KEY_G: return XK_G; + case OBS_KEY_H: return XK_H; + case OBS_KEY_I: return XK_I; + case OBS_KEY_J: return XK_J; + case OBS_KEY_K: return XK_K; + case OBS_KEY_L: return XK_L; + case OBS_KEY_M: return XK_M; + case OBS_KEY_N: return XK_N; + case OBS_KEY_O: return XK_O; + case OBS_KEY_P: return XK_P; + case OBS_KEY_Q: return XK_Q; + case OBS_KEY_R: return XK_R; + case OBS_KEY_S: return XK_S; + case OBS_KEY_T: return XK_T; + case OBS_KEY_U: return XK_U; + case OBS_KEY_V: return XK_V; + case OBS_KEY_W: return XK_W; + case OBS_KEY_X: return XK_X; + case OBS_KEY_Y: return XK_Y; + case OBS_KEY_Z: return XK_Z; + case OBS_KEY_BRACKETLEFT: return XK_bracketleft; + case OBS_KEY_BACKSLASH: return XK_backslash; + case OBS_KEY_BRACKETRIGHT: return XK_bracketright; + case OBS_KEY_ASCIICIRCUM: return XK_asciicircum; + case OBS_KEY_UNDERSCORE: return XK_underscore; + case OBS_KEY_QUOTELEFT: return XK_quoteleft; + case OBS_KEY_BRACELEFT: return XK_braceleft; + case OBS_KEY_BAR: return XK_bar; + case OBS_KEY_BRACERIGHT: return XK_braceright; + case OBS_KEY_ASCIITILDE: return XK_grave; + case OBS_KEY_NOBREAKSPACE: return XK_nobreakspace; + case OBS_KEY_EXCLAMDOWN: return XK_exclamdown; + case OBS_KEY_CENT: return XK_cent; + case OBS_KEY_STERLING: return XK_sterling; + case OBS_KEY_CURRENCY: return XK_currency; + case OBS_KEY_YEN: return XK_yen; + case OBS_KEY_BROKENBAR: return XK_brokenbar; + case OBS_KEY_SECTION: return XK_section; + case OBS_KEY_DIAERESIS: return XK_diaeresis; + case OBS_KEY_COPYRIGHT: return XK_copyright; + case OBS_KEY_ORDFEMININE: return XK_ordfeminine; + case OBS_KEY_GUILLEMOTLEFT: return XK_guillemotleft; + case OBS_KEY_NOTSIGN: return XK_notsign; + case OBS_KEY_HYPHEN: return XK_hyphen; + case OBS_KEY_REGISTERED: return XK_registered; + case OBS_KEY_MACRON: return XK_macron; + case OBS_KEY_DEGREE: return XK_degree; + case OBS_KEY_PLUSMINUS: return XK_plusminus; + case OBS_KEY_TWOSUPERIOR: return XK_twosuperior; + case OBS_KEY_THREESUPERIOR: return XK_threesuperior; + case OBS_KEY_ACUTE: return XK_acute; + case OBS_KEY_MU: return XK_mu; + case OBS_KEY_PARAGRAPH: return XK_paragraph; + case OBS_KEY_PERIODCENTERED: return XK_periodcentered; + case OBS_KEY_CEDILLA: return XK_cedilla; + case OBS_KEY_ONESUPERIOR: return XK_onesuperior; + case OBS_KEY_MASCULINE: return XK_masculine; + case OBS_KEY_GUILLEMOTRIGHT: return XK_guillemotright; + case OBS_KEY_ONEQUARTER: return XK_onequarter; + case OBS_KEY_ONEHALF: return XK_onehalf; + case OBS_KEY_THREEQUARTERS: return XK_threequarters; + case OBS_KEY_QUESTIONDOWN: return XK_questiondown; + case OBS_KEY_AGRAVE: return XK_Agrave; + case OBS_KEY_AACUTE: return XK_Aacute; + case OBS_KEY_ACIRCUMFLEX: return XK_Acircumflex; + case OBS_KEY_ATILDE: return XK_Atilde; + case OBS_KEY_ADIAERESIS: return XK_Adiaeresis; + case OBS_KEY_ARING: return XK_Aring; + case OBS_KEY_AE: return XK_AE; + case OBS_KEY_CCEDILLA: return XK_cedilla; + case OBS_KEY_EGRAVE: return XK_Egrave; + case OBS_KEY_EACUTE: return XK_Eacute; + case OBS_KEY_ECIRCUMFLEX: return XK_Ecircumflex; + case OBS_KEY_EDIAERESIS: return XK_Ediaeresis; + case OBS_KEY_IGRAVE: return XK_Igrave; + case OBS_KEY_IACUTE: return XK_Iacute; + case OBS_KEY_ICIRCUMFLEX: return XK_Icircumflex; + case OBS_KEY_IDIAERESIS: return XK_Idiaeresis; + case OBS_KEY_ETH: return XK_ETH; + case OBS_KEY_NTILDE: return XK_Ntilde; + case OBS_KEY_OGRAVE: return XK_Ograve; + case OBS_KEY_OACUTE: return XK_Oacute; + case OBS_KEY_OCIRCUMFLEX: return XK_Ocircumflex; + case OBS_KEY_ODIAERESIS: return XK_Odiaeresis; + case OBS_KEY_MULTIPLY: return XK_multiply; + case OBS_KEY_OOBLIQUE: return XK_Ooblique; + case OBS_KEY_UGRAVE: return XK_Ugrave; + case OBS_KEY_UACUTE: return XK_Uacute; + case OBS_KEY_UCIRCUMFLEX: return XK_Ucircumflex; + case OBS_KEY_UDIAERESIS: return XK_Udiaeresis; + case OBS_KEY_YACUTE: return XK_Yacute; + case OBS_KEY_THORN: return XK_Thorn; + case OBS_KEY_SSHARP: return XK_ssharp; + case OBS_KEY_DIVISION: return XK_division; + case OBS_KEY_YDIAERESIS: return XK_Ydiaeresis; + case OBS_KEY_MULTI_KEY: return XK_Multi_key; + case OBS_KEY_CODEINPUT: return XK_Codeinput; + case OBS_KEY_SINGLECANDIDATE: return XK_SingleCandidate; + case OBS_KEY_MULTIPLECANDIDATE: return XK_MultipleCandidate; + case OBS_KEY_PREVIOUSCANDIDATE: return XK_PreviousCandidate; + case OBS_KEY_MODE_SWITCH: return XK_Mode_switch; + case OBS_KEY_KANJI: return XK_Kanji; + case OBS_KEY_MUHENKAN: return XK_Muhenkan; + case OBS_KEY_HENKAN: return XK_Henkan; + case OBS_KEY_ROMAJI: return XK_Romaji; + case OBS_KEY_HIRAGANA: return XK_Hiragana; + case OBS_KEY_KATAKANA: return XK_Katakana; + case OBS_KEY_HIRAGANA_KATAKANA: return XK_Hiragana_Katakana; + case OBS_KEY_ZENKAKU: return XK_Zenkaku; + case OBS_KEY_HANKAKU: return XK_Hankaku; + case OBS_KEY_ZENKAKU_HANKAKU: return XK_Zenkaku_Hankaku; + case OBS_KEY_TOUROKU: return XK_Touroku; + case OBS_KEY_MASSYO: return XK_Massyo; + case OBS_KEY_KANA_LOCK: return XK_Kana_Lock; + case OBS_KEY_KANA_SHIFT: return XK_Kana_Shift; + case OBS_KEY_EISU_SHIFT: return XK_Eisu_Shift; + case OBS_KEY_EISU_TOGGLE: return XK_Eisu_toggle; + case OBS_KEY_HANGUL: return XK_Hangul; + case OBS_KEY_HANGUL_START: return XK_Hangul_Start; + case OBS_KEY_HANGUL_END: return XK_Hangul_End; + case OBS_KEY_HANGUL_HANJA: return XK_Hangul_Hanja; + case OBS_KEY_HANGUL_JAMO: return XK_Hangul_Jamo; + case OBS_KEY_HANGUL_ROMAJA: return XK_Hangul_Romaja; + case OBS_KEY_HANGUL_BANJA: return XK_Hangul_Banja; + case OBS_KEY_HANGUL_PREHANJA: return XK_Hangul_PreHanja; + case OBS_KEY_HANGUL_POSTHANJA: return XK_Hangul_PostHanja; + case OBS_KEY_HANGUL_SPECIAL: return XK_Hangul_Special; + case OBS_KEY_DEAD_GRAVE: return XK_dead_grave; + case OBS_KEY_DEAD_ACUTE: return XK_dead_acute; + case OBS_KEY_DEAD_CIRCUMFLEX: return XK_dead_circumflex; + case OBS_KEY_DEAD_TILDE: return XK_dead_tilde; + case OBS_KEY_DEAD_MACRON: return XK_dead_macron; + case OBS_KEY_DEAD_BREVE: return XK_dead_breve; + case OBS_KEY_DEAD_ABOVEDOT: return XK_dead_abovedot; + case OBS_KEY_DEAD_DIAERESIS: return XK_dead_diaeresis; + case OBS_KEY_DEAD_ABOVERING: return XK_dead_abovering; + case OBS_KEY_DEAD_DOUBLEACUTE: return XK_dead_doubleacute; + case OBS_KEY_DEAD_CARON: return XK_dead_caron; + case OBS_KEY_DEAD_CEDILLA: return XK_dead_cedilla; + case OBS_KEY_DEAD_OGONEK: return XK_dead_ogonek; + case OBS_KEY_DEAD_IOTA: return XK_dead_iota; + case OBS_KEY_DEAD_VOICED_SOUND: return XK_dead_voiced_sound; + case OBS_KEY_DEAD_SEMIVOICED_SOUND: return XK_dead_semivoiced_sound; + case OBS_KEY_DEAD_BELOWDOT: return XK_dead_belowdot; + case OBS_KEY_DEAD_HOOK: return XK_dead_hook; + case OBS_KEY_DEAD_HORN: return XK_dead_horn; + + case OBS_KEY_MOUSE1: return MOUSE_1; + case OBS_KEY_MOUSE2: return MOUSE_2; + case OBS_KEY_MOUSE3: return MOUSE_3; + case OBS_KEY_MOUSE4: return MOUSE_4; + case OBS_KEY_MOUSE5: return MOUSE_5; + + /* TODO: Implement keys for non-US keyboards */ + default:; + } + return 0; +} + +static inline void fill_base_keysyms(struct obs_core_hotkeys *hotkeys) +{ + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) + hotkeys->platform_context->base_keysyms[i] = get_keysym(i); +} + +static obs_key_t key_from_base_keysym(obs_hotkeys_platform_t *context, + xcb_keysym_t code) +{ + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { + if (context->base_keysyms[i] == (xcb_keysym_t)code) { + return (obs_key_t)i; + } + } + + return OBS_KEY_NONE; +} + +static inline void add_key(obs_hotkeys_platform_t *context, obs_key_t key, + int code) +{ + xcb_keycode_t kc = (xcb_keycode_t)code; + da_push_back(context->keycodes[key].list, &kc); + + if (context->keycodes[key].list.num > 1) { + blog(LOG_DEBUG, "found alternate keycode %d for %s " + "which already has keycode %d", + code, obs_key_to_name(key), + (int)context->keycodes[key].list.array[0]); + } +} + +static inline bool fill_keycodes(struct obs_core_hotkeys *hotkeys) +{ + obs_hotkeys_platform_t *context = hotkeys->platform_context; + xcb_connection_t *connection = XGetXCBConnection(context->display); + const struct xcb_setup_t *setup = xcb_get_setup(connection); + xcb_get_keyboard_mapping_cookie_t cookie; + xcb_get_keyboard_mapping_reply_t *reply; + xcb_generic_error_t *error = NULL; + int code; + + int mincode = setup->min_keycode; + int maxcode = setup->max_keycode; + + context->min_keycode = setup->min_keycode; + + cookie = xcb_get_keyboard_mapping(connection, + mincode, maxcode - mincode - 1); + + reply = xcb_get_keyboard_mapping_reply(connection, cookie, &error); + + if (error || !reply) { + blog(LOG_WARNING, "xcb_get_keyboard_mapping_reply failed"); + goto error1; + } + + const xcb_keysym_t *keysyms = xcb_get_keyboard_mapping_keysyms(reply); + int syms_per_code = (int)reply->keysyms_per_keycode; + + context->num_keysyms = (maxcode - mincode) * syms_per_code; + context->syms_per_code = syms_per_code; + context->keysyms = bmemdup(keysyms, + sizeof(xcb_keysym_t) * context->num_keysyms); + + for (code = mincode; code <= maxcode; code++) { + const xcb_keysym_t *sym; + obs_key_t key; + + sym = &keysyms[(code - mincode) * syms_per_code]; + + for (int i = 0; i < syms_per_code; i++) { + if (!sym[i]) + break; + + if (sym[i] == XK_Super_L) { + context->super_l_code = code; + break; + } else if (sym[i] == XK_Super_R) { + context->super_r_code = code; + break; + } else { + key = key_from_base_keysym(context, sym[i]); + + if (key != OBS_KEY_NONE) { + add_key(context, key, code); + break; + } + } + } + + } + +error1: + free(reply); + free(error); + + return error != NULL || reply == NULL; +} + bool obs_hotkeys_platform_init(struct obs_core_hotkeys *hotkeys) { - UNUSED_PARAMETER(hotkeys); + Display *display = XOpenDisplay(NULL); + if (!display) + return false; + hotkeys->platform_context = bzalloc(sizeof(obs_hotkeys_platform_t)); + hotkeys->platform_context->display = display; + + fill_base_keysyms(hotkeys); + fill_keycodes(hotkeys); return true; } void obs_hotkeys_platform_free(struct obs_core_hotkeys *hotkeys) { - UNUSED_PARAMETER(hotkeys); + obs_hotkeys_platform_t *context = hotkeys->platform_context; + + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) + da_free(context->keycodes[i].list); + + XCloseDisplay(context->display); + bfree(context->keysyms); + bfree(context); + + hotkeys->platform_context = NULL; +} + +static xcb_screen_t *default_screen(obs_hotkeys_platform_t *context, + xcb_connection_t *connection) +{ + int def_screen_idx = XDefaultScreen(context->display); + xcb_screen_iterator_t iter; + + iter = xcb_setup_roots_iterator(xcb_get_setup(connection)); + while (iter.rem) { + if (--def_screen_idx == 0) { + return iter.data; + break; + } + + xcb_screen_next(&iter); + } + + return NULL; +} + +static inline xcb_window_t root_window(obs_hotkeys_platform_t *context, + xcb_connection_t *connection) +{ + xcb_screen_t *screen = default_screen(context, connection); + if (screen) + return screen->root; + return 0; +} + +static bool mouse_button_pressed(xcb_connection_t *connection, + obs_hotkeys_platform_t *context, obs_key_t key) +{ + xcb_generic_error_t *error = NULL; + xcb_query_pointer_cookie_t qpc; + xcb_query_pointer_reply_t *reply; + bool ret = false; + + qpc = xcb_query_pointer(connection, root_window(context, connection)); + reply = xcb_query_pointer_reply(connection, qpc, &error); + + if (error) { + blog(LOG_WARNING, "xcb_query_pointer_reply failed"); + } else { + uint16_t buttons = reply->mask; + + switch (key) { + case OBS_KEY_MOUSE1: ret = buttons & XCB_BUTTON_MASK_1; break; + case OBS_KEY_MOUSE2: ret = buttons & XCB_BUTTON_MASK_3; break; + case OBS_KEY_MOUSE3: ret = buttons & XCB_BUTTON_MASK_2; break; + default:; + } + } + + free(reply); + free(error); + return ret; +} + +static inline bool keycode_pressed(xcb_query_keymap_reply_t *reply, + xcb_keycode_t code) +{ + return (reply->keys[code / 8] & (1 << (code % 8))) != 0; +} + +static bool key_pressed(xcb_connection_t *connection, + obs_hotkeys_platform_t *context, obs_key_t key) +{ + struct keycode_list *codes = &context->keycodes[key]; + xcb_generic_error_t *error = NULL; + xcb_query_keymap_reply_t *reply; + bool pressed = false; + + reply = xcb_query_keymap_reply(connection, + xcb_query_keymap(connection), &error); + if (error) { + blog(LOG_WARNING, "xcb_query_keymap failed"); + + } else if (key == OBS_KEY_META) { + pressed = keycode_pressed(reply, context->super_l_code) || + keycode_pressed(reply, context->super_r_code); + + } else { + for (size_t i = 0; i < codes->list.num; i++) { + if (keycode_pressed(reply, codes->list.array[i])) { + pressed = true; + break; + } + } + } + + free(reply); + free(error); + return pressed; } bool obs_hotkeys_platform_is_pressed(obs_hotkeys_platform_t *context, obs_key_t key) { - UNUSED_PARAMETER(context); - UNUSED_PARAMETER(key); + xcb_connection_t *conn = XGetXCBConnection(context->display); + + if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { + return mouse_button_pressed(conn, context, key); + } else { + return key_pressed(conn, context, key); + } +} + +static bool get_key_translation(struct dstr *dstr, xcb_keycode_t keycode) +{ + xcb_connection_t *connection; + char name[128]; + + connection = XGetXCBConnection(obs->hotkeys.platform_context->display); + + XKeyEvent event = {0}; + event.type = KeyPress; + event.display = obs->hotkeys.platform_context->display; + event.keycode = keycode; + event.root = root_window(obs->hotkeys.platform_context, connection); + event.window = event.root; + + if (keycode) { + int len = XLookupString(&event, name, 128, NULL, NULL); + if (len) { + dstr_ncopy(dstr, name, len); + dstr_to_upper(dstr); + return true; + } + } return false; } -void obs_key_to_str(obs_key_t key, struct dstr *str) +void obs_key_to_str(obs_key_t key, struct dstr *dstr) { - UNUSED_PARAMETER(key); - UNUSED_PARAMETER(str); + if (key >= OBS_KEY_MOUSE1 && key <= OBS_KEY_MOUSE29) { + if (obs->hotkeys.translations[key]) { + dstr_copy(dstr, obs->hotkeys.translations[key]); + } else { + dstr_printf(dstr, "Mouse %d", + (int)(key - OBS_KEY_MOUSE1 + 1)); + } + return; + } + + if (key >= OBS_KEY_NUM0 && key <= OBS_KEY_NUM9) { + if (obs->hotkeys.translations[key]) { + dstr_copy(dstr, obs->hotkeys.translations[key]); + } else { + dstr_printf(dstr, "Numpad %d", + (int)(key - OBS_KEY_NUM0)); + } + return; + } + +#define translate_key(key, def) \ + dstr_copy(dstr, obs_get_hotkey_translation(key, def)) + + switch (key) { + case OBS_KEY_INSERT: return translate_key(key, "Insert"); + case OBS_KEY_DELETE: return translate_key(key, "Delete"); + case OBS_KEY_HOME: return translate_key(key, "Home"); + case OBS_KEY_END: return translate_key(key, "End"); + case OBS_KEY_PAGEUP: return translate_key(key, "Page Up"); + case OBS_KEY_PAGEDOWN: return translate_key(key, "Page Down"); + case OBS_KEY_NUMLOCK: return translate_key(key, "Num Lock"); + case OBS_KEY_SCROLLLOCK: return translate_key(key, "Scroll Lock"); + case OBS_KEY_CAPSLOCK: return translate_key(key, "Caps Lock"); + case OBS_KEY_BACKSPACE: return translate_key(key, "Backspace"); + case OBS_KEY_TAB: return translate_key(key, "Tab"); + case OBS_KEY_PRINT: return translate_key(key, "Print"); + case OBS_KEY_PAUSE: return translate_key(key, "Pause"); + case OBS_KEY_LEFT: return translate_key(key, "Left"); + case OBS_KEY_RIGHT: return translate_key(key, "Right"); + case OBS_KEY_UP: return translate_key(key, "Up"); + case OBS_KEY_DOWN: return translate_key(key, "Down"); + case OBS_KEY_SHIFT: return translate_key(key, "Shift"); + case OBS_KEY_ALT: return translate_key(key, "Alt"); + case OBS_KEY_CONTROL: return translate_key(key, "Control"); + case OBS_KEY_META: return translate_key(key, "Super"); + case OBS_KEY_MENU: return translate_key(key, "Menu"); + case OBS_KEY_NUMASTERISK: return translate_key(key, "Numpad *"); + case OBS_KEY_NUMPLUS: return translate_key(key, "Numpad +"); + case OBS_KEY_NUMCOMMA: return translate_key(key, "Numpad ,"); + case OBS_KEY_NUMPERIOD: return translate_key(key, "Numpad ."); + case OBS_KEY_NUMSLASH: return translate_key(key, "Numpad /"); + default:; + } + + if (key >= OBS_KEY_F1 && key <= OBS_KEY_F35) { + dstr_printf(dstr, "F%d", + (int)(key - OBS_KEY_F1 + 1)); + return; + } + + obs_hotkeys_platform_t *context = obs->hotkeys.platform_context; + struct keycode_list *keycodes = &context->keycodes[key]; + + for (size_t i = 0; i < keycodes->list.num; i++) { + if (get_key_translation(dstr, keycodes->list.array[i])) { + break; + } + } + + if (key != OBS_KEY_NONE && dstr_is_empty(dstr)) { + dstr_copy(dstr, obs_key_to_name(key)); + } } -void obs_key_combination_to_str(obs_key_combination_t key, struct dstr *str) +static obs_key_t key_from_keycode(obs_hotkeys_platform_t *context, + xcb_keycode_t code) { - UNUSED_PARAMETER(key); - UNUSED_PARAMETER(str); + for (size_t i = 0; i < OBS_KEY_LAST_VALUE; i++) { + struct keycode_list *codes = &context->keycodes[i]; + + for (size_t j = 0; j < codes->list.num; j++) { + if (codes->list.array[j] == code) { + return (obs_key_t)i; + } + } + } + + return OBS_KEY_NONE; } -obs_key_t obs_key_from_virtual_key(int code) +obs_key_t obs_key_from_virtual_key(int sym) { - UNUSED_PARAMETER(code); + obs_hotkeys_platform_t *context = obs->hotkeys.platform_context; + const xcb_keysym_t *keysyms = context->keysyms; + int syms_per_code = context->syms_per_code; + int num_keysyms = context->num_keysyms; + + if (sym == 0) + return OBS_KEY_NONE; + + for (int i = 0; i < num_keysyms; i++) { + if (keysyms[i] == (xcb_keysym_t)sym) { + xcb_keycode_t code = (xcb_keycode_t)(i / syms_per_code); + code += context->min_keycode; + obs_key_t key = key_from_keycode(context, code); + + return key; + } + } return OBS_KEY_NONE; } int obs_key_to_virtual_key(obs_key_t key) { - UNUSED_PARAMETER(key); + if (key == OBS_KEY_META) + return XK_Super_L; - return 0; + return (int)obs->hotkeys.platform_context->base_keysyms[(int)key]; +} + +static inline void add_combo_key(obs_key_t key, struct dstr *str) +{ + struct dstr key_str = {0}; + + obs_key_to_str(key, &key_str); + + if (!dstr_is_empty(&key_str)) { + if (!dstr_is_empty(str)) { + dstr_cat(str, " + "); + } + dstr_cat_dstr(str, &key_str); + } + + dstr_free(&key_str); +} + +void obs_key_combination_to_str(obs_key_combination_t combination, + struct dstr *str) +{ + if ((combination.modifiers & INTERACT_CONTROL_KEY) != 0) { + add_combo_key(OBS_KEY_CONTROL, str); + } + if ((combination.modifiers & INTERACT_COMMAND_KEY) != 0) { + add_combo_key(OBS_KEY_META, str); + } + if ((combination.modifiers & INTERACT_ALT_KEY) != 0) { + add_combo_key(OBS_KEY_ALT, str); + } + if ((combination.modifiers & INTERACT_SHIFT_KEY) != 0) { + add_combo_key(OBS_KEY_SHIFT, str); + } + if (combination.key != OBS_KEY_NONE) { + add_combo_key(combination.key, str); + } } From 1d39c3e9b6753f90d2c98cfa125ac10d8edefc7f Mon Sep 17 00:00:00 2001 From: Palana Date: Sat, 1 Nov 2014 21:41:17 +0100 Subject: [PATCH 05/18] (API Change) libobs: Add hotkey data to *_create functions --- libobs/obs-encoder.c | 19 +++++++++++-------- libobs/obs-internal.h | 3 ++- libobs/obs-output.c | 9 +++++---- libobs/obs-scene.c | 3 ++- libobs/obs-service.c | 5 +++-- libobs/obs-source.c | 9 +++++---- libobs/obs.c | 6 ++---- libobs/obs.h | 12 +++++++----- obs/window-basic-filters.cpp | 2 +- obs/window-basic-main-outputs.cpp | 27 +++++++++++++++------------ obs/window-basic-main.cpp | 13 ++++++++----- obs/window-basic-settings.cpp | 3 ++- obs/window-basic-source-select.cpp | 2 +- test/osx/test.mm | 2 +- test/win/test.cpp | 5 +++-- 15 files changed, 68 insertions(+), 52 deletions(-) diff --git a/libobs/obs-encoder.c b/libobs/obs-encoder.c index 2cb815622..9b0cb38ca 100644 --- a/libobs/obs-encoder.c +++ b/libobs/obs-encoder.c @@ -37,12 +37,13 @@ const char *obs_encoder_get_display_name(const char *id) } static bool init_encoder(struct obs_encoder *encoder, const char *name, - obs_data_t *settings) + obs_data_t *settings, obs_data_t *hotkey_data) { pthread_mutex_init_value(&encoder->callbacks_mutex); pthread_mutex_init_value(&encoder->outputs_mutex); - if (!obs_context_data_init(&encoder->context, settings, name, NULL)) + if (!obs_context_data_init(&encoder->context, settings, name, + hotkey_data)) return false; if (pthread_mutex_init(&encoder->callbacks_mutex, NULL) != 0) return false; @@ -57,7 +58,7 @@ static bool init_encoder(struct obs_encoder *encoder, const char *name, static struct obs_encoder *create_encoder(const char *id, enum obs_encoder_type type, const char *name, - obs_data_t *settings, size_t mixer_idx) + obs_data_t *settings, size_t mixer_idx, obs_data_t *hotkey_data) { struct obs_encoder *encoder; struct obs_encoder_info *ei = find_encoder(id); @@ -70,7 +71,7 @@ static struct obs_encoder *create_encoder(const char *id, encoder->info = *ei; encoder->mixer_idx = mixer_idx; - success = init_encoder(encoder, name, settings); + success = init_encoder(encoder, name, settings, hotkey_data); if (!success) { obs_encoder_destroy(encoder); encoder = NULL; @@ -88,17 +89,19 @@ static struct obs_encoder *create_encoder(const char *id, } obs_encoder_t *obs_video_encoder_create(const char *id, const char *name, - obs_data_t *settings) + obs_data_t *settings, obs_data_t *hotkey_data) { if (!name || !id) return NULL; - return create_encoder(id, OBS_ENCODER_VIDEO, name, settings, 0); + return create_encoder(id, OBS_ENCODER_VIDEO, name, settings, 0, + hotkey_data); } obs_encoder_t *obs_audio_encoder_create(const char *id, const char *name, - obs_data_t *settings, size_t mixer_idx) + obs_data_t *settings, size_t mixer_idx, obs_data_t *hotkey_data) { if (!name || !id) return NULL; - return create_encoder(id, OBS_ENCODER_AUDIO, name, settings, mixer_idx); + return create_encoder(id, OBS_ENCODER_AUDIO, name, settings, mixer_idx, + hotkey_data); } static void receive_video(void *param, struct video_data *frame); diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index 4665b9bd4..a5dd80e6a 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -517,7 +517,8 @@ struct obs_source { extern const struct obs_source_info *find_source(struct darray *list, const char *id); extern bool obs_source_init_context(struct obs_source *source, - obs_data_t *settings, const char *name); + obs_data_t *settings, const char *name, + obs_data_t *hotkey_data); extern bool obs_source_init(struct obs_source *source, const struct obs_source_info *info); diff --git a/libobs/obs-output.c b/libobs/obs-output.c index 2994143f6..d484210a6 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -47,9 +47,10 @@ static const char *output_signals[] = { }; static bool init_output_handlers(struct obs_output *output, const char *name, - obs_data_t *settings) + obs_data_t *settings, obs_data_t *hotkey_data) { - if (!obs_context_data_init(&output->context, settings, name, NULL)) + if (!obs_context_data_init(&output->context, settings, name, + hotkey_data)) return false; signal_handler_add_array(output->context.signals, output_signals); @@ -57,7 +58,7 @@ static bool init_output_handlers(struct obs_output *output, const char *name, } obs_output_t *obs_output_create(const char *id, const char *name, - obs_data_t *settings) + obs_data_t *settings, obs_data_t *hotkey_data) { const struct obs_output_info *info = find_output(id); struct obs_output *output; @@ -73,7 +74,7 @@ obs_output_t *obs_output_create(const char *id, const char *name, if (pthread_mutex_init(&output->interleaved_mutex, NULL) != 0) goto fail; - if (!init_output_handlers(output, name, settings)) + if (!init_output_handlers(output, name, settings, hotkey_data)) goto fail; output->info = *info; diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index 6972f6270..0984e7aab 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -473,7 +473,8 @@ const struct obs_source_info scene_info = obs_scene_t *obs_scene_create(const char *name) { struct obs_source *source = - obs_source_create(OBS_SOURCE_TYPE_INPUT, "scene", name, NULL); + obs_source_create(OBS_SOURCE_TYPE_INPUT, "scene", name, NULL, + NULL); return source->context.data; } diff --git a/libobs/obs-service.c b/libobs/obs-service.c index 0b41e5335..289532886 100644 --- a/libobs/obs-service.c +++ b/libobs/obs-service.c @@ -34,7 +34,7 @@ const char *obs_service_get_display_name(const char *id) } obs_service_t *obs_service_create(const char *id, const char *name, - obs_data_t *settings) + obs_data_t *settings, obs_data_t *hotkey_data) { const struct obs_service_info *info = find_service(id); struct obs_service *service; @@ -46,7 +46,8 @@ obs_service_t *obs_service_create(const char *id, const char *name, service = bzalloc(sizeof(struct obs_service)); - if (!obs_context_data_init(&service->context, settings, name, NULL)) { + if (!obs_context_data_init(&service->context, settings, name, + hotkey_data)) { bfree(service); return NULL; } diff --git a/libobs/obs-source.c b/libobs/obs-source.c index 888328981..230285577 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -94,9 +94,10 @@ static const char *source_signals[] = { }; bool obs_source_init_context(struct obs_source *source, - obs_data_t *settings, const char *name) + obs_data_t *settings, const char *name, obs_data_t *hotkey_data) { - if (!obs_context_data_init(&source->context, settings, name, NULL)) + if (!obs_context_data_init(&source->context, settings, name, + hotkey_data)) return false; return signal_handler_add_array(source->context.signals, @@ -170,7 +171,7 @@ static inline void obs_source_dosignal(struct obs_source *source, } obs_source_t *obs_source_create(enum obs_source_type type, const char *id, - const char *name, obs_data_t *settings) + const char *name, obs_data_t *settings, obs_data_t *hotkey_data) { struct obs_source *source = bzalloc(sizeof(struct obs_source)); @@ -185,7 +186,7 @@ obs_source_t *obs_source_create(enum obs_source_type type, const char *id, source->info = *info; } - if (!obs_source_init_context(source, settings, name)) + if (!obs_source_init_context(source, settings, name, hotkey_data)) goto fail; if (info && info->get_defaults) diff --git a/libobs/obs.c b/libobs/obs.c index cd69ac848..235ac472c 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -1425,11 +1425,9 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data, uint32_t flags; uint32_t mixers; - source = obs_source_create(type, id, name, settings); + source = obs_source_create(type, id, name, settings, hotkeys); - obs_data_release(source->context.hotkey_data); - source->context.hotkey_data = hotkeys; - obs_hotkeys_load_source(source, hotkeys); + obs_data_release(hotkeys); obs_data_set_default_double(source_data, "volume", 1.0); volume = obs_data_get_double(source_data, "volume"); diff --git a/libobs/obs.h b/libobs/obs.h index 690b3aa06..e7fa32b5d 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -655,7 +655,8 @@ EXPORT const char *obs_source_get_display_name(enum obs_source_type type, * or modifying video/audio. Use obs_source_release to release it. */ EXPORT obs_source_t *obs_source_create(enum obs_source_type type, - const char *id, const char *name, obs_data_t *settings); + const char *id, const char *name, obs_data_t *settings, + obs_data_t *hotkey_data); /** * Adds/releases a reference to a source. When the last reference is @@ -1092,7 +1093,7 @@ EXPORT const char *obs_output_get_display_name(const char *id); * directshow, or other custom outputs. */ EXPORT obs_output_t *obs_output_create(const char *id, const char *name, - obs_data_t *settings); + obs_data_t *settings, obs_data_t *hotkey_data); /** * Adds/releases a reference to an output. When the last reference is @@ -1302,7 +1303,7 @@ EXPORT const char *obs_encoder_get_display_name(const char *id); * @return The video encoder context, or NULL if failed or not found. */ EXPORT obs_encoder_t *obs_video_encoder_create(const char *id, const char *name, - obs_data_t *settings); + obs_data_t *settings, obs_data_t *hotkey_data); /** * Creates an audio encoder context @@ -1314,7 +1315,8 @@ EXPORT obs_encoder_t *obs_video_encoder_create(const char *id, const char *name, * @return The video encoder context, or NULL if failed or not found. */ EXPORT obs_encoder_t *obs_audio_encoder_create(const char *id, const char *name, - obs_data_t *settings, size_t mixer_idx); + obs_data_t *settings, size_t mixer_idx, + obs_data_t *hotkey_data); /** * Adds/releases a reference to an encoder. When the last reference is @@ -1434,7 +1436,7 @@ EXPORT void obs_free_encoder_packet(struct encoder_packet *packet); EXPORT const char *obs_service_get_display_name(const char *id); EXPORT obs_service_t *obs_service_create(const char *id, const char *name, - obs_data_t *settings); + obs_data_t *settings, obs_data_t *hotkey_data); /** * Adds/releases a reference to a service. When the last reference is diff --git a/obs/window-basic-filters.cpp b/obs/window-basic-filters.cpp index 12a952681..c1b20d17b 100644 --- a/obs/window-basic-filters.cpp +++ b/obs/window-basic-filters.cpp @@ -367,7 +367,7 @@ void OBSBasicFilters::AddNewFilter(const char *id) } obs_source_t *filter = obs_source_create(OBS_SOURCE_TYPE_FILTER, - id, name.c_str(), nullptr); + id, name.c_str(), nullptr, nullptr); if (filter) { obs_source_filter_add(source, filter); obs_source_release(filter); diff --git a/obs/window-basic-main-outputs.cpp b/obs/window-basic-main-outputs.cpp index 5fc30e363..1bcbcc14c 100644 --- a/obs/window-basic-main-outputs.cpp +++ b/obs/window-basic-main-outputs.cpp @@ -65,26 +65,28 @@ struct SimpleOutput : BasicOutputHandler { SimpleOutput::SimpleOutput(OBSBasic *main_) : BasicOutputHandler(main_) { streamOutput = obs_output_create("rtmp_output", "simple_stream", - nullptr); + nullptr, nullptr); if (!streamOutput) throw "Failed to create stream output (simple output)"; obs_output_release(streamOutput); fileOutput = obs_output_create("flv_output", "simple_file_output", - nullptr); + nullptr, nullptr); if (!fileOutput) throw "Failed to create recording output (simple output)"; obs_output_release(fileOutput); - h264 = obs_video_encoder_create("obs_x264", "simple_h264", nullptr); + h264 = obs_video_encoder_create("obs_x264", "simple_h264", nullptr, + nullptr); if (!h264) throw "Failed to create h264 encoder (simple output)"; obs_encoder_release(h264); - aac = obs_audio_encoder_create("libfdk_aac", "simple_aac", nullptr, 0); + aac = obs_audio_encoder_create("libfdk_aac", "simple_aac", nullptr, 0, + nullptr); if (!aac) aac = obs_audio_encoder_create("ffmpeg_aac", "simple_aac", - nullptr, 0); + nullptr, 0, nullptr); if (!aac) throw "Failed to create audio encoder (simple output)"; obs_encoder_release(aac); @@ -322,21 +324,21 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) "obs-studio/basic/recordEncoder.json"); streamOutput = obs_output_create("rtmp_output", "adv_stream", - nullptr); + nullptr, nullptr); if (!streamOutput) throw "Failed to create stream output (advanced output)"; obs_output_release(streamOutput); if (ffmpegRecording) { fileOutput = obs_output_create("ffmpeg_output", - "adv_ffmpeg_output", nullptr); + "adv_ffmpeg_output", nullptr, nullptr); if (!fileOutput) throw "Failed to create recording FFmpeg output " "(advanced output)"; obs_output_release(fileOutput); } else { fileOutput = obs_output_create("flv_output", "adv_file_output", - nullptr); + nullptr, nullptr); if (!fileOutput) throw "Failed to create recording output " "(advanced output)"; @@ -344,7 +346,8 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) if (!useStreamEncoder) { h264Recording = obs_video_encoder_create(recordEncoder, - "recording_h264", recordEncSettings); + "recording_h264", recordEncSettings, + nullptr); if (!h264Recording) throw "Failed to create recording h264 " "encoder (advanced output)"; @@ -353,7 +356,7 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) } h264Streaming = obs_video_encoder_create(streamEncoder, - "streaming_h264", streamEncSettings); + "streaming_h264", streamEncSettings, nullptr); if (!h264Streaming) throw "Failed to create streaming h264 encoder " "(advanced output)"; @@ -364,10 +367,10 @@ AdvancedOutput::AdvancedOutput(OBSBasic *main_) : BasicOutputHandler(main_) sprintf(name, "adv_aac%d", i); aacTrack[i] = obs_audio_encoder_create("libfdk_aac", - name, nullptr, i); + name, nullptr, i, nullptr); if (!aacTrack[i]) aacTrack[i] = obs_audio_encoder_create("ffmpeg_aac", - name, nullptr, i); + name, nullptr, i, nullptr); if (!aacTrack[i]) throw "Failed to create audio encoder " "(advanced output)"; diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 7a6b789fd..a3cb2dd5d 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -292,7 +292,7 @@ void OBSBasic::CreateDefaultScene() #ifdef __APPLE__ source = obs_source_create(OBS_SOURCE_TYPE_INPUT, "display_capture", - Str("Basic.DisplayCapture"), NULL); + Str("Basic.DisplayCapture"), NULL, nullptr); if (source) { obs_scene_add(scene, source); @@ -407,7 +407,8 @@ bool OBSBasic::LoadService() obs_data_t *settings = obs_data_get_obj(data, "settings"); - service = obs_service_create(type, "default_service", settings); + service = obs_service_create(type, "default_service", settings, + nullptr); obs_service_release(service); obs_data_release(settings); @@ -421,7 +422,8 @@ bool OBSBasic::InitService() if (LoadService()) return true; - service = obs_service_create("rtmp_common", "default_service", nullptr); + service = obs_service_create("rtmp_common", "default_service", nullptr, + nullptr); if (!service) return false; obs_service_release(service); @@ -1449,7 +1451,8 @@ void OBSBasic::RenderMain(void *data, uint32_t cx, uint32_t cy) obs_service_t *OBSBasic::GetService() { if (!service) { - service = obs_service_create("rtmp_common", NULL, NULL); + service = obs_service_create("rtmp_common", NULL, NULL, + nullptr); obs_service_release(service); } return service; @@ -1635,7 +1638,7 @@ void OBSBasic::ResetAudioDevice(const char *sourceId, const char *deviceName, obs_data_t *settings = obs_data_create(); obs_data_set_string(settings, "device_id", deviceId); source = obs_source_create(OBS_SOURCE_TYPE_INPUT, - sourceId, deviceDesc, settings); + sourceId, deviceDesc, settings, nullptr); obs_data_release(settings); obs_set_output_source(channel, source); diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp index f43959e71..e6665dad4 100644 --- a/obs/window-basic-settings.cpp +++ b/obs/window-basic-settings.cpp @@ -1318,7 +1318,8 @@ void OBSBasicSettings::SaveStream1Settings() QString streamType = GetComboData(ui->streamType); obs_service_t *newService = obs_service_create(QT_TO_UTF8(streamType), - "default_service", streamProperties->GetSettings()); + "default_service", streamProperties->GetSettings(), + nullptr); if (!newService) return; diff --git a/obs/window-basic-source-select.cpp b/obs/window-basic-source-select.cpp index 626258f95..c426ae9c4 100644 --- a/obs/window-basic-source-select.cpp +++ b/obs/window-basic-source-select.cpp @@ -111,7 +111,7 @@ bool AddNew(QWidget *parent, const char *id, const char *name) } else { source = obs_source_create(OBS_SOURCE_TYPE_INPUT, - id, name, NULL); + id, name, NULL, nullptr); if (source) { obs_add_source(source); diff --git a/test/osx/test.mm b/test/osx/test.mm index 4630368b7..c0a91833b 100644 --- a/test/osx/test.mm +++ b/test/osx/test.mm @@ -68,7 +68,7 @@ static SceneContext SetupScene() /* ------------------------------------------------------ */ /* create source */ SourceContext source{obs_source_create(OBS_SOURCE_TYPE_INPUT, - "random", "a test source", nullptr)}; + "random", "a test source", nullptr, nullptr)}; if (!source) throw "Couldn't create random test source"; diff --git a/test/win/test.cpp b/test/win/test.cpp index fe78c92e1..7621c53b5 100644 --- a/test/win/test.cpp +++ b/test/win/test.cpp @@ -158,14 +158,15 @@ int WINAPI WinMain(HINSTANCE instance, HINSTANCE prevInstance, LPSTR cmdLine, /* ------------------------------------------------------ */ /* create source */ SourceContext source = obs_source_create(OBS_SOURCE_TYPE_INPUT, - "random", "some randon source", NULL); + "random", "some randon source", NULL, nullptr); if (!source) throw "Couldn't create random test source"; /* ------------------------------------------------------ */ /* create filter */ SourceContext filter = obs_source_create(OBS_SOURCE_TYPE_FILTER, - "test_filter", "a nice green filter", NULL); + "test_filter", "a nice green filter", NULL, + nullptr); if (!filter) throw "Couldn't create test filter"; obs_source_filter_add(source, filter); From 7b3b083889250b430cd9a1f0aef85e145c480699 Mon Sep 17 00:00:00 2001 From: Palana Date: Fri, 1 May 2015 03:22:12 +0200 Subject: [PATCH 06/18] libobs: Add audio source hotkeys --- libobs/obs-hotkey.c | 12 +++ libobs/obs-hotkey.h | 3 + libobs/obs-internal.h | 17 +++ libobs/obs-source.c | 240 +++++++++++++++++++++++++++++++++++++++++- libobs/obs.c | 21 ++++ libobs/obs.h | 14 +++ 6 files changed, 306 insertions(+), 1 deletion(-) diff --git a/libobs/obs-hotkey.c b/libobs/obs-hotkey.c index d6507c161..c49316237 100644 --- a/libobs/obs-hotkey.c +++ b/libobs/obs-hotkey.c @@ -1495,3 +1495,15 @@ void obs_hotkey_update_atomic(obs_hotkey_atomic_update_func func, void *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 +} diff --git a/libobs/obs-hotkey.h b/libobs/obs-hotkey.h index 8b5f20262..17d9f850b 100644 --- a/libobs/obs-hotkey.h +++ b/libobs/obs-hotkey.h @@ -120,6 +120,9 @@ EXPORT void obs_hotkeys_set_translations_s( obs_hotkeys_set_translations_s(translations, \ sizeof(struct obs_hotkeys_translations)) +EXPORT void obs_hotkeys_set_audio_hotkeys_translations( + const char *mute, const char *unmute, + const char *push_to_mute, const char *push_to_talk); /* registering hotkeys (giving hotkeys a name and a function) */ diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index a5dd80e6a..dbdd34064 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -299,6 +299,10 @@ struct obs_core_hotkeys { signal_handler_t *signals; char *translations[OBS_KEY_LAST_VALUE]; + char *mute; + char *unmute; + char *push_to_mute; + char *push_to_talk; }; struct obs_core { @@ -512,6 +516,19 @@ struct obs_source { gs_texrender_t *filter_texrender; enum obs_allow_direct_render allow_direct; bool rendering_filter; + + /* sources specific hotkeys */ + obs_hotkey_pair_id mute_unmute_key; + obs_hotkey_id push_to_mute_key; + obs_hotkey_id push_to_talk_key; + bool push_to_mute_enabled : 1; + bool push_to_mute_pressed : 1; + bool push_to_talk_enabled : 1; + bool push_to_talk_pressed : 1; + uint64_t push_to_mute_delay; + uint64_t push_to_mute_stop_time; + uint64_t push_to_talk_delay; + uint64_t push_to_talk_stop_time; }; extern const struct obs_source_info *find_source(struct darray *list, diff --git a/libobs/obs-source.c b/libobs/obs-source.c index 230285577..072283fc9 100644 --- a/libobs/obs-source.c +++ b/libobs/obs-source.c @@ -79,6 +79,10 @@ static const char *source_signals[] = { "void show(ptr source)", "void hide(ptr source)", "void mute(ptr source, bool muted)", + "void push_to_mute_changed(ptr source, bool enabled)", + "void push_to_mute_delay(ptr source, int delay)", + "void push_to_talk_changed(ptr source, bool enabled)", + "void push_to_talk_delay(ptr source, int delay)", "void enable(ptr source, bool enabled)", "void rename(ptr source, string new_name, string prev_name)", "void volume(ptr source, in out float volume)", @@ -155,6 +159,83 @@ bool obs_source_init(struct obs_source *source, return true; } +static bool obs_source_hotkey_mute(void *data, + obs_hotkey_pair_id id, obs_hotkey_t *key, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(key); + + struct obs_source *source = data; + + if (!pressed || obs_source_muted(source)) return false; + + obs_source_set_muted(source, true); + return true; +} + +static bool obs_source_hotkey_unmute(void *data, + obs_hotkey_pair_id id, obs_hotkey_t *key, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(key); + + struct obs_source *source = data; + + if (!pressed || !obs_source_muted(source)) return false; + + obs_source_set_muted(source, false); + return true; +} + +static void obs_source_hotkey_push_to_mute(void *data, + obs_hotkey_id id, obs_hotkey_t *key, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(key); + + struct obs_source *source = data; + + pthread_mutex_lock(&source->audio_mutex); + source->push_to_mute_pressed = pressed; + pthread_mutex_unlock(&source->audio_mutex); +} + +static void obs_source_hotkey_push_to_talk(void *data, + obs_hotkey_id id, obs_hotkey_t *key, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(key); + + struct obs_source *source = data; + + pthread_mutex_lock(&source->audio_mutex); + source->push_to_talk_pressed = pressed; + pthread_mutex_unlock(&source->audio_mutex); +} + +static void obs_source_init_audio_hotkeys(struct obs_source *source) +{ + if (!(source->info.output_flags & OBS_SOURCE_AUDIO)) { + source->mute_unmute_key = OBS_INVALID_HOTKEY_ID; + source->push_to_talk_key = OBS_INVALID_HOTKEY_ID; + return; + } + + source->mute_unmute_key = obs_hotkey_pair_register_source(source, + "libobs.mute", obs->hotkeys.mute, + "libobs.unmute", obs->hotkeys.unmute, + obs_source_hotkey_mute, obs_source_hotkey_unmute, + source, source); + + source->push_to_mute_key = obs_hotkey_register_source(source, + "libobs.push-to-mute", obs->hotkeys.push_to_mute, + obs_source_hotkey_push_to_mute, source); + + source->push_to_talk_key = obs_hotkey_register_source(source, + "libobs.push-to-talk", obs->hotkeys.push_to_talk, + obs_source_hotkey_push_to_talk, source); +} + static inline void obs_source_dosignal(struct obs_source *source, const char *signal_obs, const char *signal_source) { @@ -186,6 +267,10 @@ obs_source_t *obs_source_create(enum obs_source_type type, const char *id, source->info = *info; } + source->mute_unmute_key = OBS_INVALID_HOTKEY_PAIR_ID; + source->push_to_mute_key = OBS_INVALID_HOTKEY_ID; + source->push_to_talk_key = OBS_INVALID_HOTKEY_ID; + if (!obs_source_init_context(source, settings, name, hotkey_data)) goto fail; @@ -195,6 +280,8 @@ obs_source_t *obs_source_create(enum obs_source_type type, const char *id, if (!obs_source_init(source, info)) goto fail; + obs_source_init_audio_hotkeys(source); + /* allow the source to be created even if creation fails so that the * user's data doesn't become lost */ if (info) @@ -274,6 +361,10 @@ void obs_source_destroy(struct obs_source *source) source->context.data = NULL; } + obs_hotkey_unregister(source->push_to_talk_key); + obs_hotkey_unregister(source->push_to_mute_key); + obs_hotkey_pair_unregister(source->mute_unmute_key); + for (i = 0; i < source->async_cache.num; i++) obs_source_frame_decref(source->async_cache.array[i].frame); @@ -829,7 +920,22 @@ static void source_output_audio_line(obs_source_t *source, source->present_volume * obs->audio.user_volume * obs->audio.present_volume; - bool muted = !source->enabled || source->muted; + if (source->push_to_mute_enabled && source->push_to_mute_pressed) + source->push_to_mute_stop_time = os_time + + source->push_to_mute_delay * 1000000; + + if (source->push_to_talk_enabled && source->push_to_talk_pressed) + source->push_to_talk_stop_time = os_time + + source->push_to_talk_delay * 1000000; + + bool push_to_mute_active = source->push_to_mute_pressed || + os_time < source->push_to_mute_stop_time; + bool push_to_talk_active = source->push_to_talk_pressed || + os_time < source->push_to_talk_stop_time; + + bool muted = !source->enabled || source->muted || + (source->push_to_mute_enabled && push_to_mute_active) || + (source->push_to_talk_enabled && !push_to_talk_active); if (muted) in.volume = 0.0f; @@ -2820,3 +2926,135 @@ void obs_source_set_muted(obs_source_t *source, bool muted) calldata_free(&data); } + +static void source_signal_push_to_changed(obs_source_t *source, + const char *signal, bool enabled) +{ + struct calldata data = {0}; + + calldata_set_ptr (&data, "source", source); + calldata_set_bool(&data, "enabled", enabled); + + signal_handler_signal(source->context.signals, signal, &data); + calldata_free(&data); +} + +static void source_signal_push_to_delay(obs_source_t *source, + const char *signal, uint64_t delay) +{ + struct calldata data = {0}; + + calldata_set_ptr (&data, "source", source); + calldata_set_bool(&data, "delay", delay); + + signal_handler_signal(source->context.signals, signal, &data); + calldata_free(&data); +} + +bool obs_source_push_to_mute_enabled(obs_source_t *source) +{ + bool enabled; + if (!source) return false; + + pthread_mutex_lock(&source->audio_mutex); + enabled = source->push_to_mute_enabled; + pthread_mutex_unlock(&source->audio_mutex); + + return enabled; +} + +void obs_source_enable_push_to_mute(obs_source_t *source, bool enabled) +{ + if (!source) return; + + pthread_mutex_lock(&source->audio_mutex); + bool changed = source->push_to_mute_enabled != enabled; + if (obs_source_get_output_flags(source) & OBS_SOURCE_AUDIO && changed) + blog(LOG_INFO, "source '%s' %s push-to-mute", + obs_source_get_name(source), + enabled ? "enabled" : "disabled"); + + source->push_to_mute_enabled = enabled; + + if (changed) + source_signal_push_to_changed(source, "push_to_mute_changed", + enabled); + pthread_mutex_unlock(&source->audio_mutex); +} + +uint64_t obs_source_get_push_to_mute_delay(obs_source_t *source) +{ + uint64_t delay; + if (!source) return 0; + + pthread_mutex_lock(&source->audio_mutex); + delay = source->push_to_mute_delay; + pthread_mutex_unlock(&source->audio_mutex); + + return delay; +} + +void obs_source_set_push_to_mute_delay(obs_source_t *source, uint64_t delay) +{ + if (!source) return; + + pthread_mutex_lock(&source->audio_mutex); + source->push_to_mute_delay = delay; + + source_signal_push_to_delay(source, "push_to_mute_delay", delay); + pthread_mutex_unlock(&source->audio_mutex); +} + +bool obs_source_push_to_talk_enabled(obs_source_t *source) +{ + bool enabled; + if (!source) return false; + + pthread_mutex_lock(&source->audio_mutex); + enabled = source->push_to_talk_enabled; + pthread_mutex_unlock(&source->audio_mutex); + + return enabled; +} + +void obs_source_enable_push_to_talk(obs_source_t *source, bool enabled) +{ + if (!source) return; + + pthread_mutex_lock(&source->audio_mutex); + bool changed = source->push_to_talk_enabled != enabled; + if (obs_source_get_output_flags(source) & OBS_SOURCE_AUDIO && changed) + blog(LOG_INFO, "source '%s' %s push-to-talk", + obs_source_get_name(source), + enabled ? "enabled" : "disabled"); + + source->push_to_talk_enabled = enabled; + + if (changed) + source_signal_push_to_changed(source, "push_to_talk_changed", + enabled); + pthread_mutex_unlock(&source->audio_mutex); +} + +uint64_t obs_source_get_push_to_talk_delay(obs_source_t *source) +{ + uint64_t delay; + if (!source) return 0; + + pthread_mutex_lock(&source->audio_mutex); + delay = source->push_to_talk_delay; + pthread_mutex_unlock(&source->audio_mutex); + + return delay; +} + +void obs_source_set_push_to_talk_delay(obs_source_t *source, uint64_t delay) +{ + if (!source) return; + + pthread_mutex_lock(&source->audio_mutex); + source->push_to_talk_delay = delay; + + source_signal_push_to_delay(source, "push_to_talk_delay", delay); + pthread_mutex_unlock(&source->audio_mutex); +} diff --git a/libobs/obs.c b/libobs/obs.c index 235ac472c..eede4f4f7 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -642,6 +642,10 @@ static inline bool obs_init_hotkeys(void) da_init(hotkeys->hotkeys); hotkeys->signals = obs->signals; hotkeys->name_map_init_token = obs_pthread_once_init_token; + hotkeys->mute = bstrdup("Mute"); + hotkeys->unmute = bstrdup("Unmute"); + hotkeys->push_to_mute = bstrdup("Push-to-mute"); + hotkeys->push_to_talk = bstrdup("Push-to-talk"); if (!obs_hotkeys_platform_init(hotkeys)) return false; @@ -687,6 +691,11 @@ static inline void obs_free_hotkeys(void) { struct obs_core_hotkeys *hotkeys = &obs->hotkeys; + bfree(hotkeys->mute); + bfree(hotkeys->unmute); + bfree(hotkeys->push_to_mute); + bfree(hotkeys->push_to_talk); + obs_hotkey_name_map_free(); obs_hotkeys_platform_free(hotkeys); @@ -1451,6 +1460,14 @@ static obs_source_t *obs_load_source_type(obs_data_t *source_data, obs_data_set_default_bool(source_data, "muted", false); obs_source_set_muted(source, obs_data_get_bool(source_data, "muted")); + obs_data_set_default_bool(source_data, "push-to-talk", false); + obs_source_enable_push_to_talk(source, + obs_data_get_bool(source_data, "push-to-talk")); + + obs_data_set_default_int(source_data, "push-to-talk-delay", 0); + obs_source_set_push_to_talk_delay(source, + obs_data_get_int(source_data, "push-to-talk-delay")); + if (filters) { size_t count = obs_data_array_count(filters); @@ -1524,6 +1541,8 @@ obs_data_t *obs_save_source(obs_source_t *source) const char *id = obs_source_get_id(source); bool enabled = obs_source_enabled(source); bool muted = obs_source_muted(source); + bool push_to_talk= obs_source_push_to_talk_enabled(source); + uint64_t ptt_delay = obs_source_get_push_to_talk_delay(source); obs_source_save(source); hotkeys = obs_hotkeys_save_source(source); @@ -1543,6 +1562,8 @@ obs_data_t *obs_save_source(obs_source_t *source) obs_data_set_double(source_data, "volume", volume); obs_data_set_bool (source_data, "enabled", enabled); obs_data_set_bool (source_data, "muted", muted); + obs_data_set_bool (source_data, "push-to-talk", push_to_talk); + obs_data_set_int (source_data, "push-to-talk-delay", ptt_delay); obs_data_set_obj (source_data, "hotkeys", hotkey_data); pthread_mutex_lock(&source->filter_mutex); diff --git a/libobs/obs.h b/libobs/obs.h index e7fa32b5d..30f2feaa8 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -865,6 +865,20 @@ EXPORT void obs_source_set_enabled(obs_source_t *source, bool enabled); EXPORT bool obs_source_muted(const obs_source_t *source); EXPORT void obs_source_set_muted(obs_source_t *source, bool muted); +EXPORT bool obs_source_push_to_mute_enabled(obs_source_t *source); +EXPORT void obs_source_enable_push_to_mute(obs_source_t *source, bool enabled); + +EXPORT uint64_t obs_source_get_push_to_mute_delay(obs_source_t *source); +EXPORT void obs_source_set_push_to_mute_delay(obs_source_t *source, + uint64_t delay); + +EXPORT bool obs_source_push_to_talk_enabled(obs_source_t *source); +EXPORT void obs_source_enable_push_to_talk(obs_source_t *source, bool enabled); + +EXPORT uint64_t obs_source_get_push_to_talk_delay(obs_source_t *source); +EXPORT void obs_source_set_push_to_talk_delay(obs_source_t *source, + uint64_t delay); + /* ------------------------------------------------------------------------- */ /* Functions used by sources */ From 23a2228142f6736f50562f7395aa2a34a1c5858d Mon Sep 17 00:00:00 2001 From: Palana Date: Sat, 9 May 2015 03:33:55 +0200 Subject: [PATCH 07/18] libobs: Add scene item hotkeys --- libobs/obs-hotkey.c | 10 ++++++ libobs/obs-hotkey.h | 3 ++ libobs/obs-internal.h | 2 ++ libobs/obs-scene.c | 79 +++++++++++++++++++++++++++++++++++++++++++ libobs/obs-scene.h | 2 ++ libobs/obs.c | 4 +++ 6 files changed, 100 insertions(+) diff --git a/libobs/obs-hotkey.c b/libobs/obs-hotkey.c index c49316237..b5f11cb5a 100644 --- a/libobs/obs-hotkey.c +++ b/libobs/obs-hotkey.c @@ -1507,3 +1507,13 @@ void obs_hotkeys_set_audio_hotkeys_translations( 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 +} diff --git a/libobs/obs-hotkey.h b/libobs/obs-hotkey.h index 17d9f850b..d5e62ce30 100644 --- a/libobs/obs-hotkey.h +++ b/libobs/obs-hotkey.h @@ -124,6 +124,9 @@ EXPORT void obs_hotkeys_set_audio_hotkeys_translations( const char *mute, const char *unmute, const char *push_to_mute, const char *push_to_talk); +EXPORT void obs_hotkeys_set_sceneitem_hotkeys_translations( + const char *show, const char *hide); + /* registering hotkeys (giving hotkeys a name and a function) */ typedef void (*obs_hotkey_func)(void *data, diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index dbdd34064..a95295c12 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -303,6 +303,8 @@ struct obs_core_hotkeys { char *unmute; char *push_to_mute; char *push_to_talk; + char *sceneitem_show; + char *sceneitem_hide; }; struct obs_core { diff --git a/libobs/obs-scene.c b/libobs/obs-scene.c index 0984e7aab..d6652dc31 100644 --- a/libobs/obs-scene.c +++ b/libobs/obs-scene.c @@ -555,6 +555,82 @@ void obs_scene_enum_items(obs_scene_t *scene, pthread_mutex_unlock(&scene->mutex); } +static obs_sceneitem_t *sceneitem_get_ref(obs_sceneitem_t *si) +{ + long owners = si->ref; + while (owners > 0) { + if (os_atomic_compare_swap_long(&si->ref, owners, owners + 1)) + return si; + + owners = si->ref; + } + return NULL; +} + +static bool hotkey_show_sceneitem(void *data, obs_hotkey_pair_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + obs_sceneitem_t *si = sceneitem_get_ref(data); + if (pressed && si && !si->visible) { + obs_sceneitem_set_visible(si, true); + obs_sceneitem_release(si); + return true; + } + + obs_sceneitem_release(si); + return false; +} + +static bool hotkey_hide_sceneitem(void *data, obs_hotkey_pair_id id, + obs_hotkey_t *hotkey, bool pressed) +{ + UNUSED_PARAMETER(id); + UNUSED_PARAMETER(hotkey); + + obs_sceneitem_t *si = sceneitem_get_ref(data); + if (pressed && si && si->visible) { + obs_sceneitem_set_visible(si, false); + obs_sceneitem_release(si); + return true; + } + + obs_sceneitem_release(si); + return false; +} + +static void init_hotkeys(obs_scene_t *scene, obs_sceneitem_t *item, + const char *name) +{ + struct dstr show = {0}; + struct dstr hide = {0}; + struct dstr show_desc = {0}; + struct dstr hide_desc = {0}; + + dstr_copy(&show, "libobs.show_scene_item.%1"); + dstr_replace(&show, "%1", name); + dstr_copy(&hide, "libobs.hide_scene_item.%1"); + dstr_replace(&hide, "%1", name); + + dstr_copy(&show_desc, obs->hotkeys.sceneitem_show); + dstr_replace(&show_desc, "%1", name); + dstr_copy(&hide_desc, obs->hotkeys.sceneitem_hide); + dstr_replace(&hide_desc, "%1", name); + + item->toggle_visibility = obs_hotkey_pair_register_source(scene->source, + show.array, show_desc.array, + hide.array, hide_desc.array, + hotkey_show_sceneitem, hotkey_hide_sceneitem, + item, item); + + dstr_free(&show); + dstr_free(&hide); + dstr_free(&show_desc); + dstr_free(&hide_desc); +} + obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source) { struct obs_scene_item *last; @@ -602,6 +678,8 @@ obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source) pthread_mutex_unlock(&scene->mutex); + init_hotkeys(scene, item, obs_source_get_name(source)); + calldata_set_ptr(¶ms, "scene", scene); calldata_set_ptr(¶ms, "item", item); signal_handler_signal(scene->source->context.signals, "item_add", @@ -614,6 +692,7 @@ obs_sceneitem_t *obs_scene_add(obs_scene_t *scene, obs_source_t *source) static void obs_sceneitem_destroy(obs_sceneitem_t *item) { if (item) { + obs_hotkey_pair_unregister(item->toggle_visibility); if (item->source) obs_source_release(item->source); bfree(item); diff --git a/libobs/obs-scene.h b/libobs/obs-scene.h index f827919da..433d7dea3 100644 --- a/libobs/obs-scene.h +++ b/libobs/obs-scene.h @@ -49,6 +49,8 @@ struct obs_scene_item { uint32_t bounds_align; struct vec2 bounds; + obs_hotkey_pair_id toggle_visibility; + /* would do **prev_next, but not really great for reordering */ struct obs_scene_item *prev; struct obs_scene_item *next; diff --git a/libobs/obs.c b/libobs/obs.c index eede4f4f7..e96399d38 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -646,6 +646,8 @@ static inline bool obs_init_hotkeys(void) hotkeys->unmute = bstrdup("Unmute"); hotkeys->push_to_mute = bstrdup("Push-to-mute"); hotkeys->push_to_talk = bstrdup("Push-to-talk"); + hotkeys->sceneitem_show = bstrdup("Show '%1'"); + hotkeys->sceneitem_hide = bstrdup("Hide '%1'"); if (!obs_hotkeys_platform_init(hotkeys)) return false; @@ -695,6 +697,8 @@ static inline void obs_free_hotkeys(void) bfree(hotkeys->unmute); bfree(hotkeys->push_to_mute); bfree(hotkeys->push_to_talk); + bfree(hotkeys->sceneitem_show); + bfree(hotkeys->sceneitem_hide); obs_hotkey_name_map_free(); From 9f0696c1d8a43fab0049f1c9592b4ecd0166389a Mon Sep 17 00:00:00 2001 From: Palana Date: Sun, 5 Apr 2015 00:03:40 +0200 Subject: [PATCH 08/18] UI: Preserve service hotkey data --- obs/window-basic-main.cpp | 4 +++- obs/window-basic-settings.cpp | 7 ++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index a3cb2dd5d..79ea469f2 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -406,11 +406,13 @@ bool OBSBasic::LoadService() type = obs_data_get_string(data, "type"); obs_data_t *settings = obs_data_get_obj(data, "settings"); + obs_data_t *hotkey_data = obs_data_get_obj(data, "hotkeys"); service = obs_service_create(type, "default_service", settings, - nullptr); + hotkey_data); obs_service_release(service); + obs_data_release(hotkey_data); obs_data_release(settings); obs_data_release(data); diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp index e6665dad4..5c2662662 100644 --- a/obs/window-basic-settings.cpp +++ b/obs/window-basic-settings.cpp @@ -1317,9 +1317,14 @@ void OBSBasicSettings::SaveStream1Settings() { QString streamType = GetComboData(ui->streamType); + obs_service_t *oldService = main->GetService(); + obs_data_t *hotkeyData = obs_hotkeys_save_service(oldService); + obs_service_t *newService = obs_service_create(QT_TO_UTF8(streamType), "default_service", streamProperties->GetSettings(), - nullptr); + hotkeyData); + + obs_data_release(hotkeyData); if (!newService) return; From c1a00817bbc4d897319c3ac22fba7440c344f9a3 Mon Sep 17 00:00:00 2001 From: Palana Date: Sat, 1 Nov 2014 21:45:01 +0100 Subject: [PATCH 09/18] UI: Add TranslateQtKeyboardEventModifiers --- obs/qt-wrappers.cpp | 26 ++++++++++++++++++++++++++ obs/qt-wrappers.hpp | 3 +++ 2 files changed, 29 insertions(+) diff --git a/obs/qt-wrappers.cpp b/obs/qt-wrappers.cpp index fd58a8d10..46afa1049 100644 --- a/obs/qt-wrappers.cpp +++ b/obs/qt-wrappers.cpp @@ -51,3 +51,29 @@ void QTToGSWindow(WId windowId, gs_window &gswindow) gswindow.display = QX11Info::display(); #endif } + +uint32_t TranslateQtKeyboardEventModifiers(Qt::KeyboardModifiers mods) +{ + int obsModifiers = INTERACT_NONE; + + if (mods.testFlag(Qt::ShiftModifier)) + obsModifiers |= INTERACT_SHIFT_KEY; + if (mods.testFlag(Qt::AltModifier)) + obsModifiers |= INTERACT_ALT_KEY; +#ifdef __APPLE__ + // Mac: Meta = Control, Control = Command + if (mods.testFlag(Qt::ControlModifier)) + obsModifiers |= INTERACT_COMMAND_KEY; + if (mods.testFlag(Qt::MetaModifier)) + obsModifiers |= INTERACT_CONTROL_KEY; +#else + // Handle windows key? Can a browser even trap that key? + if (mods.testFlag(Qt::ControlModifier)) + obsModifiers |= INTERACT_CONTROL_KEY; + if (mods.testFlag(Qt::MetaModifier)) + obsModifiers |= INTERACT_COMMAND_KEY; + +#endif + + return obsModifiers; +} diff --git a/obs/qt-wrappers.hpp b/obs/qt-wrappers.hpp index 6b095777a..3240ebe46 100644 --- a/obs/qt-wrappers.hpp +++ b/obs/qt-wrappers.hpp @@ -18,6 +18,7 @@ #pragma once #include +#include #define QT_UTF8(str) QString::fromUtf8(str) #define QT_TO_UTF8(str) str.toUtf8().constData() @@ -28,3 +29,5 @@ struct gs_window; void OBSErrorBox(QWidget *parent, const char *msg, ...); void QTToGSWindow(WId windowId, gs_window &gswindow); + +uint32_t TranslateQtKeyboardEventModifiers(Qt::KeyboardModifiers mods); From 9cda23f4b90d8fa541b3373f6b8591744c438138 Mon Sep 17 00:00:00 2001 From: Palana Date: Sat, 1 Nov 2014 21:48:58 +0100 Subject: [PATCH 10/18] UI: Add basic hotkey integration --- obs/data/locale/en-US.ini | 46 ++++++++++++ obs/obs-app.cpp | 114 +++++++++++++++++++++++++++++ obs/obs-app.hpp | 1 + obs/window-basic-adv-audio.cpp | 2 + obs/window-basic-filters.cpp | 2 + obs/window-basic-main.cpp | 71 ++++++++++++++++++ obs/window-basic-main.hpp | 6 ++ obs/window-basic-properties.cpp | 2 + obs/window-basic-settings.cpp | 2 + obs/window-basic-source-select.cpp | 2 + obs/window-basic-transform.cpp | 2 + obs/window-log-reply.cpp | 3 + obs/window-namedialog.cpp | 3 + obs/window-projector.cpp | 2 + obs/window-remux.cpp | 4 +- 15 files changed, 261 insertions(+), 1 deletion(-) diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index b8bec046a..b0d6ef557 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -350,3 +350,49 @@ Basic.AdvAudio.Mono="Downmix to Mono" Basic.AdvAudio.Panning="Panning" Basic.AdvAudio.SyncOffset="Sync Offset (ms)" Basic.AdvAudio.AudioTracks="Tracks" + +# hotkeys that may lack translation on certain operating systems +Hotkeys.Insert="Insert" +Hotkeys.Delete="Delete" +Hotkeys.Home="Home" +Hotkeys.End="End" +Hotkeys.PageUp="Page Up" +Hotkeys.PageDown="Page Down" +Hotkeys.NumLock="Num Lock" +Hotkeys.ScrollLock="Scroll Lock" +Hotkeys.CapsLock="Caps Lock" +Hotkeys.Backspace="Backspace" +Hotkeys.Tab="Tab" +Hotkeys.Print="Print" +Hotkeys.Pause="Pause" +Hotkeys.Left="Left" +Hotkeys.Right="Right" +Hotkeys.Up="Up" +Hotkeys.Down="Down" +Hotkeys.Windows="Windows" +Hotkeys.Super="Super" +Hotkeys.Menu="Menu" +Hotkeys.Space="Space" +Hotkeys.NumpadNum="Numpad %1" +Hotkeys.NumpadMultiply="Numpad Multiply" +Hotkeys.NumpadDivide="Numpad Divide" +Hotkeys.NumpadAdd="Numpad Add" +Hotkeys.NumpadSubtract="Numpad Subtract" +Hotkeys.NumpadDecimal="Numpad Decimal" +Hotkeys.AppleKeypadNum="%1 (Keypad)" +Hotkeys.AppleKeypadMultiply="* (Keypad)" +Hotkeys.AppleKeypadDivide="/ (Keypad)" +Hotkeys.AppleKeypadAdd="+ (Keypad)" +Hotkeys.AppleKeypadSubtract="- (Keypad)" +Hotkeys.AppleKeypadDecimal=". (Keypad)" +Hotkeys.AppleKeypadEqual="= (Keypad)" +Hotkeys.MouseButton="Mouse %1" + +# audio hotkeys +Mute="Mute" +Unmute="Unmute" +Push-to-talk="Push-to-talk" + +# scene item hotkeys +SceneItemShow="Show '%1'" +SceneItemHide="Hide '%1'" diff --git a/obs/obs-app.cpp b/obs/obs-app.cpp index a0190556f..f67db32e0 100644 --- a/obs/obs-app.cpp +++ b/obs/obs-app.cpp @@ -52,6 +52,112 @@ static log_handler_t def_log_handler; static string currentLogFile; static string lastLogFile; +QObject *CreateShortcutFilter() +{ + return new OBSEventFilter([](QObject *, QEvent *event) + { + auto mouse_event = [](QMouseEvent &event) + { + obs_key_combination_t hotkey = {0, OBS_KEY_NONE}; + bool pressed = event.type() == QEvent::MouseButtonPress; + + switch (event.button()) { + case Qt::NoButton: + case Qt::LeftButton: + case Qt::RightButton: + case Qt::AllButtons: + case Qt::MouseButtonMask: + return false; + + case Qt::MidButton: + hotkey.key = OBS_KEY_MOUSE3; + break; + +#define MAP_BUTTON(i, j) case Qt::ExtraButton ## i: \ + hotkey.key = OBS_KEY_MOUSE ## j; break; + MAP_BUTTON( 1, 4); + MAP_BUTTON( 2, 5); + MAP_BUTTON( 3, 6); + MAP_BUTTON( 4, 7); + MAP_BUTTON( 5, 8); + MAP_BUTTON( 6, 9); + MAP_BUTTON( 7, 10); + MAP_BUTTON( 8, 11); + MAP_BUTTON( 9, 12); + MAP_BUTTON(10, 13); + MAP_BUTTON(11, 14); + MAP_BUTTON(12, 15); + MAP_BUTTON(13, 16); + MAP_BUTTON(14, 17); + MAP_BUTTON(15, 18); + MAP_BUTTON(16, 19); + MAP_BUTTON(17, 20); + MAP_BUTTON(18, 21); + MAP_BUTTON(19, 22); + MAP_BUTTON(20, 23); + MAP_BUTTON(21, 24); + MAP_BUTTON(22, 25); + MAP_BUTTON(23, 26); + MAP_BUTTON(24, 27); +#undef MAP_BUTTON + } + + hotkey.modifiers = TranslateQtKeyboardEventModifiers( + event.modifiers()); + + obs_hotkey_inject_event(hotkey, pressed); + return true; + }; + + auto key_event = [](QKeyEvent *event) + { + obs_key_combination_t hotkey = {0, OBS_KEY_NONE}; + bool pressed = event->type() == QEvent::KeyPress; + + switch (event->key()) { + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Alt: + case Qt::Key_Meta: + break; + +#ifdef __APPLE__ + case Qt::Key_CapsLock: + // kVK_CapsLock == 57 + hotkey.key = obs_key_from_virtual_key(57); + pressed = true; + break; +#endif + + default: + hotkey.key = obs_key_from_virtual_key( + event->nativeVirtualKey()); + } + + hotkey.modifiers = TranslateQtKeyboardEventModifiers( + event->modifiers()); + + obs_hotkey_inject_event(hotkey, pressed); + }; + + switch (event->type()) { + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + return mouse_event(*static_cast(event)); + + /*case QEvent::MouseButtonDblClick: + case QEvent::Wheel:*/ + case QEvent::KeyPress: + case QEvent::KeyRelease: + key_event(static_cast(event)); + return true; + + default: + return false; + } + }); +} + string CurrentTimeString() { time_t now = time(0); @@ -330,6 +436,14 @@ bool OBSApp::OBSInit() mainWindow->OBSInit(); + connect(this, &QGuiApplication::applicationStateChanged, + [](Qt::ApplicationState state) + { + obs_hotkey_enable_background_press( + state != Qt::ApplicationActive); + }); + obs_hotkey_enable_background_press( + applicationState() != Qt::ApplicationActive); return true; } else { return false; diff --git a/obs/obs-app.hpp b/obs/obs-app.hpp index 79853f594..390f9dbb0 100644 --- a/obs/obs-app.hpp +++ b/obs/obs-app.hpp @@ -31,6 +31,7 @@ std::string CurrentTimeString(); std::string CurrentDateTimeString(); std::string GenerateTimeDateFilename(const char *extension); +QObject *CreateShortcutFilter(); struct BaseLexer { lexer lex; diff --git a/obs/window-basic-adv-audio.cpp b/obs/window-basic-adv-audio.cpp index 9d2dff45f..d0baa2405 100644 --- a/obs/window-basic-adv-audio.cpp +++ b/obs/window-basic-adv-audio.cpp @@ -63,6 +63,8 @@ OBSBasicAdvAudio::OBSBasicAdvAudio(QWidget *parent) vlayout->addWidget(scrollArea); setLayout(vlayout); + installEventFilter(CreateShortcutFilter()); + /* get global audio sources */ for (uint32_t i = 1; i <= 10; i++) { obs_source_t *source = obs_get_output_source(i); diff --git a/obs/window-basic-filters.cpp b/obs/window-basic-filters.cpp index c1b20d17b..2a865d997 100644 --- a/obs/window-basic-filters.cpp +++ b/obs/window-basic-filters.cpp @@ -66,6 +66,8 @@ OBSBasicFilters::OBSBasicFilters(QWidget *parent, OBSSource source_) const char *name = obs_source_get_name(source); setWindowTitle(QTStr("Basic.Filters.Title").arg(QT_UTF8(name))); + installEventFilter(CreateShortcutFilter()); + connect(ui->preview, SIGNAL(DisplayResized()), this, SLOT(OnPreviewResized())); diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 79ea469f2..576328401 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -118,6 +118,7 @@ OBSBasic::OBSBasic(QWidget *parent) qRegisterMetaType ("OBSScene"); qRegisterMetaType("OBSSceneItem"); qRegisterMetaType ("OBSSource"); + qRegisterMetaType("obs_hotkey_id"); ui->scenes->setAttribute(Qt::WA_MacShowFocusRect, false); ui->sources->setAttribute(Qt::WA_MacShowFocusRect, false); @@ -132,6 +133,8 @@ OBSBasic::OBSBasic(QWidget *parent) stringstream name; name << "OBS " << App()->GetVersionString(); + installEventFilter(CreateShortcutFilter()); + blog(LOG_INFO, "%s", name.str().c_str()); setWindowTitle(QT_UTF8(name.str().c_str())); @@ -653,6 +656,7 @@ void OBSBasic::OBSInit() } InitOBSCallbacks(); + InitHotkeys(); AddExtraModulePaths(); obs_load_all_modules(); @@ -681,6 +685,71 @@ void OBSBasic::OBSInit() Qt::QueuedConnection); } +void OBSBasic::InitHotkeys() +{ + struct obs_hotkeys_translations t = {}; + t.insert = Str("Hotkeys.Insert"); + t.del = Str("Hotkeys.Delete"); + t.home = Str("Hotkeys.Home"); + t.end = Str("Hotkeys.End"); + t.page_up = Str("Hotkeys.PageUp"); + t.page_down = Str("Hotkeys.PageDown"); + t.num_lock = Str("Hotkeys.NumLock"); + t.scroll_lock = Str("Hotkeys.ScrollLock"); + t.caps_lock = Str("Hotkeys.CapsLock"); + t.backspace = Str("Hotkeys.Backspace"); + t.tab = Str("Hotkeys.Tab"); + t.print = Str("Hotkeys.Print"); + t.pause = Str("Hotkeys.Pause"); + t.left = Str("Hotkeys.Left"); + t.right = Str("Hotkeys.Right"); + t.up = Str("Hotkeys.Up"); + t.down = Str("Hotkeys.Down"); +#ifdef _WIN32 + t.meta = Str("Hotkeys.Windows"); +#else + t.meta = Str("Hotkeys.Super"); +#endif + t.menu = Str("Hotkeys.Menu"); + t.space = Str("Hotkeys.Space"); + t.numpad_num = Str("Hotkeys.NumpadNum"); + t.numpad_multiply = Str("Hotkeys.NumpadMultiply"); + t.numpad_divide = Str("Hotkeys.NumpadDivide"); + t.numpad_plus = Str("Hotkeys.NumpadAdd"); + t.numpad_minus = Str("Hotkeys.NumpadSubtract"); + t.numpad_decimal = Str("Hotkeys.NumpadDecimal"); + t.apple_keypad_num = Str("Hotkeys.AppleKeypadNum"); + t.apple_keypad_multiply = Str("Hotkeys.AppleKeypadMultiply"); + t.apple_keypad_divide = Str("Hotkeys.AppleKeypadDivide"); + t.apple_keypad_plus = Str("Hotkeys.AppleKeypadAdd"); + t.apple_keypad_minus = Str("Hotkeys.AppleKeypadSubtract"); + t.apple_keypad_decimal = Str("Hotkeys.AppleKeypadDecimal"); + t.apple_keypad_equal = Str("Hotkeys.AppleKeypadEqual"); + t.mouse_num = Str("Hotkeys.MouseButton"); + obs_hotkeys_set_translations(&t); + + obs_hotkeys_set_audio_hotkeys_translations(Str("Mute"), Str("Unmute"), + Str("Push-to-talk"), Str("Push-to-mute")); + + obs_hotkeys_set_sceneitem_hotkeys_translations( + Str("SceneItemShow"), Str("SceneItemHide")); + + obs_hotkey_enable_callback_rerouting(true); + obs_hotkey_set_callback_routing_func(OBSBasic::HotkeyTriggered, this); +} + +void OBSBasic::ProcessHotkey(obs_hotkey_id id, bool pressed) +{ + obs_hotkey_trigger_routed_callback(id, pressed); +} + +void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) +{ + OBSBasic &basic = *static_cast(data); + QMetaObject::invokeMethod(&basic, "ProcessHotkey", + Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); +} + OBSBasic::~OBSBasic() { bool previewEnabled = obs_preview_enabled(); @@ -694,6 +763,8 @@ OBSBasic::~OBSBasic() delete cpuUsageTimer; os_cpu_usage_info_destroy(cpuUsageInfo); + obs_hotkey_set_callback_routing_func(nullptr, nullptr); + service = nullptr; outputHandler.reset(); diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index 3f6013498..91c8c22a2 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -112,6 +112,8 @@ private: void Save(const char *file); void Load(const char *file); + void InitHotkeys(); + bool InitService(); bool InitBasicConfigDefaults(); @@ -177,6 +179,8 @@ private slots: void ReorderSources(OBSScene scene); + void ProcessHotkey(obs_hotkey_id id, bool pressed); + private: /* OBS Callbacks */ static void SceneReordered(void *data, calldata_t *params); @@ -199,6 +203,8 @@ private: void AddSourcePopupMenu(const QPoint &pos); void copyActionsDynamicProperties(); + static void HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed); + public: OBSScene GetCurrentScene(); diff --git a/obs/window-basic-properties.cpp b/obs/window-basic-properties.cpp index ee2cc5eb3..ff69aa255 100644 --- a/obs/window-basic-properties.cpp +++ b/obs/window-basic-properties.cpp @@ -78,6 +78,8 @@ OBSBasicProperties::OBSBasicProperties(QWidget *parent, OBSSource source_) view->setMinimumHeight(150); view->show(); + installEventFilter(CreateShortcutFilter()); + connect(view, SIGNAL(PropertiesResized()), this, SLOT(OnPropertiesResized())); diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp index 5c2662662..483bf8735 100644 --- a/obs/window-basic-settings.cpp +++ b/obs/window-basic-settings.cpp @@ -307,6 +307,8 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) // Initialize libff library ff_init(); + installEventFilter(CreateShortcutFilter()); + LoadServiceTypes(); LoadEncoderTypes(); LoadColorRanges(); diff --git a/obs/window-basic-source-select.cpp b/obs/window-basic-source-select.cpp index c426ae9c4..aa9f8a4c2 100644 --- a/obs/window-basic-source-select.cpp +++ b/obs/window-basic-source-select.cpp @@ -181,5 +181,7 @@ OBSBasicSourceSelect::OBSBasicSourceSelect(OBSBasic *parent, const char *id_) ui->sourceName->setFocus(); //Fixes deselect of text. ui->sourceName->selectAll(); + installEventFilter(CreateShortcutFilter()); + obs_enum_sources(EnumSources, this); } diff --git a/obs/window-basic-transform.cpp b/obs/window-basic-transform.cpp index 0dca0f496..f9f9d9843 100644 --- a/obs/window-basic-transform.cpp +++ b/obs/window-basic-transform.cpp @@ -50,6 +50,8 @@ OBSBasicTransform::OBSBasicTransform(OBSBasic *parent) HookWidget(ui->boundsWidth, DSCROLL_CHANGED, SLOT(OnControlChanged())); HookWidget(ui->boundsHeight, DSCROLL_CHANGED, SLOT(OnControlChanged())); + installEventFilter(CreateShortcutFilter()); + OBSScene curScene = main->GetCurrentScene(); SetScene(curScene); SetItem(FindASelectedItem(curScene)); diff --git a/obs/window-log-reply.cpp b/obs/window-log-reply.cpp index 0fc7e57fc..5f74a6d47 100644 --- a/obs/window-log-reply.cpp +++ b/obs/window-log-reply.cpp @@ -17,6 +17,7 @@ #include #include "window-log-reply.hpp" +#include "obs-app.hpp" OBSLogReply::OBSLogReply(QWidget *parent, const QString &url) : QDialog (parent), @@ -24,6 +25,8 @@ OBSLogReply::OBSLogReply(QWidget *parent, const QString &url) { ui->setupUi(this); ui->urlEdit->setText(url); + + installEventFilter(CreateShortcutFilter()); } void OBSLogReply::on_copyURL_clicked() diff --git a/obs/window-namedialog.cpp b/obs/window-namedialog.cpp index a51e44485..380db0b19 100644 --- a/obs/window-namedialog.cpp +++ b/obs/window-namedialog.cpp @@ -17,6 +17,7 @@ #include "window-namedialog.hpp" #include "ui_NameDialog.h" +#include "obs-app.hpp" using namespace std; @@ -25,6 +26,8 @@ NameDialog::NameDialog(QWidget *parent) ui (new Ui::NameDialog) { ui->setupUi(this); + + installEventFilter(CreateShortcutFilter()); } bool NameDialog::AskForName(QWidget *parent, const QString &title, diff --git a/obs/window-projector.cpp b/obs/window-projector.cpp index 44efe4ce1..904e6eb36 100644 --- a/obs/window-projector.cpp +++ b/obs/window-projector.cpp @@ -15,6 +15,8 @@ OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_) "removed", OBSSourceRemoved, this) { setAttribute(Qt::WA_DeleteOnClose, true); + + installEventFilter(CreateShortcutFilter()); } OBSProjector::~OBSProjector() diff --git a/obs/window-remux.cpp b/obs/window-remux.cpp index f83f418cd..2802f3ed6 100644 --- a/obs/window-remux.cpp +++ b/obs/window-remux.cpp @@ -47,7 +47,9 @@ OBSRemux::OBSRemux(const char *path, QWidget *parent) ui->progressBar->setMinimum(0); ui->progressBar->setMaximum(1000); ui->progressBar->setValue(0); - + + installEventFilter(CreateShortcutFilter()); + connect(ui->browseSource, &QPushButton::clicked, [&]() { BrowseInput(); }); connect(ui->browseTarget, &QPushButton::clicked, From b33be9fca6c5cf9600617d4c231cc8023e0a74c5 Mon Sep 17 00:00:00 2001 From: Palana Date: Sat, 1 Nov 2014 21:44:01 +0100 Subject: [PATCH 11/18] UI: Add OBSHotkey{Edit,Label,Widget} OBSHotkeyEdit is similar to QKeySequenceEdit, except less terribad on OSX, while OBSHotkeyWidget provides a container that automagically encapsulates multiple bindings per hotkey OBSHotkeyLabel in combination with OBSHotkeyWidget provides mechanisms for dealing with hotkey pairs (see upcoming usage in window-basic-settings.cpp) --- obs/CMakeLists.txt | 2 + obs/data/locale/en-US.ini | 2 + obs/data/themes/Dark.qss | 4 + obs/data/themes/Default.qss | 4 + obs/hotkey-edit.cpp | 471 ++++++++++++++++++++++++++++++++++++ obs/hotkey-edit.hpp | 158 ++++++++++++ 6 files changed, 641 insertions(+) create mode 100644 obs/hotkey-edit.cpp create mode 100644 obs/hotkey-edit.hpp diff --git a/obs/CMakeLists.txt b/obs/CMakeLists.txt index e96625e49..4653ee704 100644 --- a/obs/CMakeLists.txt +++ b/obs/CMakeLists.txt @@ -117,6 +117,7 @@ set(obs_SOURCES slider-absoluteset-style.cpp source-list-widget.cpp crash-report.cpp + hotkey-edit.cpp qt-wrappers.cpp) set(obs_HEADERS @@ -153,6 +154,7 @@ set(obs_HEADERS source-list-widget.hpp qt-display.hpp crash-report.hpp + hotkey-edit.hpp qt-wrappers.hpp) set(obs_UI diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index b0d6ef557..3bb763897 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -35,6 +35,8 @@ DroppedFrames="Dropped Frames %1 (%2%)" PreviewProjector="Fullscreen Projector (Preview)" SceneProjector="Fullscreen Projector (Scene)" SourceProjector="Fullscreen Projector (Source)" +Clear="Clear" +Revert="Revert" # "name already exists" dialog box NameExists.Title="Name already exists" diff --git a/obs/data/themes/Dark.qss b/obs/data/themes/Dark.qss index 9c776d401..ac4112280 100644 --- a/obs/data/themes/Dark.qss +++ b/obs/data/themes/Dark.qss @@ -487,3 +487,7 @@ MuteCheckBox::indicator:checked { MuteCheckBox::indicator:unchecked { image: url(./Dark/unmute.png); } + +OBSHotkeyLabel[hotkeyPairHover=true] { + color: red; +} diff --git a/obs/data/themes/Default.qss b/obs/data/themes/Default.qss index 92e479586..91df9b610 100644 --- a/obs/data/themes/Default.qss +++ b/obs/data/themes/Default.qss @@ -47,6 +47,10 @@ MuteCheckBox::indicator:unchecked { image: url(:/res/images/unmute.png); } +OBSHotkeyLabel[hotkeyPairHover=true] { + color: red; +} + /* Volume Control */ diff --git a/obs/hotkey-edit.cpp b/obs/hotkey-edit.cpp new file mode 100644 index 000000000..fa16c5357 --- /dev/null +++ b/obs/hotkey-edit.cpp @@ -0,0 +1,471 @@ +/****************************************************************************** + Copyright (C) 2014-2015 by Ruwen Hahn + + 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 . +******************************************************************************/ + +#include "hotkey-edit.hpp" + +#include +#include +#include + +#include "obs-app.hpp" +#include "qt-wrappers.hpp" + +static inline bool operator!=(const obs_key_combination_t &c1, + const obs_key_combination_t &c2) +{ + return c1.modifiers != c2.modifiers || c1.key != c2.key; +} + +static inline bool operator==(const obs_key_combination_t &c1, + const obs_key_combination_t &c2) +{ + return !(c1 != c2); +} + +void OBSHotkeyEdit::keyPressEvent(QKeyEvent *event) +{ + if (event->isAutoRepeat()) + return; + + obs_key_combination_t new_key; + + switch (event->key()) { + case Qt::Key_Shift: + case Qt::Key_Control: + case Qt::Key_Alt: + case Qt::Key_Meta: + new_key.key = OBS_KEY_NONE; + break; + +#ifdef __APPLE__ + case Qt::Key_CapsLock: + // kVK_CapsLock == 57 + new_key.key = obs_key_from_virtual_key(57); + break; +#endif + + default: + new_key.key = + obs_key_from_virtual_key(event->nativeVirtualKey()); + } + + new_key.modifiers = + TranslateQtKeyboardEventModifiers(event->modifiers()); + + HandleNewKey(new_key); +} + +#ifdef __APPLE__ +void OBSHotkeyEdit::keyReleaseEvent(QKeyEvent *event) +{ + if (event->isAutoRepeat()) + return; + + if (event->key() != Qt::Key_CapsLock) + return; + + obs_key_combination_t new_key; + + // kVK_CapsLock == 57 + new_key.key = obs_key_from_virtual_key(57); + new_key.modifiers = + TranslateQtKeyboardEventModifiers(event->modifiers()); + + HandleNewKey(new_key); +} +#endif + +void OBSHotkeyEdit::mousePressEvent(QMouseEvent *event) +{ + obs_key_combination_t new_key; + + switch (event->button()) { + case Qt::NoButton: + case Qt::LeftButton: + case Qt::RightButton: + case Qt::AllButtons: + case Qt::MouseButtonMask: + return; + + case Qt::MidButton: + new_key.key = OBS_KEY_MOUSE3; + break; + +#define MAP_BUTTON(i, j) case Qt::ExtraButton ## i: \ + new_key.key = OBS_KEY_MOUSE ## j; break; + MAP_BUTTON( 1, 4); + MAP_BUTTON( 2, 5); + MAP_BUTTON( 3, 6); + MAP_BUTTON( 4, 7); + MAP_BUTTON( 5, 8); + MAP_BUTTON( 6, 9); + MAP_BUTTON( 7, 10); + MAP_BUTTON( 8, 11); + MAP_BUTTON( 9, 12); + MAP_BUTTON(10, 13); + MAP_BUTTON(11, 14); + MAP_BUTTON(12, 15); + MAP_BUTTON(13, 16); + MAP_BUTTON(14, 17); + MAP_BUTTON(15, 18); + MAP_BUTTON(16, 19); + MAP_BUTTON(17, 20); + MAP_BUTTON(18, 21); + MAP_BUTTON(19, 22); + MAP_BUTTON(20, 23); + MAP_BUTTON(21, 24); + MAP_BUTTON(22, 25); + MAP_BUTTON(23, 26); + MAP_BUTTON(24, 27); +#undef MAP_BUTTON + } + + new_key.modifiers = + TranslateQtKeyboardEventModifiers(event->modifiers()); + + HandleNewKey(new_key); +} + +void OBSHotkeyEdit::HandleNewKey(obs_key_combination_t new_key) +{ + if (new_key == key) + return; + + key = new_key; + + changed = true; + emit KeyChanged(key); + + RenderKey(); +} + +void OBSHotkeyEdit::RenderKey() +{ + DStr str; + obs_key_combination_to_str(key, str); + + setText(QT_UTF8(str)); +} + +void OBSHotkeyEdit::ResetKey() +{ + key = original; + + changed = false; + emit KeyChanged(key); + + RenderKey(); +} + +void OBSHotkeyEdit::ClearKey() +{ + key = {0, OBS_KEY_NONE}; + + changed = true; + emit KeyChanged(key); + + RenderKey(); +} + +void OBSHotkeyEdit::InitSignalHandler() +{ + layoutChanged = {obs_get_signal_handler(), + "hotkey_layout_change", + [](void *this_, calldata_t*) + { + auto edit = static_cast(this_); + QMetaObject::invokeMethod(edit, "ReloadKeyLayout"); + }, this}; +} + +void OBSHotkeyEdit::ReloadKeyLayout() +{ + RenderKey(); +} + +void OBSHotkeyWidget::SetKeyCombinations( + const std::vector &combos) +{ + if (combos.empty()) + AddEdit({0, OBS_KEY_NONE}); + + for (auto combo : combos) + AddEdit(combo); +} + +bool OBSHotkeyWidget::Changed() const +{ + return changed || + std::any_of(begin(edits), end(edits), [](OBSHotkeyEdit *edit) + { + return edit->changed; + }); +} + +void OBSHotkeyWidget::Apply() +{ + for (auto &edit : edits) { + edit->original = edit->key; + edit->changed = false; + } + + changed = false; + + for (auto &revertButton : revertButtons) + revertButton->setEnabled(false); +} + +void OBSHotkeyWidget::GetCombinations( + std::vector &combinations) const +{ + combinations.clear(); + for (auto &edit : edits) + if (!obs_key_combination_is_empty(edit->key)) + combinations.emplace_back(edit->key); +} + +void OBSHotkeyWidget::Save() +{ + std::vector combinations; + Save(combinations); +} + +void OBSHotkeyWidget::Save(std::vector &combinations) +{ + GetCombinations(combinations); + Apply(); + + auto AtomicUpdate = [&]() + { + ignoreChangedBindings = true; + + obs_hotkey_load_bindings(id, + combinations.data(), combinations.size()); + + ignoreChangedBindings = false; + }; + using AtomicUpdate_t = decltype(&AtomicUpdate); + + obs_hotkey_update_atomic([](void *d) + { + (*static_cast(d))(); + }, static_cast(&AtomicUpdate)); +} + +void OBSHotkeyWidget::AddEdit(obs_key_combination combo, int idx) +{ + auto edit = new OBSHotkeyEdit(combo); + edit->setToolTip(toolTip); + + auto revert = new QPushButton; + revert->setText(QTStr("Revert")); + revert->setEnabled(false); + + auto clear = new QPushButton; + clear->setText(QTStr("Clear")); + clear->setEnabled(!obs_key_combination_is_empty(combo)); + + QObject::connect(edit, &OBSHotkeyEdit::KeyChanged, + [=](obs_key_combination_t new_combo) + { + clear->setEnabled(!obs_key_combination_is_empty(new_combo)); + revert->setEnabled(edit->original != new_combo); + }); + + auto add = new QPushButton; + add->setText("+"); + + auto remove = new QPushButton; + remove->setText("-"); + remove->setEnabled(removeButtons.size() > 0); + + auto CurrentIndex = [&, remove] + { + auto res = std::find(begin(removeButtons), + end(removeButtons), + remove); + return std::distance(begin(removeButtons), res); + }; + + QObject::connect(add, &QPushButton::clicked, + [&, CurrentIndex] + { + AddEdit({0, OBS_KEY_NONE}, CurrentIndex() + 1); + }); + + QObject::connect(remove, &QPushButton::clicked, + [&, CurrentIndex] + { + RemoveEdit(CurrentIndex()); + }); + + QHBoxLayout *subLayout = new QHBoxLayout; + subLayout->addWidget(edit); + subLayout->addWidget(revert); + subLayout->addWidget(clear); + subLayout->addWidget(add); + subLayout->addWidget(remove); + + if (removeButtons.size() == 1) + removeButtons.front()->setEnabled(true); + + if (idx != -1) { + revertButtons.insert(begin(revertButtons) + idx, revert); + removeButtons.insert(begin(removeButtons) + idx, remove); + edits.insert(begin(edits) + idx, edit); + } else { + revertButtons.emplace_back(revert); + removeButtons.emplace_back(remove); + edits.emplace_back(edit); + } + + layout()->insertLayout(idx, subLayout); + + QObject::connect(revert, &QPushButton::clicked, + edit, &OBSHotkeyEdit::ResetKey); + QObject::connect(clear, &QPushButton::clicked, + edit, &OBSHotkeyEdit::ClearKey); + + QObject::connect(edit, &OBSHotkeyEdit::KeyChanged, + [&](obs_key_combination) + { + emit KeyChanged(); + }); +} + +void OBSHotkeyWidget::RemoveEdit(size_t idx, bool signal) +{ + auto &edit = *(begin(edits) + idx); + if (!obs_key_combination_is_empty(edit->original) && signal) { + changed = true; + emit KeyChanged(); + } + + revertButtons.erase(begin(revertButtons) + idx); + removeButtons.erase(begin(removeButtons) + idx); + edits.erase(begin(edits) + idx); + + auto item = layout()->takeAt(static_cast(idx)); + QLayoutItem *child = nullptr; + while ((child = item->layout()->takeAt(0))) { + delete child->widget(); + delete child; + } + delete item; + + if (removeButtons.size() == 1) + removeButtons.front()->setEnabled(false); +} + +void OBSHotkeyWidget::BindingsChanged(void *data, calldata_t *param) +{ + auto widget = static_cast(data); + auto key = static_cast(calldata_ptr(param, "key")); + + QMetaObject::invokeMethod(widget, "HandleChangedBindings", + Q_ARG(obs_hotkey_id, obs_hotkey_get_id(key))); +} + +void OBSHotkeyWidget::HandleChangedBindings(obs_hotkey_id id_) +{ + if (ignoreChangedBindings || id != id_) return; + + std::vector bindings; + auto LoadBindings = [&](obs_hotkey_binding_t *binding) + { + if (obs_hotkey_binding_get_hotkey_id(binding) != id) return; + + auto get_combo = obs_hotkey_binding_get_key_combination; + bindings.push_back(get_combo(binding)); + }; + using LoadBindings_t = decltype(&LoadBindings); + + obs_enum_hotkey_bindings([](void *data, + size_t, obs_hotkey_binding_t *binding) + { + auto LoadBindings = *static_cast(data); + LoadBindings(binding); + return true; + }, static_cast(&LoadBindings)); + + while (edits.size() > 0) + RemoveEdit(edits.size() - 1, false); + + SetKeyCombinations(bindings); +} + +static inline void updateStyle(QWidget *widget) +{ + auto style = widget->style(); + style->unpolish(widget); + style->polish(widget); + widget->update(); +} + +void OBSHotkeyWidget::enterEvent(QEvent *event) +{ + if (!label) + return; + + event->accept(); + label->highlightPair(true); +} + +void OBSHotkeyWidget::leaveEvent(QEvent *event) +{ + if (!label) + return; + + event->accept(); + label->highlightPair(false); +} + +void OBSHotkeyLabel::highlightPair(bool highlight) +{ + if (!pairPartner) + return; + + pairPartner->setProperty("hotkeyPairHover", highlight); + updateStyle(pairPartner); + setProperty("hotkeyPairHover", highlight); + updateStyle(this); +} + +void OBSHotkeyLabel::enterEvent(QEvent *event) +{ + if (!pairPartner) + return; + + event->accept(); + highlightPair(true); +} + +void OBSHotkeyLabel::leaveEvent(QEvent *event) +{ + if (!pairPartner) + return; + + event->accept(); + highlightPair(false); +} + +void OBSHotkeyLabel::setToolTip(const QString &toolTip) +{ + QLabel::setToolTip(toolTip); + if (widget) + widget->setToolTip(toolTip); +} diff --git a/obs/hotkey-edit.hpp b/obs/hotkey-edit.hpp new file mode 100644 index 000000000..985d44b2e --- /dev/null +++ b/obs/hotkey-edit.hpp @@ -0,0 +1,158 @@ +/****************************************************************************** + Copyright (C) 2014-2015 by Ruwen Hahn + + 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 . +******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +class OBSHotkeyWidget; + +class OBSHotkeyLabel : public QLabel { + Q_OBJECT + +public: + QPointer pairPartner; + QPointer widget; + void highlightPair(bool highlight); + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; + void setToolTip(const QString &toolTip); +}; + +class OBSHotkeyEdit : public QLineEdit { + Q_OBJECT; + +public: + OBSHotkeyEdit(obs_key_combination_t original, + QWidget *parent=nullptr) + : QLineEdit(parent), + original(original) + { +#ifdef __APPLE__ + // disable the input cursor on OSX, focus should be clear + // enough with the default focus frame + setReadOnly(true); +#endif + setAttribute(Qt::WA_MacShowFocusRect, true); + InitSignalHandler(); + ResetKey(); + } + + obs_key_combination_t original; + obs_key_combination_t key; + bool changed = false; +protected: + OBSSignal layoutChanged; + + void InitSignalHandler(); + + void keyPressEvent(QKeyEvent *event) override; +#ifdef __APPLE__ + void keyReleaseEvent(QKeyEvent *event) override; +#endif + void mousePressEvent(QMouseEvent *event) override; + + void HandleNewKey(obs_key_combination_t new_key); + void RenderKey(); + +public slots: + void ReloadKeyLayout(); + void ResetKey(); + void ClearKey(); + +signals: + void KeyChanged(obs_key_combination_t); +}; + +class OBSHotkeyWidget : public QWidget { + Q_OBJECT; + +public: + OBSHotkeyWidget(obs_hotkey_id id, std::string name, + const std::vector &combos={}, + QWidget *parent=nullptr) + : QWidget(parent), + id(id), + name(name), + bindingsChanged(obs_get_signal_handler(), + "hotkey_bindings_changed", + &OBSHotkeyWidget::BindingsChanged, + this) + { + auto layout = new QVBoxLayout; + layout->setSpacing(0); + layout->setMargin(0); + setLayout(layout); + + SetKeyCombinations(combos); + } + + void SetKeyCombinations(const std::vector&); + + obs_hotkey_id id; + std::string name; + + bool changed = false; + bool Changed() const; + + QPointer label; + + QString toolTip; + void setToolTip(const QString &toolTip_) + { + toolTip = toolTip_; + for (auto &edit : edits) + edit->setToolTip(toolTip_); + } + + void Apply(); + void GetCombinations(std::vector&) const; + void Save(); + void Save(std::vector &combinations); + + void enterEvent(QEvent *event) override; + void leaveEvent(QEvent *event) override; + +private: + void AddEdit(obs_key_combination combo, int idx=-1); + void RemoveEdit(size_t idx, bool signal=true); + + static void BindingsChanged(void *data, calldata_t *param); + + std::vector> edits; + std::vector> removeButtons; + std::vector> revertButtons; + OBSSignal bindingsChanged; + bool ignoreChangedBindings = false; + + QVBoxLayout *layout() const + { + return dynamic_cast(QWidget::layout()); + } + +private slots: + void HandleChangedBindings(obs_hotkey_id id_); + +signals: + void KeyChanged(); +}; From 369d95dfc3ae2f061e8c7192b2eae6335e2a440d Mon Sep 17 00:00:00 2001 From: Palana Date: Sat, 2 May 2015 03:38:01 +0200 Subject: [PATCH 12/18] UI: Add OBSSourceLabel --- obs/CMakeLists.txt | 2 ++ obs/source-label.cpp | 44 ++++++++++++++++++++++++++++++++++++++ obs/source-label.hpp | 50 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) create mode 100644 obs/source-label.cpp create mode 100644 obs/source-label.hpp diff --git a/obs/CMakeLists.txt b/obs/CMakeLists.txt index 4653ee704..ea9a706e5 100644 --- a/obs/CMakeLists.txt +++ b/obs/CMakeLists.txt @@ -118,6 +118,7 @@ set(obs_SOURCES source-list-widget.cpp crash-report.cpp hotkey-edit.cpp + source-label.cpp qt-wrappers.cpp) set(obs_HEADERS @@ -155,6 +156,7 @@ set(obs_HEADERS qt-display.hpp crash-report.hpp hotkey-edit.hpp + source-label.hpp qt-wrappers.hpp) set(obs_UI diff --git a/obs/source-label.cpp b/obs/source-label.cpp new file mode 100644 index 000000000..a707f7db1 --- /dev/null +++ b/obs/source-label.cpp @@ -0,0 +1,44 @@ +/****************************************************************************** + Copyright (C) 2015 by Ruwen Hahn + + 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 . +******************************************************************************/ + +#include "source-label.hpp" + +void OBSSourceLabel::SourceRenamed(void *data, calldata_t *params) +{ + auto &label = *static_cast(data); + + const char *name = calldata_string(params, "new_name"); + label.setText(name); + + emit label.Renamed(name); +} + +void OBSSourceLabel::SourceRemoved(void *data, calldata_t *) +{ + auto &label = *static_cast(data); + emit label.Removed(); +} + +void OBSSourceLabel::SourceDestroyed(void *data, calldata_t *) +{ + auto &label = *static_cast(data); + emit label.Destroyed(); + + label.destroyedSignal.Disconnect(); + label.removedSignal.Disconnect(); + label.renamedSignal.Disconnect(); +} diff --git a/obs/source-label.hpp b/obs/source-label.hpp new file mode 100644 index 000000000..03b9be62a --- /dev/null +++ b/obs/source-label.hpp @@ -0,0 +1,50 @@ +/****************************************************************************** + Copyright (C) 2015 by Ruwen Hahn + + 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 . +******************************************************************************/ + +#include +#include + +class OBSSourceLabel : public QLabel { + Q_OBJECT; + +public: + OBSSignal renamedSignal; + OBSSignal removedSignal; + OBSSignal destroyedSignal; + + OBSSourceLabel(const obs_source_t *source, QWidget *parent=nullptr, + Qt::WindowFlags f=0) + : QLabel(obs_source_get_name(source), parent, f), + renamedSignal(obs_source_get_signal_handler(source), "rename", + &OBSSourceLabel::SourceRenamed, this), + removedSignal(obs_source_get_signal_handler(source), "remove", + &OBSSourceLabel::SourceRemoved, this), + destroyedSignal(obs_source_get_signal_handler(source), + "destroy", &OBSSourceLabel::SourceDestroyed, + this) + {} + +protected: + static void SourceRenamed(void *data, calldata_t *params); + static void SourceRemoved(void *data, calldata_t *params); + static void SourceDestroyed(void *data, calldata_t *params); + +signals: + void Renamed(const char *name); + void Removed(); + void Destroyed(); +}; From d28e80ae39b1e28f7d6b66544dce8a197957ec1a Mon Sep 17 00:00:00 2001 From: Palana Date: Sat, 1 Nov 2014 21:50:36 +0100 Subject: [PATCH 13/18] UI: Add hotkeys page in settings dialog --- obs/data/locale/en-US.ini | 4 + obs/forms/OBSBasicSettings.ui | 27 ++ ...preferences-desktop-keyboard-shortcuts.png | Bin 0 -> 4100 bytes obs/forms/obs.qrc | 1 + obs/window-basic-settings.cpp | 360 ++++++++++++++++++ obs/window-basic-settings.hpp | 15 +- 6 files changed, 406 insertions(+), 1 deletion(-) create mode 100644 obs/forms/images/settings/preferences-desktop-keyboard-shortcuts.png diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index 3bb763897..33f606f12 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -353,6 +353,10 @@ Basic.AdvAudio.Panning="Panning" Basic.AdvAudio.SyncOffset="Sync Offset (ms)" Basic.AdvAudio.AudioTracks="Tracks" +# basic mode 'hotkeys' settings +Basic.Settings.Hotkeys="Hotkeys" +Basic.Settings.Hotkeys.Pair="Key combinations shared with '%1' act as toggles" + # hotkeys that may lack translation on certain operating systems Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" diff --git a/obs/forms/OBSBasicSettings.ui b/obs/forms/OBSBasicSettings.ui index 9726f64fb..efe763354 100644 --- a/obs/forms/OBSBasicSettings.ui +++ b/obs/forms/OBSBasicSettings.ui @@ -87,6 +87,15 @@ :/settings/images/settings/video-display-3.png:/settings/images/settings/video-display-3.png + + + Basic.Settings.Hotkeys + + + + :/settings/images/settings/preferences-desktop-keyboard-shortcuts.png:/settings/images/settings/preferences-desktop-keyboard-shortcuts.png + + Basic.Settings.Advanced @@ -2330,6 +2339,24 @@ + + + true + + + + + 0 + + + QFormLayout::AllNonFixedFieldsGrow + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + diff --git a/obs/forms/images/settings/preferences-desktop-keyboard-shortcuts.png b/obs/forms/images/settings/preferences-desktop-keyboard-shortcuts.png new file mode 100644 index 0000000000000000000000000000000000000000..95a3aa87e45f6cfa819e5c063fd5a59e396934a0 GIT binary patch literal 4100 zcmai%XEYp4-^P~+8+8+VTP12FB+BlJAd6V5w7D4(b)$zSSaBgxeVUdlCRWg`LrpfdPpkQFNYef($A!&%nmQvcxry<5=+ z002EiMP61HH@j(MA8k6OcRm`BPUvg&w3f{FE;MaK#s+_t;ZNxhHkHyb(T&gpj!y?jrVpPfVb0yhL&K2*;A zCN3*^&c`(!(?$R>_Mn+YJP-M{G*{^M_Ng@gV+t++h`e81N)zA(fCq&}e&-u&0=z)l z)8&KZmy`FRfB`3~eGcyPG<|+d^76AM7Jyp-^}}e%leq&uir9#I*_`HqF58S<$`#=g znpHby0Fnb*D|1&!;a$ngN9+7Xzl(mQ_^Uw~Xr6{5e=sq)ap_hMu4C*;`vn0Ie=Z)& z?#STaU~?!_R7W(N$#VzEdy+t_ID3*efNPkwGiZAHuvu{VkzprYz*Byik8^AN)Sy0#W@$3;&nz2xl~-R^8VLARAZ&Nv0C$fL>UGp=+8 zD1qsuNWCX>q=B|@J|f&Y>R|Y6E0t>^vpZTMlDivg{ZcjmmQCM9zr54NRdd6O{#%JM zds~)_aR~!VMvoF=>$S z+&?7|rX9pq044kQA>5-(8ISpSS!pq`=N{9PG|!M++5qOFxOms;`m&_p5mraewn(`b zG?l({!qW9woFXo@FTc^LPs{8Ivrc(4h)v%xe8SHR0_rO;kDfeUvPGDmXZjZ^F|6cF zKRe=RFo=nc<%yjF#hVG=%g4rq5h7DFn?+|F#5^5cf+C_v5kEDtU1hl$B^Ci^4@$(; zur69pMV9mcrF585rUn~aK3RE%DuhyJ2xDZ7MoU9ee)mqs8SHt^LXkohmGuta9md7iz)rGnP z{$vapmF6r=hQ#vWEM~k^sjiMj0C^tJmg~U04gW^wV0G!A3sR6%GIA;Me!FfdXh^|B zUJ3?;Xj7}+HR+4oNJ$fEG**nM+)01pvZL?K&1OO4VC8k0okfd&DhM*KLhJY+KDrK6X1vNo$CTM(d>7W^2m=2UXe_-0mb zN{-+xW>Z6O#2T(Y9X_JSLJ%nDvY}xHDq-CtKFP!G1$iOcTGv1-Q{2c56*s}p8@I!r zE0}GEhA5)xTC@OUxvOLIUt~kW5N1)Z2w=M*hz!HVuMME+eHgKmd|u&B1q501+R_$Z zsUAXTbIKK42;$8sWS2glGhi=vuJiN17~JW7_S!;Me^Go?+&O9SP%0BcX*WbyHgD%c zCg;Ndq((mh0m)nJe6DMhJvCNbCW}zhsh-rJ5ThW&q7hzM>^1WyKI`mQ>y8Z5RD@`% zI|wYczV2)rGJG3%n~s?p&N>?<`+%N_k^=jyKUkLeITt8_wZ%1CBS%0meRN1K-6?>E z;_BLdMTx(cgl%r}=qDaCnzh!1cH0o*!AP&qV6t{sLXrd1VJGW?I~As9bkdsWV{PHG z0KBWMXspYomlqkXrX#Te!0C#MTO_I$yv?=L)&J3OR4W>&vUbqE`YN^jx8l<0*1`O< zB`LHPhad+J2kmpNsk-8C0)uwdDJA%($-F(AJ7;;*^|>VtJw-Llmp|i1FU~o?>P+v-qhgQu_2w;91SK%)aIJBtQ6<5_qD* z=oKd&gWjynqT(Gfj6t!BVXhB1ob`u1;#ZT(j`3pkp<-u;=~z+KOXw%(^e)P0fvt zT_5p?(1|g-0|7qGCf$JFI?dmkk7gj{lCY)|4xdv?PHzcXf+iDn7*&jztZDnE-SO{= zc-S<{7CRqXt(+VmJyWHChH0u8>%rK&k;$sSqZ)JH)sz4dM3a_|&fgD<-6D<|jg`Q3q*M$HZ8ac^@hf%Zj~&WTZII-$(0{7spD zabLiBRbTqXurc68*AKmD+8Kh*jqM26fkYi0oj6W}Xi+KA&@p+Z&Hma;?u|BCd25hN z<@UfrJPw*5+A=*oJ>0z!eUfB;=4FM$b?_CueJkVlv)`hWIIF!@*k)g&4sfD494;wLfX7j3G=Bdtl_1*Dhog1Nb2sD<(1XH*A2 z$N3W^WMc^xqo~eX72K#aIj{mhIBMKNXi;MHyDLfAoj`b}#eY-Uc|M4Pon4`RW^D;h zo-bNL2jZ&~PJtANi;Wgghn-%TYD;-5E-v2DoAIzd#F%zuESTrh&s*^FJxkm~!WetQ+Cq{x<)@i>q5E@d4$e`E$#6erM=}};S^CjX z>CKj%c59>)r;$J2e@vU9r4U16 zQ4*2Y$AQ5;+!bWem0BN-3+gGYI7>x16gqK8ym(|kzH4T&rtZqmFF6C0qZF_^LDtVI zmXIDj`z(Rd9==DcLut>H-S0((1CL~EjgQaow|uziEUZv9g2c(e?ti|r!NN-o3bA2W zVPCAhn(|R-qrVQ^@DGBIMVe6Fyma$c{OKoMPJ1n4PeIa}<;ZY#FulL>& zHAIIn?Nf}c#3TV+I7f!`q9~>BAy0zWw*R)-*WNVoU$1z#+V;eDyXo0y3C2{I(nAbU zUs6XnB^Fqvt(u*oKj74kt5>hO{+Fd3o{Pw`ZDfWyJiwfp)F-8vkF421Q@$|(tGc{s zZCqjn$EEKlL!oM=`f0W`QxLn6$vW3MH}zMm=wn{A{`SvLr2CgQM*WI5B%CHHaWzO3H49h-UD;^#9KpE%n6zFcMb+WW(@f4t&2grd&K54`X1PNBfgU#^ zj%LT~h_6+xHp^{ntXJ;vG03c>59NRP<&P2YWjiIvc#%d%WJp8MraIyMaRc)49D}4P z56@uFMm|^_Rb(Om9hP|$OHT00d+9sni>G?OKJ?6sK29?*wb*>$t1?uBeEZQ}3B0{v zLhu_9RU8Gg(YIm84JAX=J?awTa(;d4M`{<3hA8Gf6z&#NNx(V;A;b0PV=rCn6HXwG2)|` za{tDZ8``_HImi^$ z$8o#~n3pqsLnYolUa*(fyql(3`WJPWCR&q|v?I17zKSBF(FEsYfBov;F}xLjO*i4H zgl%wEnZf(>@9XUX6g(XI-z&eN=L5X8-awn(pkuCa%Q@4Pn?I#!X!dks6qj(|?*;T6 zQpd?|+;m!I0#?L9SZ50tpv^NjMBFvGY&AWSM+*kD0HJK@v?oz8Hv>eRGWxCvUJt@U z9t{*Adto5C5F>Zbk$-UP%}@0^t}|ZO85qKYesvbVF*3ZCI33nI^j&g{G+;as1{`i= ze#awE(~$T2x!ha6Yu0q(+|E0T^YN`})N*7fy^T2d4WLRm2i{;Mc$$6Ip;0e%A^Yx- zi#&6x!*=eAc&X=o(J5e#YYn_wogAIcGb8a^IkS&fR+Grd@Kk{`rpjDK^-jMZn5gtV z(b`;HqnqerbnIEkC<&Meq6=ivXr@GlA~}qT8mfA4%~bHY_BP%&?WE~{@}qj|p2Un- zwqYTZfv%MYl*uN+zixf3Gq-)y!gR22d8$)ywb?AhkIqYkFh;*fS$!MhZQAnoVMtFsU5W#Z~@ z)3h2{?9jJHdR@;8GgWp+?`#&NMf~D+K9P&meK!UZsim38^!z-h3i)_(#*MzPjJ~O+ zN?r5;;6B>GD4%t1@O04sD;4lccaTJobk>#DVJg}jzX<-nVNJd!Is6~LCS>#`vc$x< zm`=4Ft)lR8H60g6yUov|2`nR@sOm5Oi?`hr4BaeUtt{NEgq~g&fCvosPzWv}1e4H( ziy$7r5h8FQm^cCk3kjm``Y-UuiTIbg1mG|s7+eSr*M&Vqz~PAdm+&7T@W!F^{{kHG zPi?Ke|8Ia^6CHdBi2m7K%xI+*?L%6;vF6FP6T1Rs}0oQUkd@M zq@@lO5r#n>>?jNW8~AV5|8t}nxZn9D!15Y2j)G@bvT)wso>6SXww+{mDt& zl489yLH;$lyINWSl+ci^xlg{zyejT7GS WpVgou%kkcqUVw@MTD}a04fzj6iGf@I literal 0 HcmV?d00001 diff --git a/obs/forms/obs.qrc b/obs/forms/obs.qrc index e6a14d7ef..60ab59a4b 100644 --- a/obs/forms/obs.qrc +++ b/obs/forms/obs.qrc @@ -22,5 +22,6 @@ images/settings/applications-system-2.png images/settings/system-settings-3.png images/settings/network-bluetooth.png + images/settings/preferences-desktop-keyboard-shortcuts.png diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp index 483bf8735..99b637e78 100644 --- a/obs/window-basic-settings.cpp +++ b/obs/window-basic-settings.cpp @@ -28,7 +28,10 @@ #include #include #include +#include +#include "hotkey-edit.hpp" +#include "source-label.hpp" #include "obs-app.hpp" #include "platform.hpp" #include "properties-view.hpp" @@ -313,6 +316,26 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) LoadEncoderTypes(); LoadColorRanges(); LoadFormats(); + + auto ReloadHotkeys = [](void *data, calldata_t*) + { + auto settings = static_cast(data); + QMetaObject::invokeMethod(settings, "ReloadHotkeys"); + }; + hotkeyRegistered.Connect(obs_get_signal_handler(), "hotkey_register", + ReloadHotkeys, this); + + auto ReloadHotkeysIgnore = [](void *data, calldata_t *param) + { + auto settings = static_cast(data); + auto key = static_cast( + calldata_ptr(param,"key")); + QMetaObject::invokeMethod(settings, "ReloadHotkeys", + Q_ARG(obs_hotkey_id, obs_hotkey_get_id(key))); + }; + hotkeyUnregistered.Connect(obs_get_signal_handler(), + "hotkey_unregister", ReloadHotkeysIgnore, this); + LoadSettings(false); } @@ -1278,6 +1301,290 @@ void OBSBasicSettings::LoadAdvancedSettings() loading = false; } +template +static inline void LayoutHotkey(obs_hotkey_id id, obs_hotkey_t *key, Func &&fun, + const map> &keys) +{ + auto *label = new OBSHotkeyLabel; + label->setText(obs_hotkey_get_description(key)); + + OBSHotkeyWidget *hw = nullptr; + + auto combos = keys.find(id); + if (combos == std::end(keys)) + hw = new OBSHotkeyWidget(id, obs_hotkey_get_name(key)); + else + hw = new OBSHotkeyWidget(id, obs_hotkey_get_name(key), + combos->second); + + hw->label = label; + label->widget = hw; + + fun(key, label, hw); +} + +template +static QLabel *makeLabel(T &t, Func &&getName) +{ + return new QLabel(getName(t)); +} + +template +static QLabel *makeLabel(const OBSSource &source, Func &&) +{ + return new OBSSourceLabel(source); +} + +template +static inline void AddHotkeys(QFormLayout &layout, + Func &&getName, std::vector< + std::tuple, QPointer> + > &hotkeys) +{ + if (hotkeys.empty()) + return; + + auto line = new QFrame(); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + + layout.setItem(layout.rowCount(), QFormLayout::SpanningRole, + new QSpacerItem(0, 10)); + layout.addRow(line); + + using tuple_type = + std::tuple, QPointer>; + + stable_sort(begin(hotkeys), end(hotkeys), + [&](const tuple_type &a, const tuple_type &b) + { + const auto &o_a = get<0>(a); + const auto &o_b = get<0>(b); + return o_a != o_b && + string(getName(o_a)) < + getName(o_b); + }); + + string prevName; + for (const auto &hotkey : hotkeys) { + const auto &o = get<0>(hotkey); + const char *name = getName(o); + if (prevName != name) { + prevName = name; + layout.setItem(layout.rowCount(), + QFormLayout::SpanningRole, + new QSpacerItem(0, 10)); + layout.addRow(makeLabel(o, getName)); + } + + auto hlabel = get<1>(hotkey); + auto widget = get<2>(hotkey); + layout.addRow(hlabel, widget); + } +} + +void OBSBasicSettings::LoadHotkeySettings(obs_hotkey_id ignoreKey) +{ + hotkeys.clear(); + ui->hotkeyPage->takeWidget()->deleteLater(); + + using keys_t = map>; + keys_t keys; + obs_enum_hotkey_bindings([](void *data, + size_t, obs_hotkey_binding_t *binding) + { + auto &keys = *static_cast(data); + + keys[obs_hotkey_binding_get_hotkey_id(binding)].emplace_back( + obs_hotkey_binding_get_key_combination(binding)); + + return true; + }, &keys); + + auto layout = new QFormLayout(); + layout->setVerticalSpacing(0); + layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + layout->setLabelAlignment( + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + + auto widget = new QWidget(); + widget->setLayout(layout); + ui->hotkeyPage->setWidget(widget); + + using namespace std; + using encoders_elem_t = + tuple, QPointer>; + using outputs_elem_t = + tuple, QPointer>; + using services_elem_t = + tuple, QPointer>; + using sources_elem_t = + tuple, QPointer>; + vector encoders; + vector outputs; + vector services; + vector scenes; + vector sources; + + vector pairIds; + map> pairLabels; + + using std::move; + + auto HandleEncoder = [&](void *registerer, OBSHotkeyLabel *label, + OBSHotkeyWidget *hw) + { + auto weak_encoder = + static_cast(registerer); + auto encoder = OBSGetStrongRef(weak_encoder); + + if (!encoder) + return true; + + encoders.emplace_back(move(encoder), label, hw); + return false; + }; + + auto HandleOutput = [&](void *registerer, OBSHotkeyLabel *label, + OBSHotkeyWidget *hw) + { + auto weak_output = static_cast(registerer); + auto output = OBSGetStrongRef(weak_output); + + if (!output) + return true; + + outputs.emplace_back(move(output), label, hw); + return false; + }; + + auto HandleService = [&](void *registerer, OBSHotkeyLabel *label, + OBSHotkeyWidget *hw) + { + auto weak_service = + static_cast(registerer); + auto service = OBSGetStrongRef(weak_service); + + if (!service) + return true; + + services.emplace_back(move(service), label, hw); + return false; + }; + + auto HandleSource = [&](void *registerer, OBSHotkeyLabel *label, + OBSHotkeyWidget *hw) + { + auto weak_source = static_cast(registerer); + auto source = OBSGetStrongRef(weak_source); + + if (!source) + return true; + + if (obs_scene_from_source(source)) + scenes.emplace_back(source, label, hw); + else + sources.emplace_back(source, label, hw); + + return false; + }; + + auto RegisterHotkey = [&](obs_hotkey_t *key, OBSHotkeyLabel *label, + OBSHotkeyWidget *hw) + { + auto registerer_type = obs_hotkey_get_registerer_type(key); + void *registerer = obs_hotkey_get_registerer(key); + + obs_hotkey_id partner = obs_hotkey_get_pair_partner_id(key); + if (partner != OBS_INVALID_HOTKEY_ID) { + pairLabels.emplace(obs_hotkey_get_id(key), + make_pair(partner, label)); + pairIds.push_back(obs_hotkey_get_id(key)); + } + + using std::move; + + switch (registerer_type) { + case OBS_HOTKEY_REGISTERER_FRONTEND: + layout->addRow(label, hw); + break; + + case OBS_HOTKEY_REGISTERER_ENCODER: + if (HandleEncoder(registerer, label, hw)) + return; + break; + + case OBS_HOTKEY_REGISTERER_OUTPUT: + if (HandleOutput(registerer, label, hw)) + return; + break; + + case OBS_HOTKEY_REGISTERER_SERVICE: + if (HandleService(registerer, label, hw)) + return; + break; + + case OBS_HOTKEY_REGISTERER_SOURCE: + if (HandleSource(registerer, label, hw)) + return; + break; + } + + hotkeys.emplace_back(registerer_type == + OBS_HOTKEY_REGISTERER_FRONTEND, hw); + connect(hw, &OBSHotkeyWidget::KeyChanged, + this, &OBSBasicSettings::HotkeysChanged); + }; + + auto data = make_tuple(RegisterHotkey, std::move(keys), ignoreKey); + using data_t = decltype(data); + obs_enum_hotkeys([](void *data, obs_hotkey_id id, obs_hotkey_t *key) + { + data_t &d = *static_cast(data); + if (id != get<2>(d)) + LayoutHotkey(id, key, get<0>(d), get<1>(d)); + return true; + }, &data); + + for (auto keyId : pairIds) { + auto data1 = pairLabels.find(keyId); + if (data1 == end(pairLabels)) + continue; + + auto &label1 = data1->second.second; + if (label1->pairPartner) + continue; + + auto data2 = pairLabels.find(data1->second.first); + if (data2 == end(pairLabels)) + continue; + + auto &label2 = data2->second.second; + if (label2->pairPartner) + continue; + + QString tt = QTStr("Basic.Settings.Hotkeys.Pair"); + auto name1 = label1->text(); + auto name2 = label2->text(); + + auto Update = [&](OBSHotkeyLabel *label, const QString &name, + OBSHotkeyLabel *other, const QString &otherName) + { + label->setToolTip(tt.arg(otherName)); + label->setText(name + " ✳"); + label->pairPartner = other; + }; + Update(label1, name1, label2, name2); + Update(label2, name2, label1, name1); + } + + AddHotkeys(*layout, obs_output_get_name, outputs); + AddHotkeys(*layout, obs_source_get_name, scenes); + AddHotkeys(*layout, obs_source_get_name, sources); + AddHotkeys(*layout, obs_encoder_get_name, encoders); + AddHotkeys(*layout, obs_service_get_name, services); +} + void OBSBasicSettings::LoadSettings(bool changedOnly) { if (!changedOnly || generalChanged) @@ -1290,6 +1597,8 @@ void OBSBasicSettings::LoadSettings(bool changedOnly) LoadAudioSettings(); if (!changedOnly || videoChanged) LoadVideoSettings(); + if (!changedOnly || hotkeysChanged) + LoadHotkeySettings(); if (!changedOnly || advancedChanged) LoadAdvancedSettings(); } @@ -1560,6 +1869,33 @@ void OBSBasicSettings::SaveAudioSettings() main->ResetAudioDevices(); } +void OBSBasicSettings::SaveHotkeySettings() +{ + const auto &config = main->Config(); + + using namespace std; + + std::vector combinations; + for (auto &hotkey : hotkeys) { + auto &hw = *hotkey.second; + if (!hw.Changed()) + continue; + + hw.Save(combinations); + + if (!hotkey.first) + continue; + + obs_data_array_t *array = obs_hotkey_save(hw.id); + obs_data_t *data = obs_data_create(); + obs_data_set_array(data, "bindings", array); + const char *json = obs_data_get_json(data); + config_set_string(config, "Hotkeys", hw.name.c_str(), json); + obs_data_release(data); + obs_data_array_release(array); + } +} + void OBSBasicSettings::SaveSettings() { if (generalChanged) @@ -1572,6 +1908,8 @@ void OBSBasicSettings::SaveSettings() SaveAudioSettings(); if (videoChanged) SaveVideoSettings(); + if (hotkeysChanged) + SaveHotkeySettings(); if (advancedChanged) SaveAdvancedSettings(); @@ -1937,6 +2275,28 @@ void OBSBasicSettings::VideoChanged() } } +void OBSBasicSettings::HotkeysChanged() +{ + using namespace std; + if (loading) + return; + + hotkeysChanged = any_of(begin(hotkeys), end(hotkeys), + [](const pair> &hotkey) + { + const auto &hw = *hotkey.second; + return hw.Changed(); + }); + + if (hotkeysChanged) + EnableApplyButton(true); +} + +void OBSBasicSettings::ReloadHotkeys(obs_hotkey_id ignoreKey) +{ + LoadHotkeySettings(ignoreKey); +} + void OBSBasicSettings::AdvancedChanged() { if (!loading) { diff --git a/obs/window-basic-settings.hpp b/obs/window-basic-settings.hpp index 93bf7b9d2..85fdfb68d 100644 --- a/obs/window-basic-settings.hpp +++ b/obs/window-basic-settings.hpp @@ -31,6 +31,7 @@ class OBSBasic; class QAbstractButton; class QComboBox; class OBSPropertiesView; +class OBSHotkeyWidget; #include "ui_OBSBasicSettings.h" @@ -58,11 +59,13 @@ private: OBSBasic *main; std::unique_ptr ui; + bool generalChanged = false; bool stream1Changed = false; bool outputsChanged = false; bool audioChanged = false; bool videoChanged = false; + bool hotkeysChanged = false; bool advancedChanged = false; int pageIndex = 0; bool loading = true; @@ -74,6 +77,10 @@ private: OBSPropertiesView *streamEncoderProps = nullptr; OBSPropertiesView *recordEncoderProps = nullptr; + std::vector>> hotkeys; + OBSSignal hotkeyRegistered; + OBSSignal hotkeyUnregistered; + void SaveCombo(QComboBox *widget, const char *section, const char *value); void SaveComboData(QComboBox *widget, const char *section, @@ -91,7 +98,8 @@ private: inline bool Changed() const { return generalChanged || outputsChanged || stream1Changed || - audioChanged || videoChanged || advancedChanged; + audioChanged || videoChanged || advancedChanged || + hotkeysChanged; } inline void EnableApplyButton(bool en) @@ -106,6 +114,7 @@ private: outputsChanged = false; audioChanged = false; videoChanged = false; + hotkeysChanged = false; advancedChanged= false; EnableApplyButton(false); } @@ -125,6 +134,7 @@ private: void LoadOutputSettings(); void LoadAudioSettings(); void LoadVideoSettings(); + void LoadHotkeySettings(obs_hotkey_id ignoreKey=OBS_INVALID_HOTKEY_ID); void LoadAdvancedSettings(); void LoadSettings(bool changedOnly); @@ -165,6 +175,7 @@ private: void SaveOutputSettings(); void SaveAudioSettings(); void SaveVideoSettings(); + void SaveHotkeySettings(); void SaveAdvancedSettings(); void SaveSettings(); @@ -199,6 +210,8 @@ private slots: void VideoChanged(); void VideoChangedResolution(); void VideoChangedRestart(); + void HotkeysChanged(); + void ReloadHotkeys(obs_hotkey_id ignoreKey=OBS_INVALID_HOTKEY_ID); void AdvancedChanged(); void AdvancedChangedRestart(); From c23c071ba2789b65d0b110b75d9aad7458e32b25 Mon Sep 17 00:00:00 2001 From: Palana Date: Fri, 1 May 2015 07:07:42 +0200 Subject: [PATCH 14/18] UI: Add Push-to-talk/mute settings to audio settings --- obs/data/locale/en-US.ini | 5 + obs/forms/OBSBasicSettings.ui | 11 ++- obs/window-basic-settings.cpp | 169 ++++++++++++++++++++++++++++++++++ obs/window-basic-settings.hpp | 35 +++++++ 4 files changed, 219 insertions(+), 1 deletion(-) diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index 33f606f12..997423bc8 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -333,6 +333,10 @@ Basic.Settings.Audio.DesktopDevice2="Desktop Audio Device 2" Basic.Settings.Audio.AuxDevice="Mic/Auxiliary Audio Device" Basic.Settings.Audio.AuxDevice2="Mic/Auxiliary Audio Device 2" Basic.Settings.Audio.AuxDevice3="Mic/Auxiliary Audio Device 3" +Basic.Settings.Audio.EnablePushToMute="Enable Push-to-mute" +Basic.Settings.Audio.PushToMuteDelay="Push-to-mute delay" +Basic.Settings.Audio.EnablePushToTalk="Enable Push-to-talk" +Basic.Settings.Audio.PushToTalkDelay="Push-to-talk delay" # basic mode 'advanced' settings Basic.Settings.Advanced="Advanced" @@ -397,6 +401,7 @@ Hotkeys.MouseButton="Mouse %1" # audio hotkeys Mute="Mute" Unmute="Unmute" +Push-to-mute="Push-to-mute" Push-to-talk="Push-to-talk" # scene item hotkeys diff --git a/obs/forms/OBSBasicSettings.ui b/obs/forms/OBSBasicSettings.ui index efe763354..bcd6998a9 100644 --- a/obs/forms/OBSBasicSettings.ui +++ b/obs/forms/OBSBasicSettings.ui @@ -2014,7 +2014,16 @@ - + + + + true + + + + + + color: rgb(255, 0, 4); diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp index 99b637e78..e9907044e 100644 --- a/obs/window-basic-settings.cpp +++ b/obs/window-basic-settings.cpp @@ -317,6 +317,23 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) LoadColorRanges(); LoadFormats(); + auto ReloadAudioSources = [](void *data, calldata_t *param) + { + auto settings = static_cast(data); + auto source = static_cast(calldata_ptr(param, + "source")); + + if (!(obs_source_get_output_flags(source) & OBS_SOURCE_AUDIO)) + return; + + QMetaObject::invokeMethod(settings, "ReloadAudioSources", + Qt::QueuedConnection); + }; + sourceCreated.Connect(obs_get_signal_handler(), "source_create", + ReloadAudioSources, this); + channelChanged.Connect(obs_get_signal_handler(), "channel_change", + ReloadAudioSources, this); + auto ReloadHotkeys = [](void *data, calldata_t*) { auto settings = static_cast(data); @@ -1245,6 +1262,135 @@ void OBSBasicSettings::LoadAudioDevices() } } +void OBSBasicSettings::LoadAudioSources() +{ + auto layout = new QFormLayout(); + layout->setVerticalSpacing(15); + layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + + ui->audioSourceScrollArea->takeWidget()->deleteLater(); + audioSourceSignals.clear(); + audioSources.clear(); + + auto widget = new QWidget(); + widget->setLayout(layout); + ui->audioSourceScrollArea->setWidget(widget); + + const char *enablePtm = Str("Basic.Settings.Audio.EnablePushToMute"); + const char *ptmDelay = Str("Basic.Settings.Audio.PushToMuteDelay"); + const char *enablePtt = Str("Basic.Settings.Audio.EnablePushToTalk"); + const char *pttDelay = Str("Basic.Settings.Audio.PushToTalkDelay"); + auto AddSource = [&](obs_source_t *source) + { + if (!(obs_source_get_output_flags(source) & OBS_SOURCE_AUDIO)) + return true; + + auto form = new QFormLayout(); + form->setVerticalSpacing(0); + form->setHorizontalSpacing(5); + form->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow); + + auto ptmCB = new SilentUpdateCheckBox(); + ptmCB->setText(enablePtm); + ptmCB->setChecked(obs_source_push_to_mute_enabled(source)); + form->addRow(ptmCB); + + auto ptmSB = new SilentUpdateSpinBox(); + ptmSB->setSuffix(" ms"); + ptmSB->setRange(0, INT_MAX); + ptmSB->setValue(obs_source_get_push_to_mute_delay(source)); + form->addRow(ptmDelay, ptmSB); + + auto pttCB = new SilentUpdateCheckBox(); + pttCB->setText(enablePtt); + pttCB->setChecked(obs_source_push_to_talk_enabled(source)); + form->addRow(pttCB); + + auto pttSB = new SilentUpdateSpinBox(); + pttSB->setSuffix(" ms"); + pttSB->setRange(0, INT_MAX); + pttSB->setValue(obs_source_get_push_to_talk_delay(source)); + form->addRow(pttDelay, pttSB); + + HookWidget(ptmCB, CHECK_CHANGED, AUDIO_CHANGED); + HookWidget(ptmSB, SCROLL_CHANGED, AUDIO_CHANGED); + HookWidget(pttCB, CHECK_CHANGED, AUDIO_CHANGED); + HookWidget(pttSB, SCROLL_CHANGED, AUDIO_CHANGED); + + audioSourceSignals.reserve(audioSourceSignals.size() + 4); + + auto handler = obs_source_get_signal_handler(source); + audioSourceSignals.emplace_back(handler, "push_to_mute_changed", + [](void *data, calldata_t *param) + { + QMetaObject::invokeMethod(static_cast(data), + "setCheckedSilently", + Q_ARG(bool, calldata_bool(param, "enabled"))); + }, ptmCB); + audioSourceSignals.emplace_back(handler, "push_to_mute_delay", + [](void *data, calldata_t *param) + { + QMetaObject::invokeMethod(static_cast(data), + "setValueSilently", + Q_ARG(int, calldata_int(param, "delay"))); + }, ptmSB); + audioSourceSignals.emplace_back(handler, "push_to_talk_changed", + [](void *data, calldata_t *param) + { + QMetaObject::invokeMethod(static_cast(data), + "setCheckedSilently", + Q_ARG(bool, calldata_bool(param, "enabled"))); + }, pttCB); + audioSourceSignals.emplace_back(handler, "push_to_talk_delay", + [](void *data, calldata_t *param) + { + QMetaObject::invokeMethod(static_cast(data), + "setValueSilently", + Q_ARG(int, calldata_int(param, "delay"))); + }, pttSB); + + audioSources.emplace_back(OBSGetWeakRef(source), + ptmCB, pttSB, pttCB, pttSB); + + auto label = new OBSSourceLabel(source); + connect(label, &OBSSourceLabel::Removed, + [=]() + { + LoadAudioSources(); + }); + connect(label, &OBSSourceLabel::Destroyed, + [=]() + { + LoadAudioSources(); + }); + + layout->addRow(label, form); + return true; + }; + + for (int i = 0; i < MAX_CHANNELS; i++) { + obs_source_t *source = obs_get_output_source(i); + if (!source) continue; + + AddSource(source); + obs_source_release(source); + } + + using AddSource_t = decltype(AddSource); + obs_enum_sources([](void *data, obs_source_t *source) + { + auto &AddSource = *static_cast(data); + AddSource(source); + return true; + }, static_cast(&AddSource)); + + + if (layout->rowCount() == 0) + ui->audioSourceScrollArea->hide(); + else + ui->audioSourceScrollArea->show(); +} + void OBSBasicSettings::LoadAudioSettings() { uint32_t sampleRate = config_get_uint(main->Config(), "Audio", @@ -1272,6 +1418,7 @@ void OBSBasicSettings::LoadAudioSettings() ui->channelSetup->setCurrentIndex(1); LoadAudioDevices(); + LoadAudioSources(); loading = false; } @@ -1866,6 +2013,23 @@ void OBSBasicSettings::SaveAudioSettings() SaveComboData(ui->auxAudioDevice2, "Audio", "AuxDevice2"); SaveComboData(ui->auxAudioDevice3, "Audio", "AuxDevice3"); + for (auto &audioSource : audioSources) { + auto source = OBSGetStrongRef(get<0>(audioSource)); + if (!source) + continue; + + auto &ptmCB = get<1>(audioSource); + auto &ptmSB = get<2>(audioSource); + auto &pttCB = get<3>(audioSource); + auto &pttSB = get<4>(audioSource); + + obs_source_enable_push_to_mute(source, ptmCB->isChecked()); + obs_source_set_push_to_mute_delay(source, ptmSB->value()); + + obs_source_enable_push_to_talk(source, pttCB->isChecked()); + obs_source_set_push_to_talk_delay(source, pttSB->value()); + } + main->ResetAudioDevices(); } @@ -2236,6 +2400,11 @@ void OBSBasicSettings::AudioChangedRestart() } } +void OBSBasicSettings::ReloadAudioSources() +{ + LoadAudioSources(); +} + void OBSBasicSettings::VideoChangedRestart() { if (!loading) { diff --git a/obs/window-basic-settings.hpp b/obs/window-basic-settings.hpp index 85fdfb68d..85981b400 100644 --- a/obs/window-basic-settings.hpp +++ b/obs/window-basic-settings.hpp @@ -35,6 +35,30 @@ class OBSHotkeyWidget; #include "ui_OBSBasicSettings.h" +class SilentUpdateCheckBox : public QCheckBox { + Q_OBJECT + +public slots: + void setCheckedSilently(bool checked) + { + bool blocked = blockSignals(true); + setChecked(checked); + blockSignals(blocked); + } +}; + +class SilentUpdateSpinBox : public QSpinBox { + Q_OBJECT + +public slots: + void setValueSilently(int val) + { + bool blocked = blockSignals(true); + setValue(val); + blockSignals(blocked); + } +}; + class OBSFFDeleter { public: @@ -77,6 +101,15 @@ private: OBSPropertiesView *streamEncoderProps = nullptr; OBSPropertiesView *recordEncoderProps = nullptr; + using AudioSource_t = + std::tuple, QPointer, + QPointer, QPointer>; + std::vector audioSources; + std::vector audioSourceSignals; + OBSSignal sourceCreated; + OBSSignal channelChanged; + std::vector>> hotkeys; OBSSignal hotkeyRegistered; OBSSignal hotkeyUnregistered; @@ -161,6 +194,7 @@ private: void LoadListValues(QComboBox *widget, obs_property_t *prop, const char *configName); void LoadAudioDevices(); + void LoadAudioSources(); /* video */ void LoadRendererList(); @@ -205,6 +239,7 @@ private slots: void GeneralChanged(); void AudioChanged(); void AudioChangedRestart(); + void ReloadAudioSources(); void OutputsChanged(); void Stream1Changed(); void VideoChanged(); From 054f00ced1a1505bfb2be840dbc583b348feb32d Mon Sep 17 00:00:00 2001 From: Palana Date: Sat, 1 Nov 2014 21:53:05 +0100 Subject: [PATCH 15/18] UI: Add SelectScene hotkey UI supplied per scene hotkey to allow switching between scenes; this has to be done in the UI since it's up to the application to define what exactly "changing/selecting a scene" means (changing the output of channel 0 may not be enough for other applications) --- obs/data/locale/en-US.ini | 3 +++ obs/window-basic-main.cpp | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index 997423bc8..0c0dc76ca 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -361,6 +361,9 @@ Basic.AdvAudio.AudioTracks="Tracks" Basic.Settings.Hotkeys="Hotkeys" Basic.Settings.Hotkeys.Pair="Key combinations shared with '%1' act as toggles" +# basic mode hotkeys +Basic.Hotkeys.SelectScene="Switch to scene" + # hotkeys that may lack translation on certain operating systems Hotkeys.Insert="Insert" Hotkeys.Delete="Delete" diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 576328401..17360af07 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -918,6 +918,18 @@ void OBSBasic::AddScene(OBSSource source) item->setData(Qt::UserRole, QVariant::fromValue(OBSScene(scene))); ui->scenes->addItem(item); + obs_hotkey_register_source(source, "OBSBasic.SelectScene", + Str("Basic.Hotkeys.SelectScene"), + [](void *data, + obs_hotkey_id, obs_hotkey_t*, bool pressed) + { + auto potential_source = static_cast(data); + auto source = obs_source_get_ref(potential_source); + if (source && pressed) + obs_set_output_source(0, source); + obs_source_release(source); + }, static_cast(source)); + signal_handler_t *handler = obs_source_get_signal_handler(source); signal_handler_connect(handler, "item_add", OBSBasic::SceneItemAdded, this); From 508f04d927d071a0653c696da8c56cbc9fb9844c Mon Sep 17 00:00:00 2001 From: Palana Date: Sun, 26 Oct 2014 01:57:29 +0200 Subject: [PATCH 16/18] UI: Add OBSBasic::StartRecording/StopRecording --- obs/window-basic-main.cpp | 27 ++++++++++++++++++++------- obs/window-basic-main.hpp | 3 +++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index 17360af07..d5c5b89ea 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -2578,6 +2578,22 @@ void OBSBasic::StreamingStop(int code) QT_UTF8(errorMessage)); } +void OBSBasic::StartRecording() +{ + SaveProject(); + + if (!outputHandler->RecordingActive()) + outputHandler->StartRecording(); +} + +void OBSBasic::StopRecording() +{ + SaveProject(); + + if (outputHandler->RecordingActive()) + outputHandler->StopRecording(); +} + void OBSBasic::RecordingStart() { ui->statusbar->RecordingStarted(outputHandler->fileOutput); @@ -2607,13 +2623,10 @@ void OBSBasic::on_streamButton_clicked() void OBSBasic::on_recordButton_clicked() { - SaveProject(); - - if (outputHandler->RecordingActive()) { - outputHandler->StopRecording(); - } else { - outputHandler->StartRecording(); - } + if (outputHandler->RecordingActive()) + StopRecording(); + else + StartRecording(); } void OBSBasic::on_settingsButton_clicked() diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index 91c8c22a2..8d0cadb62 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -155,6 +155,9 @@ public slots: void StreamingStart(); void StreamingStop(int errorcode); + void StartRecording(); + void StopRecording(); + void RecordingStart(); void RecordingStop(); From 6192b34ab3d525ea8330f13dc3d430fdf7f00855 Mon Sep 17 00:00:00 2001 From: Palana Date: Sun, 26 Oct 2014 02:03:13 +0200 Subject: [PATCH 17/18] UI: Add OBSBasic::StartStreaming/StopStreaming --- obs/window-basic-main.cpp | 31 +++++++++++++++++++++++-------- obs/window-basic-main.hpp | 3 +++ 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index d5c5b89ea..e0532bb26 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -2532,6 +2532,27 @@ void OBSBasic::OpenSceneFilters() CreateFiltersWindow(source); } +void OBSBasic::StartStreaming() +{ + SaveProject(); + + if (outputHandler->StreamingActive()) + return; + + if (outputHandler->StartStreaming(service)) { + ui->streamButton->setEnabled(false); + ui->streamButton->setText(QTStr("Basic.Main.Connecting")); + } +} + +void OBSBasic::StopStreaming() +{ + SaveProject(); + + if (outputHandler->StreamingActive()) + outputHandler->StopStreaming(); +} + void OBSBasic::StreamingStart() { ui->streamButton->setText(QTStr("Basic.Main.StopStreaming")); @@ -2608,16 +2629,10 @@ void OBSBasic::RecordingStop() void OBSBasic::on_streamButton_clicked() { - SaveProject(); - if (outputHandler->StreamingActive()) { - outputHandler->StopStreaming(); + StopStreaming(); } else { - if (outputHandler->StartStreaming(service)) { - ui->streamButton->setEnabled(false); - ui->streamButton->setText( - QTStr("Basic.Main.Connecting")); - } + StartStreaming(); } } diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index 8d0cadb62..a628e3fd5 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -152,6 +152,9 @@ private: void OpenProjector(obs_source_t *source, int monitor); public slots: + void StartStreaming(); + void StopStreaming(); + void StreamingStart(); void StreamingStop(int errorcode); From 37069d372fe0fff8eb4ddd48ba61cc7844526e97 Mon Sep 17 00:00:00 2001 From: Palana Date: Sat, 1 Nov 2014 21:53:25 +0100 Subject: [PATCH 18/18] UI: Add Start/Stop Recording/Streaming hotkeys --- obs/data/locale/en-US.ini | 4 +++ obs/window-basic-main.cpp | 74 +++++++++++++++++++++++++++++++++++++++ obs/window-basic-main.hpp | 3 ++ 3 files changed, 81 insertions(+) diff --git a/obs/data/locale/en-US.ini b/obs/data/locale/en-US.ini index 0c0dc76ca..8b9770c95 100644 --- a/obs/data/locale/en-US.ini +++ b/obs/data/locale/en-US.ini @@ -362,6 +362,10 @@ Basic.Settings.Hotkeys="Hotkeys" Basic.Settings.Hotkeys.Pair="Key combinations shared with '%1' act as toggles" # basic mode hotkeys +Basic.Hotkeys.StartStreaming="Start Streaming" +Basic.Hotkeys.StopStreaming="Stop Streaming" +Basic.Hotkeys.StartRecording="Start Recording" +Basic.Hotkeys.StopRecording="Stop Recording" Basic.Hotkeys.SelectScene="Switch to scene" # hotkeys that may lack translation on certain operating systems diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index e0532bb26..ed4e838e6 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -662,6 +662,7 @@ void OBSBasic::OBSInit() obs_load_all_modules(); ResetOutputs(); + CreateHotkeys(); if (!InitService()) throw "Failed to initialize service"; @@ -750,6 +751,77 @@ void OBSBasic::HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed) Q_ARG(obs_hotkey_id, id), Q_ARG(bool, pressed)); } +void OBSBasic::CreateHotkeys() +{ + auto LoadHotkeyData = [&](const char *name) -> OBSData + { + const char *info = config_get_string(basicConfig, + "Hotkeys", name); + if (!info) + return {}; + + obs_data_t *data = obs_data_create_from_json(info); + if (!data) + return {}; + + OBSData res = data; + obs_data_release(data); + return res; + }; + + auto LoadHotkeyPair = [&](obs_hotkey_pair_id id, const char *name0, + const char *name1) + { + obs_data_array_t *array0 = + obs_data_get_array(LoadHotkeyData(name0), "bindings"); + obs_data_array_t *array1 = + obs_data_get_array(LoadHotkeyData(name1), "bindings"); + + obs_hotkey_pair_load(id, array0, array1); + obs_data_array_release(array0); + obs_data_array_release(array1); + }; + +#define MAKE_CALLBACK(pred, method) \ + [](void *data, obs_hotkey_pair_id, obs_hotkey_t*, bool pressed) \ + { \ + OBSBasic &basic = *static_cast(data); \ + if (pred && pressed) { \ + method(); \ + return true; \ + } \ + return false; \ + } + + streamingHotkeys = obs_hotkey_pair_register_frontend( + "OBSBasic.StartStreaming", + Str("Basic.Hotkeys.StartStreaming"), + "OBSBasic.StopStreaming", + Str("Basic.Hotkeys.StopStreaming"), + MAKE_CALLBACK(!basic.outputHandler->StreamingActive(), + basic.StartStreaming), + MAKE_CALLBACK(basic.outputHandler->StreamingActive(), + basic.StopStreaming), + this, this); + LoadHotkeyPair(streamingHotkeys, + "OBSBasic.StartStreaming", "OBSBasic.StopStreaming"); + + recordingHotkeys = obs_hotkey_pair_register_frontend( + "OBSBasic.StartRecording", + Str("Basic.Hotkeys.StartRecording"), + "OBSBasic.StopRecording", + Str("Basic.Hotkeys.StopRecording"), + MAKE_CALLBACK(!basic.outputHandler->RecordingActive(), + basic.StartRecording), + MAKE_CALLBACK(basic.outputHandler->RecordingActive(), + basic.StopRecording), + this, this); + LoadHotkeyPair(recordingHotkeys, + "OBSBasic.StartRecording", "OBSBasic.StopRecording"); + +#undef MAKE_CALLBACK +} + OBSBasic::~OBSBasic() { bool previewEnabled = obs_preview_enabled(); @@ -764,6 +836,8 @@ OBSBasic::~OBSBasic() os_cpu_usage_info_destroy(cpuUsageInfo); obs_hotkey_set_callback_routing_func(nullptr, nullptr); + obs_hotkey_pair_unregister(streamingHotkeys); + obs_hotkey_pair_unregister(recordingHotkeys); service = nullptr; outputHandler.reset(); diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index a628e3fd5..f200db73d 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -113,6 +113,7 @@ private: void Load(const char *file); void InitHotkeys(); + void CreateHotkeys(); bool InitService(); @@ -151,6 +152,8 @@ private: void Nudge(int dist, MoveDir dir); void OpenProjector(obs_source_t *source, int monitor); + obs_hotkey_pair_id streamingHotkeys, recordingHotkeys; + public slots: void StartStreaming(); void StopStreaming();