add single instance mode
[uwplayer.git] / application / settings.c
index d3a670b..ac45d5d 100644 (file)
 #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 <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"
@@ -52,6 +59,8 @@ static void conf_load_global_settings(void);
 
 static char *uwp_config_dir;
 
+static int instance_socket = -1;
+
 /*
  * root json config object
  */
@@ -249,3 +258,181 @@ static void* player_bin_search_thread(void *data) {
 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);
+}