Skip to content

Commit ee5fb7b

Browse files
committed
Initial commit
0 parents  commit ee5fb7b

File tree

10 files changed

+646
-0
lines changed

10 files changed

+646
-0
lines changed

.clangd

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# fix for clang LSP complain about missing "config.h"
2+
CompileFlags:
3+
Add:
4+
- "-Ibuilddir"

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2025 Jerzy Mansarliński
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# tshotkeytrigger
2+
3+
`tshotkeytrigger` is a CLI tool that triggers hotkey actions in [Teamspeak](https://teamspeak.com/) 6 via TeamSpeak Remote Applications API.
4+
5+
Its primary purpose is to work around current limitations of TeamSpeak on Linux under Wayland compositors, the lack of support for global hotkeys.
6+
In these environments, hotkeys only work when the TeamSpeak window is focused.
7+
8+
This tool enables you to trigger actions such as "Toggle Mute" by calling TeamSpeak’s Remote API externally. You can then use your desktop environment's native global shortcut system to bind keys to run this tool.
9+
For example on Gnome: [Gnome Keyboard Shortcuts](https://help.gnome.org/users/gnome-help/stable/keyboard-shortcuts-set.html.en)
10+
11+
> **Note:** This tool cannot simulate push-to-talk key presses, as it does not support holding a key down, only discrete press and release actions can be triggered.
12+
13+
14+
## Installation
15+
16+
This tool is built to be run on Linux.
17+
18+
1. Install `libwebsockets` library.
19+
20+
Debian/Ubuntu
21+
```
22+
apt install libwebsockets
23+
```
24+
25+
Arch Linux
26+
```
27+
pacman -S libwebsockets
28+
```
29+
30+
2. Download the archive from the releases page, extract it and copy the executable to your selected directory.
31+
> **Note:** This application is still under development and has not been released.
32+
33+
34+
## Usage
35+
36+
For a list of available options and usage instructions run:
37+
```
38+
tshotkeytrigger --help
39+
```
40+
41+
### Initial set up
42+
43+
In TeamSpeak, ensure that Remote Apps are enabled - go to **Settings** -> **Remote Apps** -> **Enabled**.
44+
45+
To allow the tool to trigger an action, you’ll need to bind a virtual key press (sent by the tool) to a specific action in TeamSpeak. This can be configured under **Settings** -> **Key Bindings**.
46+
47+
The application will guide you through creating a new trigger during setup.
48+
49+
Run the following command to set up a new trigger
50+
```
51+
tshotkeytrigger --button-id <button-id> --setup
52+
```
53+
54+
`<button-id>` is a custom identifier of the virtual key you want to use. It can be any string you choose. For example "toggle.mute".
55+
56+
57+
### Trigger
58+
59+
```
60+
tshotkeytrigger --button-id <button-id>
61+
```
62+
63+
## Development
64+
65+
### Requirements
66+
67+
* [Meson](https://mesonbuild.com) with Ninja backend
68+
* C compiler - gcc
69+
* [libwebsockets](https://libwebsockets.org/) development files installed
70+
71+
### Build
72+
73+
Set up the build directory in release mode
74+
```
75+
meson setup builddir --buildtype=release
76+
```
77+
78+
Compile the project
79+
```
80+
meson compile -C builddir
81+
```
82+
83+
## License
84+
This project is licensed under the MIT License. See [LICENSE](./LICENSE) for full text.
85+
86+
87+
## Acknowledgments
88+
89+
This tool uses:
90+
* [libwebsockets](https://libwebsockets.org/) (MIT License) for WebSocket integration
91+
92+
See [THIRD_PARTY_LICENSES](./THIRD_PARTY_LICENSES).

THIRD_PARTY_LICENSES

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
This application uses libwebsockets - https://libwebsockets.org/, which is licensed under the MIT License:
2+
3+
https://opensource.org/licenses/MIT
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to
7+
deal in the Software without restriction, including without limitation the
8+
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9+
sell copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21+
IN THE SOFTWARE.
22+

config.h.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#define VERSION_STR "@version@"

datafile.c

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
#include "datafile.h"
2+
#include <libwebsockets.h>
3+
#include <stdlib.h>
4+
#include <string.h>
5+
#include <sys/stat.h>
6+
7+
static error_t load_api_key_file_path(char *result, size_t result_buf_len) {
8+
char *home_path = getenv("HOME");
9+
char *base_path = getenv("XDG_DATA_HOME");
10+
size_t buffer_used_len = 0;
11+
12+
if (base_path == NULL || strlen(base_path) == 0) {
13+
buffer_used_len = lws_snprintf(result, result_buf_len, "%s/%s", home_path,
14+
".local/share");
15+
if (buffer_used_len >= result_buf_len - 1) {
16+
lwsl_err("data file path exceeds max length");
17+
return ERR_DATA_PATH_EXCEED_MAX_PATH;
18+
}
19+
} else {
20+
buffer_used_len = lws_snprintf(result, result_buf_len, "%s", base_path);
21+
}
22+
23+
buffer_used_len =
24+
lws_snprintf(result + buffer_used_len, result_buf_len - buffer_used_len,
25+
"%s", "/tshotkeysctl/api_key");
26+
27+
if (buffer_used_len >= result_buf_len - 1) {
28+
lwsl_err("api key data file path exceeds max length");
29+
return ERR_DATA_FILE_EXCEED_MAX_PATH;
30+
}
31+
32+
lwsl_notice("api key file path: %s", result);
33+
34+
return NO_ERROR;
35+
}
36+
37+
static void ensure_data_dir_present(const char *data_path) {
38+
const char separator = '/';
39+
40+
const char *ret = data_path;
41+
int index = 0;
42+
struct stat stat_struct = {0};
43+
44+
while (ret != NULL && ret[0] != '\0') {
45+
ret = strchr(&data_path[ret - data_path], separator);
46+
47+
if (ret == NULL) {
48+
break;
49+
}
50+
51+
char *dir = strdup(data_path);
52+
index = ret - data_path;
53+
54+
dir[index + 1] = 0;
55+
56+
if (stat(dir, &stat_struct)) {
57+
lwsl_notice("creating path: %s", dir);
58+
59+
mkdir(dir, 0700);
60+
}
61+
62+
free(dir);
63+
64+
ret++;
65+
}
66+
}
67+
68+
static void load_api_key(const char *api_key_file_path, char *result,
69+
size_t result_buf_len) {
70+
FILE *file;
71+
72+
lwsl_notice("Loading API key");
73+
74+
file = fopen(api_key_file_path, "r");
75+
76+
if (file) {
77+
fgets(result, result_buf_len, file);
78+
fclose(file);
79+
lwsl_notice("API key loaded");
80+
} else {
81+
lwsl_notice("API key file not found");
82+
}
83+
}
84+
85+
const char *df_get_api_key(df_data_t *data) { return data->api_key; }
86+
87+
error_t df_init(df_data_t *data) {
88+
error_t result_code =
89+
load_api_key_file_path(data->api_key_file_path, PATH_BUF_SIZE);
90+
if (result_code != 0) {
91+
return result_code;
92+
}
93+
94+
load_api_key(data->api_key_file_path, data->api_key, API_KEY_BUF_SIZE);
95+
return NO_ERROR;
96+
}
97+
98+
error_t df_save_api_key(df_data_t *data, const char *new_api_key) {
99+
error_t result_code = NO_ERROR;
100+
FILE *file;
101+
size_t api_key_len;
102+
103+
if (strcmp(data->api_key, new_api_key) == 0) {
104+
lwsl_notice("API key is not changed. Continue...");
105+
return result_code;
106+
}
107+
108+
api_key_len = strlen(new_api_key);
109+
if (api_key_len >= API_KEY_BUF_SIZE) {
110+
111+
lwsl_warn("API key exceeds the maximum key size %d. Not saving to the file",
112+
API_KEY_BUF_SIZE - 1);
113+
return ERR_API_KEY_EXCEED_MAX_LEN;
114+
}
115+
116+
lwsl_notice("New API key - save to: %s", data->api_key_file_path);
117+
118+
ensure_data_dir_present(data->api_key_file_path);
119+
120+
file = fopen(data->api_key_file_path, "w");
121+
if (file == NULL) {
122+
lwsl_err("Error opening file: %s", data->api_key_file_path);
123+
result_code = ERR_DATA_FILE_OPEN;
124+
} else {
125+
if (fputs(new_api_key, file) < 0) {
126+
lwsl_err("Error writing api key to: %s", data->api_key_file_path);
127+
result_code = ERR_DATA_FILE_WRITE;
128+
}
129+
130+
fclose(file);
131+
}
132+
133+
return result_code;
134+
}

datafile.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#include "error.h"
2+
#include <stddef.h>
3+
4+
#define PATH_BUF_SIZE 4096
5+
#define API_KEY_BUF_SIZE 40
6+
7+
typedef struct {
8+
char api_key_file_path[PATH_BUF_SIZE];
9+
char api_key[API_KEY_BUF_SIZE];
10+
} df_data_t;
11+
12+
error_t df_init(df_data_t *data);
13+
14+
const char *df_get_api_key(df_data_t *data);
15+
16+
error_t df_save_api_key(df_data_t *data, const char *new_api_key);

error.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
typedef enum {
2+
NO_ERROR = 0,
3+
ERR_PARAMETERS,
4+
ERR_LWS_INIT,
5+
ERR_JSON_PARSE,
6+
ERR_CONNECTION_INIT,
7+
ERR_CONNECTION,
8+
ERR_DATA_PATH_EXCEED_MAX_PATH,
9+
ERR_DATA_FILE_EXCEED_MAX_PATH,
10+
ERR_API_KEY_EXCEED_MAX_LEN,
11+
ERR_DATA_FILE_OPEN,
12+
ERR_DATA_FILE_WRITE,
13+
} error_t;

0 commit comments

Comments
 (0)