use unix domain socket for communication with mpv
authorOlaf Wintermann <olaf.wintermann@gmail.com>
Sat, 8 Jan 2022 15:02:44 +0000 (16:02 +0100)
committerOlaf Wintermann <olaf.wintermann@gmail.com>
Sat, 8 Jan 2022 15:02:44 +0000 (16:02 +0100)
application/json.c [new file with mode: 0644]
application/json.h [new file with mode: 0644]
application/main.c
application/player.c
application/player.h
application/window.h
configure
make/project.xml

diff --git a/application/json.c b/application/json.c
new file mode 100644 (file)
index 0000000..6b126d4
--- /dev/null
@@ -0,0 +1,844 @@
+/*
+ * 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 <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "json.h"
+
+/*
+ * RFC 8259
+ * https://tools.ietf.org/html/rfc8259
+ */
+
+#define PARSER_STATES_ALLOC 32
+
+JSONParser* json_parser_new(void) {
+    JSONParser *parser = calloc(1, sizeof(JSONParser));
+    if(!parser) {
+        return NULL;
+    }
+    
+    parser->states_alloc = PARSER_STATES_ALLOC;
+    parser->states = calloc(PARSER_STATES_ALLOC, sizeof(int));
+    if(!parser->states) {
+        free(parser);
+        return NULL;
+    }
+    
+    parser->reader_array_alloc = 8;
+    
+    return parser;
+}
+
+void json_parser_fill(JSONParser *p, const char *buf, size_t size) {
+    p->buffer = buf;
+    p->size = size;
+    p->pos = 0;
+}
+
+
+static JSONToken nulltoken = { JSON_NO_TOKEN, NULL, 0, 0 };
+
+int token_append(JSONToken *token, const char *buf, size_t len) {
+    if(len == 0) {
+        return 0;
+    }
+    
+    size_t newlen = token->length + len;
+    if(token->alloc < newlen) {
+        char *newbuf = realloc(
+                token->alloc == 0 ? NULL : (char*)token->content,
+                newlen);
+        if(!newbuf) {
+            return 1;
+        }
+        token->content = newbuf;
+        token->alloc = newlen;
+    }
+    
+    memcpy((char*)token->content+token->length, buf, len);
+    token->length = newlen;
+    return 0;
+}
+
+JSONToken get_content(JSONParser *p, size_t start, size_t end) {
+    JSONToken token = nulltoken;
+    size_t part2 = end - start;
+    if(p->uncompleted.tokentype == JSON_NO_TOKEN) {
+        token.content = p->buffer + start;
+        token.length = part2;
+    } else if(part2 == 0) {
+        token = p->uncompleted;
+    } else {
+        if(token_append(&p->uncompleted, p->buffer+start, end - start)) {
+            return nulltoken;
+        }
+        token = p->uncompleted;
+    }
+    p->uncompleted = nulltoken;
+    return token;
+}
+
+int token_isliteral(const char *content, size_t length) {
+    if(length == 4) {
+        if(!memcmp(content, "true", 4)) {
+            return 1;
+        } else if(!memcmp(content, "null", 4)) {
+            return 1;
+        }
+    } else if(length == 5 && !memcmp(content, "false", 5)) {
+        return 1;
+    }
+    return 0;
+}
+
+static int num_isexp(const char *content, size_t length, size_t pos) {
+    if(pos >= length) {
+        return 0;
+    }
+    
+    int ok = 0;
+    for(size_t i=pos;i<length;i++) {
+        int c = content[i];
+        if(isdigit(c)) {
+            ok = 1;
+        } else if(i == pos) {
+            if(!(c == '+' || c == '-')) {
+                return 0;
+            }
+        } else {
+            return 0;
+        }
+    }
+    
+    return ok;
+}
+
+JSONTokenType token_numbertype(const char *content, size_t length) {
+    if(length == 0) return JSON_TOKEN_ERROR;
+    
+    if(content[0] != '-' && !isdigit(content[0])) {
+        return JSON_TOKEN_ERROR;
+    }
+    
+    JSONTokenType type = JSON_TOKEN_INTEGER;
+    for(size_t i=1;i<length;i++) {
+        if(content[i] == '.') {
+            if(type == JSON_TOKEN_NUMBER) {
+                return JSON_TOKEN_ERROR; // more than one decimal separator
+            }
+            type = JSON_TOKEN_NUMBER;
+        } else if(content[i] == 'e' || content[i] == 'E') {
+            return num_isexp(content, length, i+1) ? JSON_TOKEN_NUMBER : JSON_TOKEN_ERROR;
+        } else if(!isdigit(content[i])) {
+            return JSON_TOKEN_ERROR; // char is not a diget, decimal separator or exponent sep
+        }
+    }
+    
+    return type;
+}
+
+JSONToken get_token(JSONParser *p, size_t start, size_t end) {
+    JSONToken token = get_content(p, start, end);
+    if(token_isliteral(token.content, token.length)) {
+        token.tokentype = JSON_TOKEN_LITERAL;
+    } else {
+        token.tokentype = token_numbertype(token.content, token.length);
+    } 
+    p->pos = end;
+    return token;
+}
+
+static JSONTokenType char2ttype(char c) {
+    switch(c) {
+        case '[': {
+            return JSON_TOKEN_BEGIN_ARRAY;
+        }
+        case '{': {
+            return JSON_TOKEN_BEGIN_OBJECT;
+        }
+        case ']': {
+            return JSON_TOKEN_END_ARRAY;
+        }
+        case '}': {
+            return JSON_TOKEN_END_OBJECT;
+        }
+        case ':': {
+            return JSON_TOKEN_NAME_SEPARATOR;
+        }
+        case ',': {
+            return JSON_TOKEN_VALUE_SEPARATOR;
+        }
+        case '"': {
+            return JSON_TOKEN_STRING;
+        }
+        default: {
+            if(isspace(c)) {
+                return JSON_TOKEN_SPACE;
+            }
+        }
+    }
+    return JSON_NO_TOKEN;
+}
+
+JSONToken json_parser_next_token(JSONParser *p) { 
+    // current token type and start index
+    JSONTokenType ttype = p->uncompleted.tokentype;
+    size_t token_start = p->pos;
+    
+    for(size_t i=p->pos;i<p->size;i++) {
+        char c = p->buffer[i];
+        if(ttype != JSON_TOKEN_STRING) {
+            // currently non-string token
+            
+            JSONTokenType ctype = char2ttype(c); // start of new token?
+            
+            if(ttype == JSON_NO_TOKEN) {
+                if(ctype == JSON_TOKEN_SPACE) {
+                    continue;
+                } else if(ctype == JSON_TOKEN_STRING) {
+                    // begin string
+                    ttype = JSON_TOKEN_STRING;
+                    token_start = i;
+                } else if(ctype != JSON_NO_TOKEN) {
+                    // single-char token
+                    p->pos = i + 1;
+                    JSONToken token = {  ctype, NULL, 0, 0};
+                    return token;
+                } else {
+                    ttype = JSON_TOKEN_LITERAL; // number or literal
+                    token_start = i;
+                }
+            } else {
+                // finish token
+                if(ctype != JSON_NO_TOKEN) {
+                    return get_token(p, token_start, i);
+                }
+            }
+        } else {
+            // currently inside a string
+            if(!p->tokenizer_escape) {
+                if(c == '"') {
+                    JSONToken ret = get_content(p, token_start, i+1);
+                    ret.tokentype = JSON_TOKEN_STRING;
+                    p->pos = i+1;
+                    return ret;
+                } else if(c == '\\') {
+                    p->tokenizer_escape = 1;
+                }
+            } else {
+                p->tokenizer_escape = 0;
+            }
+        }
+    }
+    
+    if(ttype != JSON_NO_TOKEN) {
+        // uncompleted token
+        size_t uncompeted_len = p->size - token_start;
+        if(p->uncompleted.tokentype == JSON_NO_TOKEN) {
+            // current token is uncompleted
+            // save current token content in p->uncompleted
+            JSONToken uncompleted;
+            uncompleted.tokentype = ttype;
+            uncompleted.length = uncompeted_len;
+            uncompleted.alloc = uncompeted_len + 16;
+            char *tmp = malloc(uncompleted.alloc);
+            if(tmp) {
+                memcpy(tmp, p->buffer+token_start, uncompeted_len);
+                uncompleted.content = tmp;
+                p->uncompleted = uncompleted;
+            } else {
+                p->error = 1;
+            }
+        } else {
+            // previously we also had an uncompleted token
+            // combine the uncompleted token with the current token
+            if(token_append(&p->uncompleted, p->buffer+token_start, uncompeted_len)) {
+                p->error = 1;
+            }
+        }
+    }
+    
+    JSONToken ret = { JSON_NO_TOKEN, NULL, 0, 0};
+    return ret;
+}
+
+static int create_string(JSONToken token, JSONValue **value) {
+    JSONValue *v = malloc(sizeof(JSONValue));
+    if(!v) {
+        *value = NULL;
+        return -1;
+    }
+    v->type = JSON_STRING;
+    
+    char *str = malloc(token.length+1);
+    if(!str) {
+        free(v);
+        *value = NULL;
+        return -1;
+    }
+    memcpy(str, token.content, token.length);
+    str[token.length] = 0;
+    
+    v->type = JSON_STRING;
+    v->value.string.string = str;
+    v->value.string.length = token.length;
+    *value = v;
+    return 0;
+}
+
+typedef struct json_ustr {
+    char *ptr;
+    size_t length;
+} json_ustr;
+static json_ustr unescape_string(const char *str, size_t len) {
+    char *newstr = malloc(len+1);
+    if(!newstr) {
+        json_ustr r;
+        r.ptr = NULL;
+        r.length = 0;
+        return r;
+    }
+    
+    int j = 0;
+    int u = 0;
+    for(int i=1;i<len-1;i++) {
+        char c = str[i];
+        if(!u) {
+            if(c == '\\') {
+                u = 1;
+            } else {
+                newstr[j++] = c;
+            }
+        } else {
+            u = 0;
+            if(c == 'n') {
+                c = '\n';
+            } else if(c == 't') {
+                c = '\t';
+            }
+            newstr[j++] = c;
+        }
+    }
+    newstr[j] = 0;
+    
+    json_ustr r;
+    r.ptr = newstr;
+    r.length = j;
+    return r;
+}
+
+static int parse_integer(const char *str, size_t len, int64_t *value) {
+    char *endptr = NULL;
+    char buf[32];
+    if(len > 30) {
+        return 1;
+    }
+    memcpy(buf, str, len);
+    buf[len] = 0;
+    
+    long long v = strtoll(buf, &endptr, 10);
+    if(endptr != &buf[len]) {
+        return 1;
+    }
+    *value = (int64_t)v;
+    
+    return 0;
+}
+
+static int parse_number(const char *str, size_t len, double *value) {
+    char *endptr = NULL;
+    char buf[32];
+    if(len > 30) {
+        return 1;
+    }
+    memcpy(buf, str, len);
+    buf[len] = 0;
+    
+    double v = strtod(buf, &endptr);
+    if(endptr != &buf[len]) {
+        return 1;
+    }
+    *value = v;
+    
+    return 0;
+}
+
+static int add_state(JSONParser *p, int state) {
+    if(p->nstates >= p->states_alloc) {
+        p->states_alloc += PARSER_STATES_ALLOC;
+        p->states = realloc(p->states, p->states_alloc * sizeof(int));
+        if(!p->states) {
+            return 1;
+        }
+    }
+    p->states[++p->nstates] = state;
+    return 0;
+}
+
+static void end_elm(JSONParser *p, JSONReaderType type) {
+    p->reader_type = type;
+    p->nstates--;
+}
+
+#define JP_STATE_VALUE_BEGIN        0
+#define JP_STATE_VALUE_BEGIN_OBJ    1
+#define JP_STATE_VALUE_BEGIN_AR     2
+#define JP_STATE_ARRAY_SEP_OR_CLOSE 3
+#define JP_STATE_OBJ_NAME_OR_CLOSE  4
+#define JP_STATE_OBJ_NAME           5
+#define JP_STATE_OBJ_COLON          6
+#define JP_STATE_OBJ_SEP_OR_CLOSE   7
+
+static int next_state_after_value(int current) {
+    switch(current) {
+        default: return -1;
+        // after value JSON complete, expect nothing
+        case JP_STATE_VALUE_BEGIN: return -1; 
+        // after obj value, expect ',' or '}'
+        case JP_STATE_VALUE_BEGIN_OBJ: return JP_STATE_OBJ_SEP_OR_CLOSE;
+        // after array value, expect ',' or ']'
+        case JP_STATE_VALUE_BEGIN_AR: return JP_STATE_ARRAY_SEP_OR_CLOSE;
+    }
+}
+
+static void clear_valuename(JSONParser *p) {
+    if(p->value_name) free(p->value_name);
+    p->value_name = NULL;
+    p->value_name_len = 0;
+}
+
+static void clear_values(JSONParser *p) {
+    if(p->value_str) free(p->value_str);
+    p->value_str = NULL;
+    p->value_str_len = 0;
+    p->value_int = 0;
+    p->value_double = 0;
+}
+
+int json_read(JSONParser *p) {
+    int state = p->states[p->nstates];
+    clear_values(p);
+    JSONToken token = json_parser_next_token(p);
+    p->reader_token = token;
+
+    
+    p->value_ready = 0;
+    
+    if(token.tokentype == JSON_NO_TOKEN) {
+        return 0;
+    }
+    
+    int ret = 1;
+    
+    // 0 JP_STATE_VALUE_BEGIN          value begin
+    // 1 JP_STATE_VALUE_BEGIN_OBJ      value begin (inside object)
+    // 2 JP_STATE_VALUE_BEGIN_AR       value begin (inside array)
+    // 3 JP_STATE_ARRAY_SEP_OR_CLOSE   array, expect separator or arrayclose
+    // 4 JP_STATE_OBJ_NAME_OR_CLOSE    object, expect name or objclose
+    // 5 JP_STATE_OBJ_NAME             object, expect name
+    // 6 JP_STATE_OBJ_COLON            object, expect ':'
+    // 7 JP_STATE_OBJ_SEP_OR_CLOSE     object, expect separator, objclose
+    
+    if(state == JP_STATE_VALUE_BEGIN_AR || state == JP_STATE_OBJ_SEP_OR_CLOSE) {
+        clear_valuename(p);
+    }
+    
+    if(state < 3) {
+        // expect value
+        p->states[p->nstates] = next_state_after_value(state);
+        p->value_ready = 1;
+        switch(token.tokentype) {
+            case JSON_TOKEN_BEGIN_ARRAY: {
+                p->reader_type = JSON_READER_ARRAY_BEGIN;
+                if(add_state(p, JP_STATE_VALUE_BEGIN_AR)) return -1;
+                return 1;
+                //return json_read(p);
+            }
+            case JSON_TOKEN_BEGIN_OBJECT: {
+                p->reader_type = JSON_READER_OBJECT_BEGIN;
+                if(add_state(p, JP_STATE_OBJ_NAME_OR_CLOSE)) return -1;
+                return 1;
+                //return json_read(p);
+            }
+            case JSON_TOKEN_END_ARRAY: {
+                p->value_ready = 0;
+                end_elm(p, JSON_READER_ARRAY_END);
+                break;
+            }
+            case JSON_TOKEN_END_OBJECT: {
+                p->value_ready = 0;
+                end_elm(p, JSON_READER_OBJECT_END);
+                break;
+            }
+            case JSON_TOKEN_STRING: {
+                p->reader_type = JSON_READER_STRING;
+                json_ustr str = unescape_string(token.content, token.length);
+                if(str.ptr) {
+                    p->value_str = str.ptr;
+                    p->value_str_len = str.length;
+                } else {
+                    return -1;
+                }
+                break;
+            }
+            case JSON_TOKEN_INTEGER: {
+                p->reader_type = JSON_READER_INTEGER;
+                int64_t value;
+                if(parse_integer(token.content, token.length, &value)) {
+                    return -1;
+                }
+                p->value_int = value;
+                p->value_double = (double)value;
+                break;
+            }
+            case JSON_TOKEN_NUMBER: {
+                p->reader_type = JSON_READER_NUMBER;
+                double value;
+                if(parse_number(token.content, token.length, &value)) {
+                    return -1;
+                }
+                p->value_double = value;
+                p->value_int = (int64_t)value;
+                break;
+            }
+            case JSON_TOKEN_LITERAL: {
+                p->reader_type = JSON_READER_LITERAL;
+                break;
+            }
+            default: return -1;
+        }
+    } else if(state == JP_STATE_ARRAY_SEP_OR_CLOSE) {
+        // expect ',' or ']'
+        if(token.tokentype == JSON_TOKEN_VALUE_SEPARATOR) {
+            p->states[p->nstates] = JP_STATE_VALUE_BEGIN_AR;
+            return json_read(p);
+        } else if(token.tokentype == JSON_TOKEN_END_ARRAY) {
+            end_elm(p, JSON_READER_ARRAY_END);
+        } else {
+            return -1;
+        }
+    } else if(state == JP_STATE_OBJ_NAME_OR_CLOSE || state == JP_STATE_OBJ_NAME) {
+        if(state == JP_STATE_OBJ_NAME_OR_CLOSE && token.tokentype == JSON_TOKEN_END_OBJECT) {
+            clear_valuename(p);
+            end_elm(p, JSON_READER_OBJECT_END);
+        } else {
+            // expect string
+            if(token.tokentype != JSON_TOKEN_STRING) return -1;
+
+            if(p->value_name) free(p->value_name);
+            json_ustr valname = unescape_string(token.content, token.length);
+            p->value_name = valname.ptr;
+            p->value_name_len = valname.length;
+
+            // next state
+            p->states[p->nstates] = JP_STATE_OBJ_COLON;
+            return json_read(p);
+        }
+    } else if(state == JP_STATE_OBJ_COLON) {
+        // expect ':'
+        if(token.tokentype != JSON_TOKEN_NAME_SEPARATOR) return -1;
+        // next state
+        p->states[p->nstates] = 1;
+        return json_read(p);
+    } else if(state == 7) {
+        // expect ',' or '}]'
+        if(token.tokentype == JSON_TOKEN_VALUE_SEPARATOR) {
+            p->states[p->nstates] = JP_STATE_OBJ_NAME;
+            return json_read(p);
+        } else if(token.tokentype == JSON_TOKEN_END_OBJECT) {
+            end_elm(p, JSON_READER_OBJECT_END);
+        } else {
+            return -1;
+        }
+    }
+    
+    return ret;
+}
+
+
+JSONReaderType json_reader_type(JSONParser *p) {
+    return p->reader_type;
+}
+
+const char* json_reader_name(JSONParser *p, size_t *opt_len) {
+    if(opt_len) *opt_len = p->value_name_len;
+    return p->value_name;
+}
+
+const char* json_reader_string(JSONParser *p, size_t *opt_len) {
+    if(opt_len) *opt_len = p->value_str_len;
+    
+    if(p->reader_token.tokentype != JSON_TOKEN_STRING) {
+        return NULL;
+    }
+    
+    return p->value_str;
+}
+
+int64_t json_reader_int(JSONParser *p) {
+    return p->value_int;
+}
+
+double json_reader_double(JSONParser *p) {
+    return p->value_double;
+}
+
+int json_reader_isnull(JSONParser *p) {
+    if(p->reader_token.tokentype == JSON_TOKEN_LITERAL && p->reader_token.length == 4) {
+        return !memcmp(p->reader_token.content, "null", 4);
+    }
+    return 0;
+}
+
+JSONLiteralType json_reader_literal(JSONParser *p) {
+    const char *l = p->reader_token.content;
+    if(!strcmp(l, "true")) {
+        return JSON_TRUE;
+    } else if(!strcmp(l, "false")) {
+        return JSON_FALSE;
+    }
+    return JSON_NULL;
+}
+
+int json_reader_bool(JSONParser *p) {
+    JSONLiteralType lt = json_reader_literal(p);
+    return lt == JSON_TRUE ? 1 : 0;
+}
+
+
+/* -------------------- read value functions -------------------- */
+
+static JSONValue* init_value(JSONParser *p) {
+    JSONValue *value = malloc(sizeof(JSONValue));
+    if(!value) {
+        return NULL;
+    }
+    memset(value, 0, sizeof(JSONValue));
+    return value;
+}
+
+static int setup_read_value(JSONParser *p) {
+    p->readvalue_alloc = PARSER_STATES_ALLOC;
+    p->readvalue_nelm = 0;
+    p->readvalue_stack = calloc(PARSER_STATES_ALLOC, sizeof(JSONValue*));
+    if(!p->readvalue_stack) return -1;
+    
+    p->read_value = NULL;
+    p->readvalue_stack[0] = NULL;
+    
+    return 0;
+}
+
+static int obj_init_values(JSONParser *p, JSONValue *v) {  
+    v->value.object.values = calloc(sizeof(JSONObjValue), p->reader_array_alloc);
+    if(!v->value.object.values) {
+        return -1;
+    }
+    v->value.object.alloc = p->reader_array_alloc;
+    v->value.object.size = 0;
+    
+    return 0;
+}
+
+static int obj_add_value(JSONParser *p, JSONValue *parent, JSONObjValue v) {
+    if(!parent->value.object.values) {
+        if(obj_init_values(p, parent)) {
+            return -1;
+        }
+    }
+    
+    if(parent->value.object.size == parent->value.object.alloc) {
+        parent->value.object.alloc *= 2;
+        parent->value.object.values = realloc(parent->value.object.values, sizeof(JSONObjValue) * parent->value.object.alloc);
+        if(!parent->value.object.values) {
+            return -1;
+        }
+    }
+    
+    parent->value.object.values[parent->value.object.size++] = v;
+    
+    return 0;
+}
+
+static int array_init(JSONParser *p, JSONValue *v) {  
+    v->value.array.array = calloc(sizeof(JSONValue*), p->reader_array_alloc);
+    if(!v->value.array.array) {
+        return -1;
+    }
+    v->value.array.alloc = p->reader_array_alloc;
+    v->value.array.size = 0;
+    
+    return 0;
+}
+
+static int array_add_value(JSONParser *p, JSONValue *parent, JSONValue *v) {
+    if(!parent->value.array.array) {
+        if(array_init(p, parent)) {
+            return -1;
+        }
+    }
+    
+    if(parent->value.array.size == parent->value.array.alloc) {
+        parent->value.array.alloc *= 2;
+        parent->value.array.array = realloc(parent->value.array.array, sizeof(JSONValue*) * parent->value.array.alloc);
+        if(!parent->value.array.array) {
+            return -1;
+        }
+    }
+    
+    parent->value.array.array[parent->value.array.size++] = v;
+    
+    return 0;
+}
+
+static int add_to_parent(JSONParser *p, JSONValue *parent, JSONValue *v) {
+    if(!parent) {
+        return -1; // shouldn't happen but who knows
+    }
+    
+    int ret = 0;
+    if(parent->type == JSON_OBJECT) {
+        if(!p->value_name || p->value_name_len == 0) {
+            return -1;
+        }
+        char *valuename = p->value_name;
+        p->value_name = NULL;
+        
+        JSONObjValue newvalue;
+        newvalue.name = valuename;
+        newvalue.value = v;
+        
+        ret = obj_add_value(p, parent, newvalue);
+    } else if(parent->type == JSON_ARRAY) {
+        ret = array_add_value(p, parent, v);
+    } else {
+        ret = -1; // should also never happen
+    }
+    
+    return ret;
+}
+
+
+static int readvaluestack_add(JSONParser *p, JSONValue *v) {
+    if(p->readvalue_nelm == p->readvalue_alloc) {
+        p->readvalue_alloc *= 2;
+        p->readvalue_stack = realloc(p->readvalue_stack, sizeof(JSONValue*) * p->readvalue_alloc);
+        if(!p->readvalue_stack) {
+            return -1;
+        }
+    }
+    p->readvalue_stack[p->readvalue_nelm++] = v;
+    return 0;
+}
+
+int json_read_value(JSONParser *p, JSONValue **value) {
+    *value = NULL;
+    if(!p->readvalue_stack) {
+        if(setup_read_value(p)) return -1;
+    }
+    
+    while(p->readvalue_nelm > 0 || !p->read_value) {
+        //JSONValue *s = p->readvalue_stack[p->readvalue_nelm];
+        if(p->value_ready) {
+            // value available without another read
+            JSONValue *v = init_value(p);
+            if(!v) return -1;
+            
+            if(p->readvalue_nelm > 0) {
+                if(add_to_parent(p, p->readvalue_stack[p->readvalue_nelm-1], v)) {
+                    return -1;
+                }
+            } else {
+                // set this value as root
+                p->read_value = v;
+            }
+            
+            switch(p->reader_type) {
+                case JSON_READER_OBJECT_BEGIN: {
+                    v->type = JSON_OBJECT;
+                    if(readvaluestack_add(p, v)) {
+                        return -1;
+                    }
+                    break;
+                }
+                case JSON_READER_OBJECT_END: return -1; // should not happen
+                case JSON_READER_ARRAY_BEGIN: {
+                    v->type = JSON_ARRAY;
+                    if(readvaluestack_add(p, v)) {
+                        return -1;
+                    }
+                    break;
+                }
+                case JSON_READER_ARRAY_END: return -1; // should not happen
+                case JSON_READER_STRING: {
+                    v->type = JSON_STRING;
+                    if(p->value_str) {
+                        v->value.string.string = p->value_str;
+                        v->value.string.length = p->value_str_len;
+                        p->value_str = NULL;
+                    }
+                    break;
+                }
+                case JSON_READER_INTEGER: {
+                    v->type = JSON_INTEGER;
+                    v->value.integer.value = json_reader_int(p);
+                    break;
+                }
+                case JSON_READER_NUMBER: {
+                    v->type = JSON_NUMBER;
+                    v->value.number.value = json_reader_double(p);
+                    break;
+                }
+                case JSON_READER_LITERAL: {
+                    v->type = JSON_LITERAL;
+                    v->value.literal.literal = json_reader_literal(p);
+                    break;
+                }
+            }
+        } else if(p->readvalue_initialized) {  
+            JSONReaderType rt = p->reader_type;
+            if(rt == JSON_READER_OBJECT_END || rt == JSON_READER_ARRAY_END) {
+                p->readvalue_nelm--;
+            }
+            // else: p->value_ready is 1, this will be handled in the next run
+        }
+        
+        if(p->readvalue_nelm > 0 || !p->read_value) {
+            int r = json_read(p);
+            if(r != 1) {
+                p->readvalue_initialized = 0;
+                return r;
+            }
+            p->readvalue_initialized = 1;
+        }        
+    }
+    
+    *value = p->read_value;    
+    p->readvalue_initialized = 0;
+    p->read_value = NULL;
+    
+    return 1;
+}
+
diff --git a/application/json.h b/application/json.h
new file mode 100644 (file)
index 0000000..88494bf
--- /dev/null
@@ -0,0 +1,209 @@
+/*
+ * 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.
+ */
+
+#ifndef JSON_H
+#define JSON_H
+
+#include <stdlib.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct JSONParser    JSONParser;
+typedef struct JSONToken     JSONToken;
+
+typedef struct JSONValue     JSONValue;
+
+typedef enum JSONTokenType   JSONTokenType;
+typedef enum JSONValueType   JSONValueType;
+typedef enum JSONLiteralType JSONLiteralType;
+typedef enum JSONReaderType  JSONReaderType;
+
+typedef struct JSONArray JSONArray;
+typedef struct JSONObject JSONObject;
+typedef struct JSONString JSONString;
+typedef struct JSONInteger JSONInteger;
+typedef struct JSONNumber JSONNumber;
+typedef struct JSONLiteral JSONLiteral;
+
+typedef struct JSONObjValue JSONObjValue;
+
+typedef struct JSONReadValueStack JSONReadValueStack;
+
+enum JSONTokenType {
+    JSON_NO_TOKEN = 0,
+    JSON_TOKEN_ERROR,
+    JSON_TOKEN_BEGIN_ARRAY,
+    JSON_TOKEN_BEGIN_OBJECT,
+    JSON_TOKEN_END_ARRAY,
+    JSON_TOKEN_END_OBJECT,
+    JSON_TOKEN_NAME_SEPARATOR,
+    JSON_TOKEN_VALUE_SEPARATOR,
+    JSON_TOKEN_STRING,
+    JSON_TOKEN_INTEGER,
+    JSON_TOKEN_NUMBER,
+    JSON_TOKEN_LITERAL,
+    JSON_TOKEN_SPACE
+};
+
+enum JSONValueType {
+    JSON_OBJECT = 0,
+    JSON_ARRAY,
+    JSON_STRING,
+    JSON_INTEGER,
+    JSON_NUMBER,
+    JSON_LITERAL
+};
+
+enum JSONLiteralType {
+    JSON_NULL = 0,
+    JSON_TRUE,
+    JSON_FALSE
+};
+
+enum JSONReaderType {
+    JSON_READER_OBJECT_BEGIN,
+    JSON_READER_OBJECT_END,
+    JSON_READER_ARRAY_BEGIN,
+    JSON_READER_ARRAY_END,
+    JSON_READER_STRING,
+    JSON_READER_INTEGER,
+    JSON_READER_NUMBER,
+    JSON_READER_LITERAL
+};
+
+struct JSONToken {
+    JSONTokenType tokentype;
+    const char *content;
+    size_t length;
+    size_t alloc;
+};
+
+struct JSONParser {
+    const char *buffer;
+    size_t size;
+    size_t pos;
+    
+    JSONToken uncompleted;
+    int tokenizer_escape;
+    
+    int *states;
+    int nstates;
+    int states_alloc;
+    
+    JSONToken reader_token;
+    JSONReaderType reader_type;
+    int value_ready;
+    char *value_name;
+    size_t value_name_len;
+    char *value_str;
+    size_t value_str_len;
+    int64_t value_int;
+    double value_double;
+    
+    JSONValue **readvalue_stack;
+    int readvalue_nelm;
+    int readvalue_alloc;
+    JSONValue *read_value;
+    int readvalue_initialized;
+    
+    int reader_array_alloc;
+    
+    int error;
+};
+
+struct JSONArray {
+    JSONValue **array;
+    size_t alloc;
+    size_t size;
+};
+
+struct JSONObject {
+    JSONObjValue *values;
+    size_t alloc;
+    size_t size;
+};
+
+struct JSONObjValue {
+    char *name;
+    JSONValue *value;
+};
+
+struct JSONString {
+    char *string;
+    size_t length;
+};
+
+struct JSONInteger {
+    int64_t value;
+};
+
+struct JSONNumber {
+    double value;
+};
+
+struct JSONLiteral {
+    JSONLiteralType literal;
+};
+
+
+struct JSONValue {
+    JSONValueType type;
+    union {
+        JSONArray   array;
+        JSONObject  object;
+        JSONString  string;
+        JSONInteger integer;
+        JSONNumber  number;
+        JSONLiteral literal;
+    } value;
+};
+
+
+JSONParser* json_parser_new(void);
+
+void json_parser_fill(JSONParser *p, const char *buf, size_t size);
+
+JSONToken json_parser_next_token(JSONParser *p);
+
+int json_read(JSONParser *p);
+
+JSONReaderType json_reader_type(JSONParser *p);
+const char* json_reader_name(JSONParser *p, size_t *opt_len);
+const char* json_reader_string(JSONParser *p, size_t *opt_len);
+int64_t json_reader_int(JSONParser *p);
+double json_reader_double(JSONParser *p);
+int json_reader_isnull(JSONParser *p);
+JSONLiteralType json_reader_literal(JSONParser *p);
+int json_reader_bool(JSONParser *p);
+
+int json_read_value(JSONParser *p, JSONValue **value);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* JSON_H */
+
index 6dcca3b..8a43d19 100644 (file)
@@ -23,6 +23,9 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
+#include <locale.h>
+#include <time.h>
+#include <inttypes.h>
 
 #include "window.h"
 #include "main.h"
