2 * Copyright 2022 Olaf Wintermann
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
34 #include <sys/socket.h>
45 #include <cx/hash_map.h>
46 //include <ucx/properties.h>
47 #include <cx/buffer.h>
49 #include <cx/printf.h>
51 #define CONFIG_BASE_DIR ".config"
52 #define UWP_CONFIG_DIR "uwplayer"
53 #define UWP_CONFIG_FILE "uwplayer.conf"
55 #define JS_READ_BUFSIZE 4096
57 static void* player_bin_search_thread(void *data);
58 static void conf_load_global_settings(void);
60 static char *uwp_config_dir;
62 static int instance_socket = -1;
65 * root json config object
67 static JSONObject *uwp_config;
70 * global settings from json config converted to key/value pairs
72 static CxMap *uwp_settings;
77 static CxMap *uwp_default;
79 static int check_config_dir(void) {
80 char *home = getenv("HOME");
85 char *cfg_dir = util_concat_path(home, CONFIG_BASE_DIR);
87 if(mkdir(cfg_dir, S_IRWXU)) {
89 fprintf(stderr, "Error: Cannot access %s: %s\n", cfg_dir, strerror(errno));
95 uwp_config_dir = util_concat_path(cfg_dir, UWP_CONFIG_DIR);
96 if(mkdir(uwp_config_dir, S_IRWXU)) {
98 fprintf(stderr, "Error: Cannot access %s: %s\n", uwp_config_dir, strerror(errno));
108 int load_config(void) {
109 if(check_config_dir()) {
113 uwp_settings = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16);
114 uwp_default = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32);
116 char *cfgfile_path = util_concat_path(uwp_config_dir, UWP_CONFIG_FILE);
117 FILE *cfgfile = fopen(cfgfile_path, "r");
122 JSONParser *parser = json_parser_new();
124 JSONValue *value = NULL;
125 char buf[JS_READ_BUFSIZE];
128 while((ret = json_read_value(parser, &value)) >= 0) {
130 r = fread(buf, 1, JS_READ_BUFSIZE, cfgfile);
134 json_parser_fill(parser, buf, r);
140 json_parser_free(parser);
143 if(value->type == JSON_OBJECT) {
145 uwp_config = &value->value.object;
146 conf_load_global_settings();
163 // check if mpv or mplayer binaries are configured
164 char *player_bin = cxMapGet(uwp_settings, cx_hash_key_str(UWP_PLAYER_BIN));
165 char *player_type = cxMapGet(uwp_settings, cx_hash_key_str(UWP_PLAYER_TYPE));
168 // try to find the mpv or mplayer binary path
170 pthread_create(&st, NULL, player_bin_search_thread, NULL);
171 } else if(!player_type) {
172 fprintf(stderr, "Warning: unknown player type (mplayer, mpv)\n");
178 static void conf_load_global_settings(void) {
179 JSONValue *settings = json_obj_get(uwp_config, "settings");
184 if(settings->type != JSON_OBJECT) {
185 fprintf(stderr, "Warning: 'settings' not an object\n");
189 JSONObject *s = &settings->value.object;
191 for(size_t i=0;i<s->size;i++) {
192 JSONObjValue *gs = &s->values[i];
193 if(gs->value->type == JSON_STRING) {
194 cxMapPut(uwp_settings, cx_hash_key_str(gs->name), strdup(gs->value->value.string.string));
199 static char* get_which_output(FILE *f, CxBuffer *buf) {
202 cx_stream_copy(f, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite);
206 for(i=0;i<buf->pos;i++) {
207 if(buf->space[i] == '\n') {
217 static Boolean finish_bin_search(XtPointer data) {
218 PlayerInfo *playerInfo = data;
219 cxMapPut(uwp_settings, cx_hash_key_str(UWP_PLAYER_BIN), playerInfo->bin);
220 cxMapPut(uwp_settings, cx_hash_key_str(UWP_PLAYER_TYPE), playerInfo->type);
225 static void* player_bin_search_thread(void *data) {
227 cxBufferInit(&buf, NULL, 256, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
229 FILE *f = popen("which mpv", "r");
231 char *bin = get_which_output(f, &buf);
233 PlayerInfo *playerInfo = malloc(sizeof(PlayerInfo));
234 playerInfo->bin = strdup(bin);
235 playerInfo->type = strdup("mpv");
236 AppExecProc(finish_bin_search, playerInfo);
238 cxBufferDestroy(&buf);
243 f = popen("which mplayer", "r");
245 char *bin = get_which_output(f, &buf);
247 PlayerInfo *playerInfo = malloc(sizeof(PlayerInfo));
248 playerInfo->bin = strdup(bin);
249 playerInfo->type = strdup("mplayer");
250 AppExecProc(finish_bin_search, playerInfo);
254 cxBufferDestroy(&buf);
258 char* SettingsGetPlayerBin(void) {
259 return cxMapGet(uwp_settings, cx_hash_key_str(UWP_PLAYER_BIN));
263 char *InstanceFilePath(Display *dp) {
264 cxmutstr instance_file = cx_asprintf("instance%s", DisplayString(dp));
265 char *path = util_concat_path(uwp_config_dir, instance_file.ptr);
266 free(instance_file.ptr);
270 int ConnectToInstance(const char *path) {
271 int fd = socket(AF_UNIX, SOCK_STREAM, 0);
276 size_t path_len = strlen(path);
278 struct sockaddr_un addr;
279 if(path_len > sizeof(addr.sun_path)-1) {
283 memset(&addr, 0, sizeof(addr));
284 addr.sun_family = AF_UNIX;
285 memcpy(addr.sun_path, path, strlen(path) + 1);
287 if(connect(fd, (struct sockaddr*)&addr, sizeof(addr))) {
295 int CreateSingleInstanceSocket(Display *dp, Bool *already_running) {
296 char *instance_path = InstanceFilePath(dp);
297 size_t instance_path_len = strlen(instance_path);
298 *already_running = 0;
301 struct sockaddr_un addr;
302 if(instance_path_len > sizeof(addr.sun_path)-1) {
303 fprintf(stderr, "instance path '%s' too long for unix domain socket", instance_path);
308 // check if the socket already exists and is open
310 if(!stat(instance_path, &s)) {
311 int fd = ConnectToInstance(instance_path);
314 *already_running = 1;
315 return 0; // instance already running
318 // instance not running but socket file exists
319 // remove socket before creating a new socket
320 unlink(instance_path);
323 memset(&addr, 0, sizeof(addr));
324 addr.sun_family = AF_UNIX;
325 memcpy(addr.sun_path, instance_path, instance_path_len+1);
328 instance_socket = socket(AF_UNIX, SOCK_STREAM, 0);
329 if(instance_socket < 0) {
330 fprintf(stderr, "cannot create instance socket: %s", strerror(errno));
334 if(bind(instance_socket, (struct sockaddr*)&addr, sizeof(addr))) {
335 fprintf(stderr, "cannot bind instance socket: %s", strerror(errno));
340 if(pthread_create(&tid, NULL, instance_socket_thread, NULL)) {
341 close(instance_socket);
342 instance_socket = -1;
350 static Boolean cmd_open(XtPointer data) {
351 MainWindow *win = GetMainWindow();
353 printf("open %s\n", file);
356 PlayListAddFile(win, file);
357 PlayListPlayTrack(win, win->playlist.tracks->size-1);
363 static void process_msg(CxBuffer *msgbuf, size_t *rpos) {
364 cxstring msg = cx_strn(NULL, 0);
365 for(size_t i=*rpos;i<msgbuf->size;i++) {
366 if(msgbuf->space[i] == '\n') {
367 msg.ptr = msgbuf->space + *rpos;
368 msg.length = i - *rpos;
375 if(cx_strprefix(msg, CX_STR("open "))) {
376 cxstring file = cx_strsubs(msg, 5);
377 cxmutstr mfile = cx_strdup(file);
378 AppExecProc(cmd_open, mfile.ptr);
380 fprintf(stderr, "unknown instance command: {%.*s}\n", (int)msg.length, msg.ptr);
383 if(*rpos < msgbuf->size) {
384 process_msg(msgbuf, rpos);
389 void* instance_socket_thread(void *data) {
390 listen(instance_socket, 8);
393 cxBufferInit(&msgbuf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS);
398 printf("accept instance socket\n");
399 int fd = accept(instance_socket, NULL, 0);
403 printf("accept instance connection\n");
410 while((r = read(fd, buf, 1024)) > 0) {
411 cxBufferWrite(buf, 1, r, &msgbuf);
412 process_msg(&msgbuf, &rpos);
415 printf("close instance connection\n");
419 cxBufferDestroy(&msgbuf);
421 printf("instance socket shutdown\n");
426 void ShutdownInstanceSocket(Display *dp) {
427 if(instance_socket < 0) {
431 shutdown(instance_socket, SHUT_RDWR);
432 close(instance_socket);
433 instance_socket = -1;
435 char *instance_path = InstanceFilePath(dp);
436 unlink(instance_path);