Merge branch 'global-hotkeys'

master
jp9000 2015-05-11 20:15:08 -07:00
commit 6c535513b5
48 changed files with 7317 additions and 75 deletions

View File

@ -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
@ -216,6 +231,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 +250,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

File diff suppressed because it is too large Load Diff

View File

@ -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))
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);

View File

@ -0,0 +1,411 @@
/******************************************************************************
Copyright (C) 2014 by Ruwen Hahn <palana@stunned.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <string.h>
#include <assert.h>
#include <util/bmem.h>
#include <util/c99defs.h>
#include <util/darray.h>
#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);
}

1519
libobs/obs-hotkey.c Normal file

File diff suppressed because it is too large Load Diff

287
libobs/obs-hotkey.h Normal file
View File

@ -0,0 +1,287 @@
/******************************************************************************
Copyright (C) 2014-2015 by Ruwen Hahn <palana@stunned.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#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))
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,
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

477
libobs/obs-hotkeys.h Normal file
View File

@ -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

View File

@ -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,41 @@ 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];
char *mute;
char *unmute;
char *push_to_mute;
char *push_to_talk;
char *sceneitem_show;
char *sceneitem_hide;
};
struct obs_core {
struct obs_module *first_module;
DARRAY(struct obs_module_path) module_paths;
@ -236,6 +330,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 +348,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 +363,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,
@ -418,12 +518,26 @@ 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,
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);

View File

@ -24,6 +24,11 @@
#endif
#include <sys/sysinfo.h>
#include <sys/utsname.h>
#include <xcb/xcb.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xlib-xcb.h>
#include <X11/keysym.h>
#include <inttypes.h>
#include "util/dstr.h"
#include "obs-internal.h"
@ -227,3 +232,735 @@ void log_system_info(void)
log_distribution_info();
#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)
{
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)
{
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)
{
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 *dstr)
{
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));
}
}
static obs_key_t key_from_keycode(obs_hotkeys_platform_t *context,
xcb_keycode_t code)
{
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 sym)
{
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)
{
if (key == OBS_KEY_META)
return XK_Super_L;
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);
}
}

View File

@ -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))
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;

View File

@ -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;
}
@ -554,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;
@ -601,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(&params, "scene", scene);
calldata_set_ptr(&params, "item", item);
signal_handler_signal(scene->source->context.signals, "item_add",
@ -613,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);

View File

@ -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;

View File

@ -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)) {
if (!obs_context_data_init(&service->context, settings, name,
hotkey_data)) {
bfree(service);
return NULL;
}

View File

@ -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)",
@ -94,9 +98,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))
if (!obs_context_data_init(&source->context, settings, name,
hotkey_data))
return false;
return signal_handler_add_array(source->context.signals,
@ -154,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)
{
@ -170,7 +252,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 +267,11 @@ 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))
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;
if (info && info->get_defaults)
@ -194,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)
@ -273,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);
@ -828,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;
@ -2819,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);
}

View File

@ -194,3 +194,289 @@ void log_system_info(void)
log_available_memory();
log_windows_version();
}
struct obs_hotkeys_platform {
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)
{
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)
{
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)
{
if (key == OBS_KEY_META) {
return vk_down(VK_LWIN) || vk_down(VK_RWIN);
}
UNUSED_PARAMETER(context);
return vk_down(obs_key_to_virtual_key(key));
}
void obs_key_to_str(obs_key_t key, struct dstr *str)
{
wchar_t name[128] = L"";
UINT scan_code;
int vk;
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)
{
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)
{
if (key == OBS_KEY_META)
return VK_LWIN;
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);
}
}

View File

@ -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,82 @@ 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;
hotkeys->mute = bstrdup("Mute");
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;
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;
bfree(hotkeys->mute);
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();
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 +720,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 +770,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,12 +1432,15 @@ 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;
uint32_t mixers;
source = obs_source_create(type, id, name, settings);
source = obs_source_create(type, id, name, settings, hotkeys);
obs_data_release(hotkeys);
obs_data_set_default_double(source_data, "volume", 1.0);
volume = obs_data_get_double(source_data, "volume");
@ -1376,6 +1464,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);
@ -1439,6 +1535,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);
@ -1447,8 +1545,17 @@ 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);
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 +1566,9 @@ 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);
@ -1522,7 +1632,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 +1650,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 +1672,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);

View File

@ -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
@ -654,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
@ -863,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 */
@ -1091,7 +1107,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
@ -1301,7 +1317,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
@ -1313,7 +1329,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
@ -1433,7 +1450,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