@@ -56,14 +59,20 @@ static String fallback[] = {
        NULL
 };
 
-int main(int argc, char** argv) { 
+static String langProc(Display *dp, String xnl, XtPointer closure) {
+    setlocale(LC_ALL, xnl);
+    setlocale(LC_NUMERIC, "C");
+    return setlocale(LC_ALL, NULL);
+}
+
+int main(int argc, char** argv) {  
     // disable stdout buffering, because the netbeans's internal terminal
     // has a bug on freebsd and doesn't flush the output after a newline
     setvbuf(stdout, NULL, _IONBF, 0);
-    
+     
     // initialize toolkit
     XtToolkitInitialize();
-    XtSetLanguageProc(NULL, NULL, NULL);
+    XtSetLanguageProc(NULL, langProc, NULL);
     app = XtCreateApplicationContext();
     XtAppSetFallbackResources(app, fallback);
     
@@ -76,6 +85,9 @@ int main(int argc, char** argv) {
     
     MainWindow *window = WindowCreate(display);
     
+    // random numbers only used for creating tmp dirs
+    srand(time(NULL));
+    
     WindowShow(window);
     XtAppMainLoop(app);
     
index 8e03fbc..d553b81 100644 (file)
 #include <spawn.h>
 #include <sys/wait.h>
 #include <signal.h>
-
+#include <poll.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <sys/un.h>
 #include <pthread.h>
 
+
+
 extern char **environ;
 
+#define STR_BUFSIZE 512
 #define WID_ARG_BUFSIZE 24
 
+#define PLAYER_POLL_TIMEOUT 500
+#define PLAYER_IN_BUFSIZE   8192
+
 static void* start_player(void *data);
-static void* player_io_thread(void *data);
+
+static void player_io(Player *p);
 
 void PlayerOpenFile(MainWindow *win) {
     pthread_t tid;
@@ -46,8 +58,50 @@ void PlayerOpenFile(MainWindow *win) {
     }
 }
 
-static void* start_player(void *data) {
-    MainWindow *win = data;
+static int prepare_player(Player *player, char *log_arg, char *ipc_arg) {
+    // create tmp directory for IPC
+    char *player_tmp = NULL;
+    char buf[STR_BUFSIZE];
+    snprintf(buf, STR_BUFSIZE, "/tmp/uwplayer-%x", rand());
+    int mkdir_success = 0;
+    for(int t=0;t<5;t++) {
+        if(!mkdir(buf, S_IRWXU)) {
+            mkdir_success = 1;
+            break;
+        } else if(errno != EEXIST) {
+            break;
+        }
+    }
+    if(!mkdir_success) return 1;
+    player_tmp = strdup(buf);
+    player->tmp = player_tmp;
+    
+    // prepare log/ipc args and create log fifo
+    int err = 0;
+    
+    if(snprintf(log_arg, STR_BUFSIZE, "--log-file=%s/%s", player_tmp, "log.fifo") >= STR_BUFSIZE) {
+        err = 1;
+    }
+    if(snprintf(ipc_arg, STR_BUFSIZE, "--input-ipc-server=%s/%s", player_tmp, "ipc.socket") >= STR_BUFSIZE) {
+        err = 1;
+    }
+    
+    snprintf(buf, STR_BUFSIZE, "%s/log.fifo", player_tmp);
+    if(err || mkfifo(buf, S_IRUSR|S_IWUSR)) {
+        rmdir(player_tmp);
+        return 1;
+    }
+    
+    return 0;
+}
+
+static int start_player_process(Player *player, MainWindow *win) {
+    char log_arg[STR_BUFSIZE];
+    char ipc_arg[STR_BUFSIZE];
+    
+    if(prepare_player(player, log_arg, ipc_arg)) {
+        return 1;
+    }
     
     char *player_bin = "/usr/local/bin/mpv"; // TODO: get bin from settings
     
@@ -55,7 +109,7 @@ static void* start_player(void *data) {
     Window wid = XtWindow(win->player_widget);
     char wid_arg[WID_ARG_BUFSIZE];
     if(snprintf(wid_arg, WID_ARG_BUFSIZE, "%lu", wid) >= WID_ARG_BUFSIZE) {
-        return NULL;
+        return 1;
     }
     
     // create player arg list
@@ -63,70 +117,181 @@ static void* start_player(void *data) {
     args[0] = player_bin;
     args[1] = "-wid";
     args[2] = wid_arg;
-    args[3] = win->file;
-    args[4] = NULL;
-    
-    // redirect stdin/stdout
-    int pout[2];
-    int pin[2];
-    if(pipe(pout)) {
-        perror("pipe");
-        return NULL;
-    }
-    if(pipe(pin)) {
-        perror("pipe");
-        return NULL;
-    }
+    args[3] = "--no-terminal";
+    args[4] = log_arg;
+    args[5] = ipc_arg;
+    args[6] = win->file;
+    args[7] = NULL;
     
     posix_spawn_file_actions_t actions;
     posix_spawn_file_actions_init(&actions);
     
-    posix_spawn_file_actions_adddup2(&actions, pin[0], STDIN_FILENO);
-    posix_spawn_file_actions_adddup2(&actions, pout[1], STDOUT_FILENO);
+    //posix_spawn_file_actions_adddup2(&actions, pin[0], STDIN_FILENO);
+    //posix_spawn_file_actions_adddup2(&actions, pout[1], STDOUT_FILENO);
     
     // start player
     pid_t player_pid;
     if(posix_spawn(&player_pid, player_bin, &actions, NULL, args, environ)) {
         perror("posix_spawn");
-        return NULL;
+        return 1;
     }
     posix_spawn_file_actions_destroy(&actions);
     
+    player->process = player_pid;
+    player->isactive = TRUE;
+    
+    return 0;
+}
+
+static int wait_for_ipc(Player *player) {
+    char buf[STR_BUFSIZE];
+    snprintf(buf, STR_BUFSIZE, "%s/log.fifo", player->tmp); // cannot fail
+    
+    // open log
+    int fd_log = open(buf, O_RDONLY);
+    if(fd_log < 0) {
+        perror("Cannot open log");
+        return 1;
+    }
+    player->log = fd_log;
+    
+    // read log until IPC socket is created
+    char *scan_str = "Listening to IPC";
+    int scan_pos = 0;
+    int scan_len = strlen(scan_str);
+    int ipc_listen = 0;
+    ssize_t r;
+    while((r = read(fd_log, buf, STR_BUFSIZE)) > 0) {
+        for(int i=0;i<r;i++) {
+            char c = buf[i];
+            char *s_str = buf+i;
+            
+            if(scan_pos == scan_len) {
+                ipc_listen = 1;
+                break;
+            }
+            
+            if(scan_str[scan_pos] == c) {
+                scan_pos++;
+            } else {
+                scan_pos = 0;
+            }
+        }
+        if(ipc_listen) break;
+    }
+    
+    return 0;
+}
+
+static int connect_to_ipc(Player *player) {
+    // connect to IPC socket
+    int fd_ipc = socket(AF_UNIX, SOCK_STREAM, 0);
+    if(fd_ipc < 0) {
+        perror("Cannot create IPC socket");
+        return 1;
+    }
+    player->ipc = fd_ipc;
+    
+    char buf[STR_BUFSIZE];
+    snprintf(buf, STR_BUFSIZE, "%s/%s", player->tmp, "ipc.socket"); // cannot fail
+    
+    struct sockaddr_un ipc_addr;
+    memset(&ipc_addr, 0, sizeof(struct sockaddr_un));
+    ipc_addr.sun_family = AF_UNIX;
+    memcpy(ipc_addr.sun_path, buf, strlen(buf));
+    if(connect(fd_ipc, (struct sockaddr *)&ipc_addr, sizeof(ipc_addr)) == -1) {
+        perror("Cannot connect to IPC socket");
+        return 1;
+    }
+    
+    return 0;
+}
+
+static void* start_player(void *data) {
+    MainWindow *win = data;
+    
     Player *player = malloc(sizeof(Player));
     memset(player, 0, sizeof(Player));
     
-    player->in = pin[1];
-    player->out = pout[0];
-    close(pin[0]);
-    close(pout[1]);
-    player->process = player_pid;
-    player->isactive = TRUE;
+    // start mpv
+    if(start_player_process(player, win)) {
+        PlayerDestroy(player);
+        return NULL;
+    } 
+    
+    // wait until IPC socket is ready
+    if(wait_for_ipc(player)) {
+        PlayerDestroy(player);
+        return NULL;
+    }
+     
+    if(connect_to_ipc(player)) {
+        PlayerDestroy(player);
+        return NULL;
+    }
     
+    // set player in main window
     if(win->player) {
         PlayerDestroy(win->player);
     }
     win->player = player;
     
-    // start thread for mplayer IO
-    pthread_t tid;
-    if(pthread_create(&tid, NULL, player_io_thread, player)) {
-        perror("pthread_create");
-    }
+    // IO
+    player_io(player);
     
     return NULL;
 }
 
+static void player_io(Player *p) {
+    int flags = fcntl(p->log, F_GETFL, 0);
+    fcntl(p->log, F_SETFL, flags | O_NONBLOCK);
+    
+    struct pollfd fds[2];
+    fds[0].fd = p->log;
+    fds[0].events = POLLIN;
+    fds[0].revents = 0;
+    fds[1].fd = p->ipc;
+    fds[1].events = POLLIN;
+    fds[1].revents = 0;
+    
+    char buf[PLAYER_IN_BUFSIZE];
+    while(poll(fds, 2, PLAYER_POLL_TIMEOUT)) {
+        if(fds[0].revents == POLLIN) {
+            // clean up fifo
+            read(fds[0].fd, buf, PLAYER_IN_BUFSIZE);
+        }
+        
+        if(fds[1].revents == POLLIN) {
+            ssize_t r;
+            if((r = read(fds[1].fd, buf, PLAYER_IN_BUFSIZE)) <= 0) {
+                break;
+            }
+            
+            write(1, buf, r);
+            
+        }
+        
+        char *cmd = "{ \"command\": [\"get_property\", \"playback-time\"] }\n";
+        write(p->ipc, cmd, strlen(cmd));
+    }
+}
+
 void PlayerDestroy(Player *p) {
+    if(p->log >= 0) {
+        close(p->log);
+    }
+    if(p->ipc >= 0) {
+        close(p->ipc);
+    }
+    
+    if(p->tmp) {
+        free(p->tmp);
+    }
+    
     if(p->isactive) {
-        close(p->in);
-        close(p->out);
         kill(p->process, SIGTERM);
     }
+    
     free(p);
 }
 
-static void* player_io_thread(void *data) {
-    Player *player = data;
-    
-    return NULL;
-}
index ebd6ab4..8d50a72 100644 (file)
@@ -30,6 +30,8 @@
 extern "C" {
 #endif
 
+#define REQ_ID_PLAYBACK_TIME 1
+    
 void PlayerOpenFile(MainWindow *win);
 
 void PlayerDestroy(Player *p);
index eb47127..f63add5 100644 (file)
@@ -21,8 +21,8 @@
  */
 
 
-#ifndef WINDOW_H
-#define WINDOW_H
+#ifndef UWP_WINDOW_H
+#define UWP_WINDOW_H
 
 #include <Xm/XmAll.h>
 #include <stdbool.h>
@@ -33,9 +33,10 @@ extern "C" {
 #endif
 
 typedef struct Player {
+    char *tmp;
     pid_t process;
-    int in;
-    int out;
+    int log;
+    int ipc;
     bool isactive;
 } Player;
     
@@ -61,5 +62,5 @@ void WindowMenubarSetVisible(MainWindow *win, bool visible);
 }
 #endif
 
-#endif /* WINDOW_H */
+#endif /* UWP_WINDOW_H */
 
index b640363..0207ee5 100755 (executable)
--- a/configure
+++ b/configure
@@ -233,7 +233,7 @@ dependency_motif()
     while true
     do
         CFLAGS="$CFLAGS -DUI_MOTIF"    
-        LDFLAGS="$LDFLAGS -lXm -lXt -lX11 -lpthread"    
+        LDFLAGS="$LDFLAGS -lXm -lXt -lX11"    
                echo yes
         return 0
     done
index 9ce2c49..4c29c5b 100644 (file)
@@ -2,7 +2,7 @@
 <project>
        <dependency name="motif">
                <cflags>-DUI_MOTIF</cflags>
-               <ldflags>-lXm -lXt -lX11 -lpthread</ldflags>
+               <ldflags>-lXm -lXt -lX11</ldflags>
        </dependency>
        
        <dependency platform="unix">