/* * Copyright 2022 Olaf Wintermann * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include "settings.h" #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "utils.h" #include "json.h" #include "window.h" #include "playlist.h" #include #include //include #include #include #include #define CONFIG_BASE_DIR ".config" #define UWP_CONFIG_DIR "uwplayer" #define UWP_CONFIG_FILE "uwplayer.conf" #define JS_READ_BUFSIZE 4096 static void* player_bin_search_thread(void *data); static void conf_load_global_settings(void); static char *uwp_config_dir; static int instance_socket = -1; /* * root json config object */ static JSONObject *uwp_config; /* * global settings from json config converted to key/value pairs */ static CxMap *uwp_settings; /* * default settings */ static CxMap *uwp_default; static int check_config_dir(void) { char *home = getenv("HOME"); if(!home) { return 1; } char *cfg_dir = util_concat_path(home, CONFIG_BASE_DIR); int ret = 0; if(mkdir(cfg_dir, S_IRWXU)) { if(errno != EEXIST) { fprintf(stderr, "Error: Cannot access %s: %s\n", cfg_dir, strerror(errno)); ret = 1; } } if(!ret) { uwp_config_dir = util_concat_path(cfg_dir, UWP_CONFIG_DIR); if(mkdir(uwp_config_dir, S_IRWXU)) { if(errno != EEXIST) { fprintf(stderr, "Error: Cannot access %s: %s\n", uwp_config_dir, strerror(errno)); ret = 1; } } } free(cfg_dir); return ret; } int load_config(void) { if(check_config_dir()) { return 1; } uwp_settings = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 16); uwp_default = cxHashMapCreate(cxDefaultAllocator, CX_STORE_POINTERS, 32); char *cfgfile_path = util_concat_path(uwp_config_dir, UWP_CONFIG_FILE); FILE *cfgfile = fopen(cfgfile_path, "r"); free(cfgfile_path); int ret = 0; if(cfgfile) { JSONParser *parser = json_parser_new(); JSONValue *value = NULL; char buf[JS_READ_BUFSIZE]; size_t r; while((ret = json_read_value(parser, &value)) >= 0) { if(ret == 0) { r = fread(buf, 1, JS_READ_BUFSIZE, cfgfile); if(r == 0) { break; } json_parser_fill(parser, buf, r); } else { break; } } json_parser_free(parser); if(value) { if(value->type == JSON_OBJECT) { ret = 0; uwp_config = &value->value.object; conf_load_global_settings(); } else { ret = 1; } } else { ret = 1; } fclose(cfgfile); if(ret) { return ret; } } // check if mpv or mplayer binaries are configured char *player_bin = cxMapGet(uwp_settings, cx_hash_key_str(UWP_PLAYER_BIN)); char *player_type = cxMapGet(uwp_settings, cx_hash_key_str(UWP_PLAYER_TYPE)); if(!player_bin) { // try to find the mpv or mplayer binary path pthread_t st; pthread_create(&st, NULL, player_bin_search_thread, NULL); } else if(!player_type) { fprintf(stderr, "Warning: unknown player type (mplayer, mpv)\n"); } return 0; } static void conf_load_global_settings(void) { JSONValue *settings = json_obj_get(uwp_config, "settings"); if(!settings) { return; } if(settings->type != JSON_OBJECT) { fprintf(stderr, "Warning: 'settings' not an object\n"); return; } JSONObject *s = &settings->value.object; for(size_t i=0;isize;i++) { JSONObjValue *gs = &s->values[i]; if(gs->value->type == JSON_STRING) { cxMapPut(uwp_settings, cx_hash_key_str(gs->name), strdup(gs->value->value.string.string)); } } } static char* get_which_output(FILE *f, CxBuffer *buf) { buf->pos = 0; buf->size = 0; cx_stream_copy(f, buf, (cx_read_func)fread, (cx_write_func)cxBufferWrite); if(!pclose(f)) { cxBufferPut(buf, 0); size_t i; for(i=0;ipos;i++) { if(buf->space[i] == '\n') { buf->space[i] = 0; break; } } return buf->space; } return NULL; } static Boolean finish_bin_search(XtPointer data) { PlayerInfo *playerInfo = data; cxMapPut(uwp_settings, cx_hash_key_str(UWP_PLAYER_BIN), playerInfo->bin); cxMapPut(uwp_settings, cx_hash_key_str(UWP_PLAYER_TYPE), playerInfo->type); free(playerInfo); return 0; } static void* player_bin_search_thread(void *data) { CxBuffer buf; cxBufferInit(&buf, NULL, 256, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); FILE *f = popen("which mpv", "r"); if(f) { char *bin = get_which_output(f, &buf); if(bin) { PlayerInfo *playerInfo = malloc(sizeof(PlayerInfo)); playerInfo->bin = strdup(bin); playerInfo->type = strdup("mpv"); AppExecProc(finish_bin_search, playerInfo); cxBufferDestroy(&buf); return NULL; } } f = popen("which mplayer", "r"); if(f) { char *bin = get_which_output(f, &buf); if(bin) { PlayerInfo *playerInfo = malloc(sizeof(PlayerInfo)); playerInfo->bin = strdup(bin); playerInfo->type = strdup("mplayer"); AppExecProc(finish_bin_search, playerInfo); } } cxBufferDestroy(&buf); return NULL; } char* SettingsGetPlayerBin(void) { return cxMapGet(uwp_settings, cx_hash_key_str(UWP_PLAYER_BIN)); } char *InstanceFilePath(Display *dp) { cxmutstr instance_file = cx_asprintf("instance%s", DisplayString(dp)); char *path = util_concat_path(uwp_config_dir, instance_file.ptr); free(instance_file.ptr); return path; } int ConnectToInstance(const char *path) { int fd = socket(AF_UNIX, SOCK_STREAM, 0); if(!fd) { return -1; } size_t path_len = strlen(path); struct sockaddr_un addr; if(path_len > sizeof(addr.sun_path)-1) { return -1; } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; memcpy(addr.sun_path, path, strlen(path) + 1); if(connect(fd, (struct sockaddr*)&addr, sizeof(addr))) { close(fd); return -1; } return fd; } int CreateSingleInstanceSocket(Display *dp, Bool *already_running) { char *instance_path = InstanceFilePath(dp); size_t instance_path_len = strlen(instance_path); *already_running = 0; // check path struct sockaddr_un addr; if(instance_path_len > sizeof(addr.sun_path)-1) { fprintf(stderr, "instance path '%s' too long for unix domain socket", instance_path); free(instance_path); return 1; } // check if the socket already exists and is open struct stat s; if(!stat(instance_path, &s)) { int fd = ConnectToInstance(instance_path); close(fd); if(fd >= 0) { *already_running = 1; return 0; // instance already running } // instance not running but socket file exists // remove socket before creating a new socket unlink(instance_path); } memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; memcpy(addr.sun_path, instance_path, instance_path_len+1); free(instance_path); instance_socket = socket(AF_UNIX, SOCK_STREAM, 0); if(instance_socket < 0) { fprintf(stderr, "cannot create instance socket: %s", strerror(errno)); return 1; } if(bind(instance_socket, (struct sockaddr*)&addr, sizeof(addr))) { fprintf(stderr, "cannot bind instance socket: %s", strerror(errno)); return 1; } pthread_t tid; if(pthread_create(&tid, NULL, instance_socket_thread, NULL)) { close(instance_socket); instance_socket = -1; return -1; } pthread_detach(tid); return 0; } static Boolean cmd_open(XtPointer data) { MainWindow *win = GetMainWindow(); char *file = data; printf("open %s\n", file); PlayListClear(win); PlayListAddFile(win, file); PlayListPlayTrack(win, win->playlist.tracks->size-1); free(data); return 0; } static void process_msg(CxBuffer *msgbuf, size_t *rpos) { cxstring msg = cx_strn(NULL, 0); for(size_t i=*rpos;isize;i++) { if(msgbuf->space[i] == '\n') { msg.ptr = msgbuf->space + *rpos; msg.length = i - *rpos; *rpos = i+1; break; } } if(msg.length > 0) { if(cx_strprefix(msg, CX_STR("open "))) { cxstring file = cx_strsubs(msg, 5); cxmutstr mfile = cx_strdup(file); AppExecProc(cmd_open, mfile.ptr); } else { fprintf(stderr, "unknown instance command: {%.*s}\n", (int)msg.length, msg.ptr); } if(*rpos < msgbuf->size) { process_msg(msgbuf, rpos); } } } void* instance_socket_thread(void *data) { listen(instance_socket, 8); CxBuffer msgbuf; cxBufferInit(&msgbuf, NULL, 1024, cxDefaultAllocator, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); char buf[1024]; while(TRUE) { printf("accept instance socket\n"); int fd = accept(instance_socket, NULL, 0); if(fd < 0) { break; } printf("accept instance connection\n"); msgbuf.pos = 0; msgbuf.size = 0; size_t rpos = 0; ssize_t r; while((r = read(fd, buf, 1024)) > 0) { cxBufferWrite(buf, 1, r, &msgbuf); process_msg(&msgbuf, &rpos); } printf("close instance connection\n"); close(fd); } cxBufferDestroy(&msgbuf); printf("instance socket shutdown\n"); return NULL; } void ShutdownInstanceSocket(Display *dp) { if(instance_socket < 0) { return; } shutdown(instance_socket, SHUT_RDWR); close(instance_socket); instance_socket = -1; char *instance_path = InstanceFilePath(dp); unlink(instance_path); free(instance_path); }