View File

@ -117,6 +117,8 @@ set(obs_SOURCES
slider-absoluteset-style.cpp
source-list-widget.cpp
crash-report.cpp
hotkey-edit.cpp
source-label.cpp
qt-wrappers.cpp)
set(obs_HEADERS
@ -153,6 +155,8 @@ set(obs_HEADERS
source-list-widget.hpp
qt-display.hpp
crash-report.hpp
hotkey-edit.hpp
source-label.hpp
qt-wrappers.hpp)
set(obs_UI

View File

@ -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"
@ -331,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"
@ -350,3 +356,61 @@ Basic.AdvAudio.Mono="Downmix to Mono"
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"
# 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
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-mute="Push-to-mute"
Push-to-talk="Push-to-talk"
# scene item hotkeys
SceneItemShow="Show '%1'"
SceneItemHide="Hide '%1'"

View File

@ -487,3 +487,7 @@ MuteCheckBox::indicator:checked {
MuteCheckBox::indicator:unchecked {
image: url(./Dark/unmute.png);
}
OBSHotkeyLabel[hotkeyPairHover=true] {
color: red;
}

View File

@ -47,6 +47,10 @@ MuteCheckBox::indicator:unchecked {
image: url(:/res/images/unmute.png);
}
OBSHotkeyLabel[hotkeyPairHover=true] {
color: red;
}
/* Volume Control */

View File

@ -87,6 +87,15 @@
<normaloff>:/settings/images/settings/video-display-3.png</normaloff>:/settings/images/settings/video-display-3.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>Basic.Settings.Hotkeys</string>
</property>
<property name="icon">
<iconset resource="obs.qrc">
<normaloff>:/settings/images/settings/preferences-desktop-keyboard-shortcuts.png</normaloff>:/settings/images/settings/preferences-desktop-keyboard-shortcuts.png</iconset>
</property>
</item>
<item>
<property name="text">
<string>Basic.Settings.Advanced</string>
@ -2005,7 +2014,16 @@
</property>
</widget>
</item>
<item row="7" column="1">
<item row="7" colspan="2">
<widget class="QScrollArea" name="audioSourceScrollArea">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="audioSourceWidget">
</widget>
</widget>
</item>
<item row="8" column="1">
<widget class="QLabel" name="audioMsg">
<property name="styleSheet">
<string notr="true">color: rgb(255, 0, 4);</string>
@ -2330,6 +2348,24 @@
</item>
</layout>
</widget>
<widget class="QScrollArea" name="hotkeyPage">
<property name="widgetResizable">
<bool>true</bool>
</property>
<widget class="QWidget" name="hotkeyWidget">
<layout class="QFormLayout" name="hotkeyLayout">
<property name="verticalSpacing">
<number>0</number>
</property>
<property name="fieldGrowthPolicy">
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
</property>
<property name="labelAlignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</layout>
</widget>
</widget>
<widget class="QWidget" name="advancedPage">
<layout class="QHBoxLayout" name="horizontalLayout_11">
<property name="leftMargin">

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -22,5 +22,6 @@
<file>images/settings/applications-system-2.png</file>
<file>images/settings/system-settings-3.png</file>
<file>images/settings/network-bluetooth.png</file>
<file>images/settings/preferences-desktop-keyboard-shortcuts.png</file>
</qresource>
</RCC>

471
obs/hotkey-edit.cpp Normal file
View File

@ -0,0 +1,471 @@
/******************************************************************************
Copyright (C) 2014-2015 by Ruwen Hahn <palana@stunned.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "hotkey-edit.hpp"
#include <util/dstr.hpp>
#include <QPointer>
#include <QStyle>
#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<OBSHotkeyEdit*>(this_);
QMetaObject::invokeMethod(edit, "ReloadKeyLayout");
}, this};
}
void OBSHotkeyEdit::ReloadKeyLayout()
{
RenderKey();
}
void OBSHotkeyWidget::SetKeyCombinations(
const std::vector<obs_key_combination_t> &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<obs_key_combination_t> &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<obs_key_combination_t> combinations;
Save(combinations);
}
void OBSHotkeyWidget::Save(std::vector<obs_key_combination_t> &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<AtomicUpdate_t>(d))();
}, static_cast<void*>(&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<int>(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<OBSHotkeyWidget*>(data);
auto key = static_cast<obs_hotkey_t*>(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<obs_key_combination_t> 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<LoadBindings_t>(data);
LoadBindings(binding);
return true;
}, static_cast<void*>(&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);
}

158
obs/hotkey-edit.hpp Normal file
View File

@ -0,0 +1,158 @@
/******************************************************************************
Copyright (C) 2014-2015 by Ruwen Hahn <palana@stunned.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <QLineEdit>
#include <QKeyEvent>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QPointer>
#include <QLabel>
#include <obs.hpp>
class OBSHotkeyWidget;
class OBSHotkeyLabel : public QLabel {
Q_OBJECT
public:
QPointer<OBSHotkeyLabel> pairPartner;
QPointer<OBSHotkeyWidget> 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<obs_key_combination_t> &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_key_combination_t>&);
obs_hotkey_id id;
std::string name;
bool changed = false;
bool Changed() const;
QPointer<OBSHotkeyLabel> label;
QString toolTip;
void setToolTip(const QString &toolTip_)
{
toolTip = toolTip_;
for (auto &edit : edits)
edit->setToolTip(toolTip_);
}
void Apply();
void GetCombinations(std::vector<obs_key_combination_t>&) const;
void Save();
void Save(std::vector<obs_key_combination_t> &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<QPointer<OBSHotkeyEdit>> edits;
std::vector<QPointer<QPushButton>> removeButtons;
std::vector<QPointer<QPushButton>> revertButtons;
OBSSignal bindingsChanged;
bool ignoreChangedBindings = false;
QVBoxLayout *layout() const
{
return dynamic_cast<QVBoxLayout*>(QWidget::layout());
}
private slots:
void HandleChangedBindings(obs_hotkey_id id_);
signals:
void KeyChanged();
};

View File

@ -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<QMouseEvent*>(event));
/*case QEvent::MouseButtonDblClick:
case QEvent::Wheel:*/
case QEvent::KeyPress:
case QEvent::KeyRelease:
key_event(static_cast<QKeyEvent*>(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;

View File

@ -31,6 +31,7 @@
std::string CurrentTimeString();
std::string CurrentDateTimeString();
std::string GenerateTimeDateFilename(const char *extension);
QObject *CreateShortcutFilter();
struct BaseLexer {
lexer lex;

View File

@ -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;
}

View File

@ -18,6 +18,7 @@
#pragma once
#include <QWidget>
#include <obs.h>
#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);

44
obs/source-label.cpp Normal file
View File

@ -0,0 +1,44 @@
/******************************************************************************
Copyright (C) 2015 by Ruwen Hahn <palana@stunned.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "source-label.hpp"
void OBSSourceLabel::SourceRenamed(void *data, calldata_t *params)
{
auto &label = *static_cast<OBSSourceLabel*>(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<OBSSourceLabel*>(data);
emit label.Removed();
}
void OBSSourceLabel::SourceDestroyed(void *data, calldata_t *)
{
auto &label = *static_cast<OBSSourceLabel*>(data);
emit label.Destroyed();
label.destroyedSignal.Disconnect();
label.removedSignal.Disconnect();
label.renamedSignal.Disconnect();
}

50
obs/source-label.hpp Normal file
View File

@ -0,0 +1,50 @@
/******************************************************************************
Copyright (C) 2015 by Ruwen Hahn <palana@stunned.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
#include <QLabel>
#include <obs.hpp>
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();
};

View File

@ -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);

View File

@ -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()));
@ -367,7 +369,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);

View File

@ -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)";

View File

@ -118,6 +118,7 @@ OBSBasic::OBSBasic(QWidget *parent)
qRegisterMetaType<OBSScene> ("OBSScene");
qRegisterMetaType<OBSSceneItem>("OBSSceneItem");
qRegisterMetaType<OBSSource> ("OBSSource");
qRegisterMetaType<obs_hotkey_id>("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()));
@ -292,7 +295,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);
@ -406,10 +409,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);
service = obs_service_create(type, "default_service", settings,
hotkey_data);
obs_service_release(service);
obs_data_release(hotkey_data);
obs_data_release(settings);
obs_data_release(data);
@ -421,7 +427,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);
@ -649,11 +656,13 @@ void OBSBasic::OBSInit()
}
InitOBSCallbacks();
InitHotkeys();
AddExtraModulePaths();
obs_load_all_modules();
ResetOutputs();
CreateHotkeys();
if (!InitService())
throw "Failed to initialize service";
@ -677,6 +686,142 @@ 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<OBSBasic*>(data);
QMetaObject::invokeMethod(&basic, "ProcessHotkey",
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<OBSBasic*>(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();
@ -690,6 +835,10 @@ OBSBasic::~OBSBasic()
delete cpuUsageTimer;
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();
@ -843,6 +992,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<obs_source_t*>(data);
auto source = obs_source_get_ref(potential_source);
if (source && pressed)
obs_set_output_source(0, source);
obs_source_release(source);
}, static_cast<obs_source_t*>(source));
signal_handler_t *handler = obs_source_get_signal_handler(source);
signal_handler_connect(handler, "item_add",
OBSBasic::SceneItemAdded, this);
@ -1449,7 +1610,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 +1797,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);
@ -2444,6 +2606,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"));
@ -2490,6 +2673,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);
@ -2504,28 +2703,19 @@ 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();
}
}
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()

View File

@ -112,6 +112,9 @@ private:
void Save(const char *file);
void Load(const char *file);
void InitHotkeys();
void CreateHotkeys();
bool InitService();
bool InitBasicConfigDefaults();
@ -149,10 +152,18 @@ 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();
void StreamingStart();
void StreamingStop(int errorcode);
void StartRecording();
void StopRecording();
void RecordingStart();
void RecordingStop();
@ -177,6 +188,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 +212,8 @@ private:
void AddSourcePopupMenu(const QPoint &pos);
void copyActionsDynamicProperties();
static void HotkeyTriggered(void *data, obs_hotkey_id id, bool pressed);
public:
OBSScene GetCurrentScene();

View File

@ -78,6 +78,8 @@ OBSBasicProperties::OBSBasicProperties(QWidget *parent, OBSSource source_)
view->setMinimumHeight(150);
view->show();
installEventFilter(CreateShortcutFilter());
connect(view, SIGNAL(PropertiesResized()),
this, SLOT(OnPropertiesResized()));

View File

@ -28,7 +28,10 @@
#include <QVariant>
#include <QTreeView>
#include <QStandardItemModel>
#include <QSpacerItem>
#include "hotkey-edit.hpp"
#include "source-label.hpp"
#include "obs-app.hpp"
#include "platform.hpp"
#include "properties-view.hpp"
@ -307,10 +310,49 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent)
// Initialize libff library
ff_init();
installEventFilter(CreateShortcutFilter());
LoadServiceTypes();
LoadEncoderTypes();
LoadColorRanges();
LoadFormats();
auto ReloadAudioSources = [](void *data, calldata_t *param)
{
auto settings = static_cast<OBSBasicSettings*>(data);
auto source = static_cast<obs_source_t*>(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<OBSBasicSettings*>(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<OBSBasicSettings*>(data);
auto key = static_cast<obs_hotkey_t*>(
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);
}
@ -1220,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<QObject*>(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<QObject*>(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<QObject*>(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<QObject*>(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<AddSource_t*>(data);
AddSource(source);
return true;
}, static_cast<void*>(&AddSource));
if (layout->rowCount() == 0)
ui->audioSourceScrollArea->hide();
else
ui->audioSourceScrollArea->show();
}
void OBSBasicSettings::LoadAudioSettings()
{
uint32_t sampleRate = config_get_uint(main->Config(), "Audio",
@ -1247,6 +1418,7 @@ void OBSBasicSettings::LoadAudioSettings()
ui->channelSetup->setCurrentIndex(1);
LoadAudioDevices();
LoadAudioSources();
loading = false;
}
@ -1276,6 +1448,290 @@ void OBSBasicSettings::LoadAdvancedSettings()
loading = false;
}
template <typename Func>
static inline void LayoutHotkey(obs_hotkey_id id, obs_hotkey_t *key, Func &&fun,
const map<obs_hotkey_id, vector<obs_key_combination_t>> &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 <typename Func, typename T>
static QLabel *makeLabel(T &t, Func &&getName)
{
return new QLabel(getName(t));
}
template <typename Func>
static QLabel *makeLabel(const OBSSource &source, Func &&)
{
return new OBSSourceLabel(source);
}
template <typename Func, typename T>
static inline void AddHotkeys(QFormLayout &layout,
Func &&getName, std::vector<
std::tuple<T, QPointer<QLabel>, QPointer<QWidget>>
> &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<T, QPointer<QLabel>, QPointer<QWidget>>;
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<obs_hotkey_id, vector<obs_key_combination_t>>;
keys_t keys;
obs_enum_hotkey_bindings([](void *data,
size_t, obs_hotkey_binding_t *binding)
{
auto &keys = *static_cast<keys_t*>(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<OBSEncoder, QPointer<QLabel>, QPointer<QWidget>>;
using outputs_elem_t =
tuple<OBSOutput, QPointer<QLabel>, QPointer<QWidget>>;
using services_elem_t =
tuple<OBSService, QPointer<QLabel>, QPointer<QWidget>>;
using sources_elem_t =
tuple<OBSSource, QPointer<QLabel>, QPointer<QWidget>>;
vector<encoders_elem_t> encoders;
vector<outputs_elem_t> outputs;
vector<services_elem_t> services;
vector<sources_elem_t> scenes;
vector<sources_elem_t> sources;
vector<obs_hotkey_id> pairIds;
map<obs_hotkey_id, pair<obs_hotkey_id, OBSHotkeyLabel*>> pairLabels;
using std::move;
auto HandleEncoder = [&](void *registerer, OBSHotkeyLabel *label,
OBSHotkeyWidget *hw)
{
auto weak_encoder =
static_cast<obs_weak_encoder_t*>(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<obs_weak_output_t*>(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<obs_weak_service_t*>(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<obs_weak_source_t*>(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_t*>(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)
@ -1288,6 +1744,8 @@ void OBSBasicSettings::LoadSettings(bool changedOnly)
LoadAudioSettings();
if (!changedOnly || videoChanged)
LoadVideoSettings();
if (!changedOnly || hotkeysChanged)
LoadHotkeySettings();
if (!changedOnly || advancedChanged)
LoadAdvancedSettings();
}
@ -1317,8 +1775,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());
"default_service", streamProperties->GetSettings(),
hotkeyData);
obs_data_release(hotkeyData);
if (!newService)
return;
@ -1549,9 +2013,53 @@ 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();
}
void OBSBasicSettings::SaveHotkeySettings()
{
const auto &config = main->Config();
using namespace std;
std::vector<obs_key_combination> 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)
@ -1564,6 +2072,8 @@ void OBSBasicSettings::SaveSettings()
SaveAudioSettings();
if (videoChanged)
SaveVideoSettings();
if (hotkeysChanged)
SaveHotkeySettings();
if (advancedChanged)
SaveAdvancedSettings();
@ -1890,6 +2400,11 @@ void OBSBasicSettings::AudioChangedRestart()
}
}
void OBSBasicSettings::ReloadAudioSources()
{
LoadAudioSources();
}
void OBSBasicSettings::VideoChangedRestart()
{
if (!loading) {
@ -1929,6 +2444,28 @@ void OBSBasicSettings::VideoChanged()
}
}
void OBSBasicSettings::HotkeysChanged()
{
using namespace std;
if (loading)
return;
hotkeysChanged = any_of(begin(hotkeys), end(hotkeys),
[](const pair<bool, QPointer<OBSHotkeyWidget>> &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) {

View File

@ -31,9 +31,34 @@ class OBSBasic;
class QAbstractButton;
class QComboBox;
class OBSPropertiesView;
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:
@ -58,11 +83,13 @@ private:
OBSBasic *main;
std::unique_ptr<Ui::OBSBasicSettings> 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 +101,19 @@ private:
OBSPropertiesView *streamEncoderProps = nullptr;
OBSPropertiesView *recordEncoderProps = nullptr;
using AudioSource_t =
std::tuple<OBSWeakSource,
QPointer<QCheckBox>, QPointer<QSpinBox>,
QPointer<QCheckBox>, QPointer<QSpinBox>>;
std::vector<AudioSource_t> audioSources;
std::vector<OBSSignal> audioSourceSignals;
OBSSignal sourceCreated;
OBSSignal channelChanged;
std::vector<std::pair<bool, QPointer<OBSHotkeyWidget>>> hotkeys;
OBSSignal hotkeyRegistered;
OBSSignal hotkeyUnregistered;
void SaveCombo(QComboBox *widget, const char *section,
const char *value);
void SaveComboData(QComboBox *widget, const char *section,
@ -91,7 +131,8 @@ private:
inline bool Changed() const
{
return generalChanged || outputsChanged || stream1Changed ||
audioChanged || videoChanged || advancedChanged;
audioChanged || videoChanged || advancedChanged ||
hotkeysChanged;
}
inline void EnableApplyButton(bool en)
@ -106,6 +147,7 @@ private:
outputsChanged = false;
audioChanged = false;
videoChanged = false;
hotkeysChanged = false;
advancedChanged= false;
EnableApplyButton(false);
}
@ -125,6 +167,7 @@ private:
void LoadOutputSettings();
void LoadAudioSettings();
void LoadVideoSettings();
void LoadHotkeySettings(obs_hotkey_id ignoreKey=OBS_INVALID_HOTKEY_ID);
void LoadAdvancedSettings();
void LoadSettings(bool changedOnly);
@ -151,6 +194,7 @@ private:
void LoadListValues(QComboBox *widget, obs_property_t *prop,
const char *configName);
void LoadAudioDevices();
void LoadAudioSources();
/* video */
void LoadRendererList();
@ -165,6 +209,7 @@ private:
void SaveOutputSettings();
void SaveAudioSettings();
void SaveVideoSettings();
void SaveHotkeySettings();
void SaveAdvancedSettings();
void SaveSettings();
@ -194,11 +239,14 @@ private slots:
void GeneralChanged();
void AudioChanged();
void AudioChangedRestart();
void ReloadAudioSources();
void OutputsChanged();
void Stream1Changed();
void VideoChanged();
void VideoChangedResolution();
void VideoChangedRestart();
void HotkeysChanged();
void ReloadHotkeys(obs_hotkey_id ignoreKey=OBS_INVALID_HOTKEY_ID);
void AdvancedChanged();
void AdvancedChangedRestart();

View File

@ -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);
@ -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);
}

View File

@ -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));

View File

@ -17,6 +17,7 @@
#include <QClipboard>
#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()

View File

@ -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,

View File

@ -15,6 +15,8 @@ OBSProjector::OBSProjector(QWidget *widget, obs_source_t *source_)
"removed", OBSSourceRemoved, this)
{
setAttribute(Qt::WA_DeleteOnClose, true);
installEventFilter(CreateShortcutFilter());
}
OBSProjector::~OBSProjector()

View File

@ -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,

View File

@ -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";

View File

@ -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);