From 5dddf6a0fd936f4f3349d0174c1261d8fb99523c Mon Sep 17 00:00:00 2001 From: Olaf Wintermann Date: Sat, 8 Jan 2022 16:02:44 +0100 Subject: [PATCH] use unix domain socket for communication with mpv --- application/json.c | 844 +++++++++++++++++++++++++++++++++++++++++++ application/json.h | 209 +++++++++++ application/main.c | 18 +- application/player.c | 245 +++++++++++-- application/player.h | 2 + application/window.h | 11 +- configure | 2 +- make/project.xml | 2 +- 8 files changed, 1283 insertions(+), 50 deletions(-) create mode 100644 application/json.c create mode 100644 application/json.h diff --git a/application/json.c b/application/json.c new file mode 100644 index 0000000..6b126d4 --- /dev/null +++ b/application/json.c @@ -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 +#include +#include + +#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;ipos = 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;isize;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 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 index 0000000..88494bf --- /dev/null +++ b/application/json.h @@ -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 +#include + +#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 */ + diff --git a/application/main.c b/application/main.c index 6dcca3b..8a43d19 100644 --- a/application/main.c +++ b/application/main.c @@ -23,6 +23,9 @@ #include #include #include +#include +#include +#include #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); diff --git a/application/player.c b/application/player.c index 8e03fbc..d553b81 100644 --- a/application/player.c +++ b/application/player.c @@ -29,15 +29,27 @@ #include #include #include - +#include +#include +#include +#include +#include +#include #include + + 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;iipc = 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; -} diff --git a/application/player.h b/application/player.h index ebd6ab4..8d50a72 100644 --- a/application/player.h +++ b/application/player.h @@ -30,6 +30,8 @@ extern "C" { #endif +#define REQ_ID_PLAYBACK_TIME 1 + void PlayerOpenFile(MainWindow *win); void PlayerDestroy(Player *p); diff --git a/application/window.h b/application/window.h index eb47127..f63add5 100644 --- a/application/window.h +++ b/application/window.h @@ -21,8 +21,8 @@ */ -#ifndef WINDOW_H -#define WINDOW_H +#ifndef UWP_WINDOW_H +#define UWP_WINDOW_H #include #include @@ -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 */ diff --git a/configure b/configure index b640363..0207ee5 100755 --- 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 diff --git a/make/project.xml b/make/project.xml index 9ce2c49..4c29c5b 100644 --- a/make/project.xml +++ b/make/project.xml @@ -2,7 +2,7 @@ -DUI_MOTIF - -lXm -lXt -lX11 -lpthread + -lXm -lXt -lX11 -- 2.43.5