#include <sys/stat.h>
#include <pthread.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+
+
+#include "main.h"
#include "utils.h"
+#include "json.h"
+#include "window.h"
+#include "playlist.h"
-#include <ucx/map.h>
-#include <ucx/properties.h>
+#include <cx/map.h>
+#include <cx/hash_map.h>
+//include <ucx/properties.h>
+#include <cx/buffer.h>
+#include <cx/utils.h>
+#include <cx/printf.h>
#define CONFIG_BASE_DIR ".config"
#define UWP_CONFIG_DIR "uwplayer"
-#define UWP_CONFIG_FILE "uwplayer.properties"
+#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 UcxMap *uwp_settings;
+
+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");
return ret;
}
-int load_settings(void) {
+int load_config(void) {
if(check_config_dir()) {
return 1;
}
- uwp_settings = ucx_map_new(16);
+ 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);
- if(!cfgfile) return 0;
int ret = 0;
- if(ucx_properties_load(uwp_settings, cfgfile)) {
- fprintf(stderr, "Error: Cannot read uwplayer settings\n");
- ret = 1;
- }
- fclose(cfgfile);
-
- if(ret) {
- return ret;
+ 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 = ucx_map_cstr_get(uwp_settings, UWP_PLAYER_BIN);
- char *player_type = ucx_map_cstr_get(uwp_settings, UWP_PLAYER_TYPE);
+ 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
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;i<s->size;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;i<buf->pos;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) {
- // TODO:
- //printf("search\n");
+ 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;i<msgbuf->size;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);
+}