Sun, 30 Nov 2025 18:15:46 +0100
add initial client code
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/Makefile Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,57 @@ +# +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. +# +# Copyright 2011 Olaf Wintermann. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +BUILD_ROOT = .. +include ../config.mk + +CFLAGS += -I../ui/ -I../ucx + +APP_BIN_OBJ = ../build/client/main$(OBJ_EXT) +APP_BIN_OBJ += ../build/client/message$(OBJ_EXT) +APP_BIN_OBJ += ../build/client/uiclient$(OBJ_EXT) +APP_BIN_OBJ += ../build/client/args$(OBJ_EXT) + +APP_BIN = ../build/$(BUILD_BIN_DIR)/ui-client$(APP_EXT) + +all: + @if test -z "$(DISABLE_CLIENT)"; then \ + $(MAKE) client; \ + else \ + echo "ui-client disabled"; \ + fi + +include $(SYS_MAKEFILE) + +client: $(APP_BIN) + +$(APP_BIN): $(APP_BIN_OBJ) $(RES_FILE) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) + $(LD) -o $(APP_BIN) $(APP_BIN_OBJ) $(RES_FILE) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)uitk$(LIB_EXT) $(BUILD_ROOT)/build/$(BUILD_LIB_DIR)/$(LIB_PREFIX)ucx$(LIB_EXT) $(LDFLAGS) $(TK_LDFLAGS) $(CLIENT_LDFLAGS) + +../build/client/%$(OBJ_EXT): %.c + $(CC) $(CFLAGS) $(TK_CFLAGS) $(CLIENT_CFLAGS) -o $@ -c $< +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/Makefile.win32 Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,5 @@ +RES_FILE = app.res + +$(RES_FILE): app.rc app.manifest + llvm-rc app.rc +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/app.manifest Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> + <dependency> + <dependentAssembly> + <assemblyIdentity + type="win32" + name="Microsoft.Windows.Common-Controls" + version="6.0.0.0" + processorArchitecture="*" + publicKeyToken="6595b64144ccf1df" + language="*" + /> + </dependentAssembly> + </dependency> +</assembly>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/app.rc Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,1 @@ +1 24 "app.manifest"
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/args.c Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,234 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "args.h" + +#include "../ui/common/args.h" + +#define DEFAULT_ARG_FUNCS(var, prefix) \ + static ArgDefaultFuncs var = { \ + .fill = (argfunc_set_bool)prefix##_args_set_fill, \ + .hexpand = (argfunc_set_bool)prefix##_args_set_hexpand, \ + .vexpand = (argfunc_set_bool)prefix##_args_set_vexpand, \ + .hfill = (argfunc_set_bool)prefix##_args_set_hfill, \ + .vfill = (argfunc_set_bool)prefix##_args_set_vfill, \ + .override_defaults = (argfunc_set_bool)prefix##_args_set_override_defaults, \ + .margin = (argfunc_set_int)prefix##_args_set_margin, \ + .margin_left = (argfunc_set_int)prefix##_args_set_margin_left, \ + .margin_right = (argfunc_set_int)prefix##_args_set_margin_right, \ + .margin_top = (argfunc_set_int)prefix##_args_set_margin_top, \ + .margin_bottom = (argfunc_set_int)prefix##_args_set_margin_bottom, \ + .colspan = (argfunc_set_int)prefix##_args_set_colspan, \ + .rowspan = (argfunc_set_int)prefix##_args_set_rowspan, \ + .name = (argfunc_set_str)prefix##_args_set_name, \ + .style_class = (argfunc_set_str)prefix##_args_set_style_class \ + } + +DEFAULT_ARG_FUNCS(container_args, ui_container); + +DEFAULT_ARG_FUNCS(button_args, ui_button); + + + +static void init_common_args(const CxJsonValue *value, void *args, ArgDefaultFuncs *funcs) { + CxJsonValue *val; + + // boolean args + val = cxJsonObjGet(value, "fill"); + if(val && val->type == CX_JSON_LITERAL && val->value.literal == CX_JSON_TRUE) { + funcs->fill(args, TRUE); + } + + val = cxJsonObjGet(value, "hexpand"); + if(val && val->type == CX_JSON_LITERAL && val->value.literal == CX_JSON_TRUE) { + funcs->hexpand(args, TRUE); + } + + val = cxJsonObjGet(value, "vexpand"); + if(val && val->type == CX_JSON_LITERAL && val->value.literal == CX_JSON_TRUE) { + funcs->vexpand(args, TRUE); + } + + val = cxJsonObjGet(value, "hfill"); + if(val && val->type == CX_JSON_LITERAL && val->value.literal == CX_JSON_TRUE) { + funcs->hfill(args, TRUE); + } + + val = cxJsonObjGet(value, "vfill"); + if(val && val->type == CX_JSON_LITERAL && val->value.literal == CX_JSON_TRUE) { + funcs->vfill(args, TRUE); + } + + val = cxJsonObjGet(value, "override_defaults"); + if(val && val->type == CX_JSON_LITERAL && val->value.literal == CX_JSON_TRUE) { + funcs->override_defaults(args, TRUE); + } + + // int args + val = cxJsonObjGet(value, "margin"); + if(val && val->type == CX_JSON_INTEGER) { + funcs->margin(args, (int)val->value.integer); + } + + val = cxJsonObjGet(value, "margin_left"); + if(val && val->type == CX_JSON_INTEGER) { + funcs->margin_left(args, (int)val->value.integer); + } + + val = cxJsonObjGet(value, "margin_right"); + if(val && val->type == CX_JSON_INTEGER) { + funcs->margin_right(args, (int)val->value.integer); + } + + val = cxJsonObjGet(value, "margin_top"); + if(val && val->type == CX_JSON_INTEGER) { + funcs->margin_top(args, (int)val->value.integer); + } + + val = cxJsonObjGet(value, "margin_bottom"); + if(val && val->type == CX_JSON_INTEGER) { + funcs->margin_bottom(args, (int)val->value.integer); + } + + val = cxJsonObjGet(value, "colspan"); + if(val && val->type == CX_JSON_INTEGER) { + funcs->colspan(args, (int)val->value.integer); + } + + val = cxJsonObjGet(value, "rowspan"); + if(val && val->type == CX_JSON_INTEGER) { + funcs->rowspan(args, (int)val->value.integer); + } + + // string args + val = cxJsonObjGet(value, "name"); + if(val && val->type == CX_JSON_STRING) { + funcs->name(args, val->value.string.ptr); + } + + val = cxJsonObjGet(value, "style_class"); + if(val && val->type == CX_JSON_STRING) { + funcs->name(args, val->value.string.ptr); + } +} + +void init_groups(const CxJsonValue *value, void *args, argfunc_set_intarray setarray) { + CxJsonValue *val = cxJsonObjGet(value, "states"); + if(!val || val->type != CX_JSON_ARRAY) { + return; + } + + int len = (int)val->value.array.array_size; + int *states = calloc(len, sizeof(int)); + for(int i=0;i<len;i++) { + CxJsonValue *s = val->value.array.array[i]; + if(s->type == CX_JSON_INTEGER) { + states[i] = (int)s->value.integer; + } + } + + setarray(args, states, len); +} + +UiContainerArgs* json2container_args(const CxJsonValue *value) { + UiContainerArgs *args = ui_container_args_new(); + if(value->type != CX_JSON_OBJECT) { + return args; + } + + init_common_args(value, args, &container_args); + + CxJsonValue *val = cxJsonObjGet(value, "spacing"); + if(val && val->type == CX_JSON_INTEGER) { + args->spacing = (int)val->value.integer; + } + + val = cxJsonObjGet(value, "columnspacing"); + if(val && val->type == CX_JSON_INTEGER) { + args->columnspacing = (int)val->value.integer; + } + + val = cxJsonObjGet(value, "rowspacing"); + if(val && val->type == CX_JSON_INTEGER) { + args->rowspacing = (int)val->value.integer; + } + + val = cxJsonObjGet(value, "def_hfill"); + if(val && val->type == CX_JSON_LITERAL && val->value.literal == CX_JSON_TRUE) { + args->def_hfill = TRUE; + } + + val = cxJsonObjGet(value, "def_vfill"); + if(val && val->type == CX_JSON_LITERAL && val->value.literal == CX_JSON_TRUE) { + args->def_vfill = TRUE; + } + + val = cxJsonObjGet(value, "def_hexpand"); + if(val && val->type == CX_JSON_LITERAL && val->value.literal == CX_JSON_TRUE) { + args->def_hexpand = TRUE; + } + + val = cxJsonObjGet(value, "def_vexpand"); + if(val && val->type == CX_JSON_LITERAL && val->value.literal == CX_JSON_TRUE) { + args->def_vexpand = TRUE; + } + + return args; +} + +UiButtonArgs* json2button_args(const CxJsonValue *value) { + UiButtonArgs *args = ui_button_args_new(); + if(value->type != CX_JSON_OBJECT) { + return args; + } + + init_common_args(value, args, &button_args); + init_groups(value, args, (argfunc_set_intarray)ui_button_args_set_groups); + + CxJsonValue *val = cxJsonObjGet(value, "label"); + if(val && val->type == CX_JSON_STRING) { + ui_button_args_set_label(args, val->value.string.ptr); + } + + val = cxJsonObjGet(value, "icon"); + if(val && val->type == CX_JSON_STRING) { + ui_button_args_set_icon(args, val->value.string.ptr); + } + + val = cxJsonObjGet(value, "tooltip"); + if(val && val->type == CX_JSON_STRING) { + ui_button_args_set_tooltip(args, val->value.string.ptr); + } + + val = cxJsonObjGet(value, "labeltype"); + if(val && val->type == CX_JSON_INTEGER) { + ui_button_args_set_labeltype(args, (int)val->value.integer); + } + + return args; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/args.h Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,73 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CLIENT_ARGS_H +#define CLIENT_ARGS_H + +#include <ui/ui.h> +#include <cx/json.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void(*argfunc_set_bool)(void *, UiBool); +typedef void(*argfunc_set_int)(void *, int); +typedef void(*argfunc_set_str)(void *, const char*); +typedef void(*argfunc_set_intarray)(void *, int *, int); + +typedef struct ArgDefaultFuncs { + argfunc_set_bool fill; + argfunc_set_bool hexpand; + argfunc_set_bool vexpand; + argfunc_set_bool hfill; + argfunc_set_bool vfill; + argfunc_set_bool override_defaults; + argfunc_set_int margin; + argfunc_set_int margin_left; + argfunc_set_int margin_right; + argfunc_set_int margin_top; + argfunc_set_int margin_bottom; + argfunc_set_int colspan; + argfunc_set_int rowspan; + argfunc_set_str name; + argfunc_set_str style_class; + +} ArgDefaultFuncs; + +UiContainerArgs* json2container_args(const CxJsonValue *value); + +UiButtonArgs* json2button_args(const CxJsonValue *value); + + +#ifdef __cplusplus +} +#endif + +#endif /* CLIENT_ARGS_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/main.c Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,204 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <ui/ui.h> +#include <cx/printf.h> +#include <pthread.h> + +#include "main.h" +#include "uiclient.h" + +/* + * debug window that is always created + */ +static UiObject *debug_window; + +/* + * test window, that is only created in testing mode + */ +static UiObject *test_window; + +static int test_mode = 0; + +static int input_fd[2]; +static int output_fd[2]; + +int main(int argc, char **argv) { + test_mode = 1; + + ui_init(NULL, argc, argv); + ui_onstartup(application_onstartup, NULL); + ui_onopen(application_onopen, NULL); + ui_onexit(application_onexit, NULL); + + int in = STDIN_FILENO; + int out = STDOUT_FILENO; + if(test_mode) { + if(pipe(input_fd)) { + perror("pipe"); + return 1; + } + if(pipe(output_fd)) { + perror("pipe"); + return 1; + } + + in = input_fd[0]; + out = output_fd[1]; + + pthread_t tid; + if(pthread_create(&tid, NULL, testwindow_read_thread, NULL)) { + perror("pthread_create"); + return 1; + } + if(pthread_detach(tid)) { + perror("pthread_detach"); + return 1; + } + } + MessageHandler *h = simple_msg_handler(in, out, client_msg_received); + client_init(h); + h->start(h); + + ui_main(); + + h->stop(h); + fprintf(stderr, "client: end"); + + return 0; +} + +void application_onstartup(UiEvent *event, void *userdata) { + // We need at least one window for the event loop to work. + // Create a debug window, that is invisible by default + debug_window = ui_simple_window("debug", NULL); + // TODO: debug UI + + if(test_mode) { + testwindow_create(); + } +} + +void application_onopen(UiEvent *event, void *userdata) { + +} + +void application_onexit(UiEvent *event, void *userdata) { + +} + +static void testwindow_close(UiEvent *event, void *userdata) { + ui_close(debug_window); + ui_app_quit(); + exit(0); +} + +static void testwindow_send(UiEvent *event, void *userdata) { + TestWindow *window = event->window; + + char *str = ui_get(window->input); + int len = strlen(str); + cxmutstr msg = cx_asprintf("%d\n%s", len, str); + write(input_fd[1], msg.ptr, msg.length); + free(msg.ptr); + + ui_set(window->input, ""); +} + +static void testwindow_clear_output(UiEvent *event, void *userdata) { + TestWindow *window = event->window; + ui_set(window->output, ""); +} + +void testwindow_create(void) { + UiObject *obj = ui_simple_window("Test", NULL); + ui_context_closefunc(obj->ctx, testwindow_close, NULL); + ui_window_size(obj, 1800, 1400); + + TestWindow *window = ui_malloc(obj->ctx, sizeof(TestWindow)); + window->input = ui_text_new(obj->ctx, NULL); + window->output = ui_text_new(obj->ctx, NULL); + obj->window = window; + + ui_hsplitpane(obj, .fill = TRUE, .initial_position = 900) { + // left + ui_vbox(obj, .fill = TRUE) { + ui_grid(obj, .margin = 10, .columnspacing = 10, .rowspacing = 10, .fill = TRUE) { + ui_llabel(obj, .label = "Input", .style = UI_LABEL_STYLE_TITLE, .hexpand = TRUE, .hfill = TRUE); + ui_newline(obj); + ui_textarea(obj, .value = window->input, .fill = TRUE); + ui_newline(obj); + ui_button(obj, .label = "Send", .onclick = testwindow_send); + } + } + + + // right + ui_vbox(obj, .fill = TRUE) { + ui_grid(obj, .margin = 10, .columnspacing = 10, .rowspacing = 10, .fill = TRUE) { + ui_llabel(obj, .label = "Output", .style = UI_LABEL_STYLE_TITLE, .hexpand = TRUE, .hfill = TRUE); + ui_newline(obj); + ui_textarea(obj, .value = window->output, .fill = TRUE); + ui_newline(obj); + ui_button(obj, .label = "Clear", .onclick = testwindow_clear_output); + } + } + } + + ui_show(obj); + + test_window = obj; +} + +static int append_log(void *data) { + char *msg = data; + TestWindow *window = test_window->window; + UiText *out = window->output; + int length = out->length(out); + out->insert(out, length, msg); + free(msg); + return 0; +} + +void* testwindow_read_thread(void *data) { + char buf[4096]; + ssize_t r; + while((r = read(output_fd[0], buf, 4096)) > 0) { + char *str = malloc(r+1); + memcpy(str, buf, r); + str[r] = 0; + ui_call_mainthread(append_log, str); + } + + return NULL; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/main.h Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,59 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CLIENT_MAIN_H +#define CLIENT_MAIN_H + +#include <ui/ui.h> + +#include "message.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct TestWindow { + UiText *input; + UiText *output; +} TestWindow; + +void application_onstartup(UiEvent *event, void *userdata); +void application_onopen(UiEvent *event, void *userdata); +void application_onexit(UiEvent *event, void *userdata); + +void testwindow_create(void); + +void* testwindow_read_thread(void *data); + + +#ifdef __cplusplus +} +#endif + +#endif /* CLIENT_MAIN_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/message.c Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,174 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> + +#include "message.h" + +MessageHandler* simple_msg_handler(int in, int out, msg_received_callback callback) { + SimpleMessageHandler *handler = malloc(sizeof(SimpleMessageHandler)); + handler->handler.start = simple_msg_handler_start; + handler->handler.stop = simple_msg_handler_stop; + handler->handler.send = simple_msg_handler_send; + handler->handler.callback = callback; + handler->in = in; + handler->out = out; + handler->outbuf = cxBufferCreate(NULL, 4096, NULL, CX_BUFFER_FREE_CONTENTS | CX_BUFFER_AUTO_EXTEND); + handler->stop = 0; + pthread_mutex_init(&handler->queue_lock, NULL); + pthread_mutex_init(&handler->avlbl_lock, NULL); + pthread_cond_init(&handler->available, NULL); + return (MessageHandler*)handler; +} + +int simple_msg_handler_start(MessageHandler *handler) { + SimpleMessageHandler *sh = (SimpleMessageHandler*)handler; + if(pthread_create(&sh->in_thread, NULL, simple_msg_handler_in_thread, sh)) { + return 1; + } + if(pthread_create(&sh->out_thread, NULL, simple_msg_handler_out_thread, sh)) { + return 1; + } + return 0; +} + +int simple_msg_handler_stop(MessageHandler *handler) { + SimpleMessageHandler *sh = (SimpleMessageHandler*)handler; + pthread_mutex_lock(&sh->queue_lock); + sh->stop = 0; + pthread_cond_signal(&sh->available); + pthread_mutex_unlock(&sh->queue_lock); + close(sh->in); + sh->in = -1; + + pthread_join(sh->in_thread, NULL); + pthread_join(sh->out_thread, NULL); + + return 0; +} + +int simple_msg_handler_send(MessageHandler *handler, cxstring msg) { + SimpleMessageHandler *sh = (SimpleMessageHandler*)handler; + pthread_mutex_lock(&sh->queue_lock); + cxBufferWrite(msg.ptr, 1, msg.length, sh->outbuf); + pthread_cond_signal(&sh->available); + pthread_mutex_unlock(&sh->queue_lock); + return 0; +} + +#define HEADERBUF_SIZE 64 + +void* simple_msg_handler_in_thread(void *data) { + SimpleMessageHandler *handler = data; + + char *msg = NULL; + size_t msg_size = 0; + size_t msg_pos = 0; // currently received message length + + char headerbuf[HEADERBUF_SIZE]; + size_t headerpos = 0; + + char buf[2048]; + ssize_t r; + while((r = read(handler->in, buf, 2024)) > 0) { + char *buffer = buf; + size_t available = r; + + while(available > 0) { + if(msg) { + // read message + size_t need = msg_size - msg_pos; + size_t cplen = r > need ? need : available; + memcpy(msg+msg_pos, buffer, cplen); + buffer += cplen; + available -= cplen; + msg_pos += cplen; + if(msg_pos == msg_size) { + // message complete + //fprintf(stderr, "send: %.*s\n", (int)msg_size, msg); + if(handler->handler.callback) { + handler->handler.callback(cx_mutstrn(msg, msg_size)); + } + msg = NULL; + msg_size = 0; + msg_pos = 0; + } + } else { + size_t header_max = HEADERBUF_SIZE - headerpos - 1; + if(header_max > available) { + header_max = available; + } + // search for line break + int i; + int header_complete = 0; + for(i=0;i<header_max;i++) { + if(buffer[i] == '\n') { + header_complete = 1; + break; + } + } + i++; + memcpy(headerbuf+headerpos, buffer, i); + headerpos += i; + buffer += i; + available -= i; + + if(header_complete) { + headerbuf[headerpos-1] = 0; // terminate buffer + char *end; + long length = strtol(headerbuf, &end, 10); + if(*end == '\0') { + //fprintf(stderr, "header: %d\n", (int)length); + msg = malloc(length); + msg_size = length; + headerpos = 0; + } else { + fprintf(stderr, "Error: invalid message {%s}\n", headerbuf); + } + } else if(headerpos+1 >= HEADERBUF_SIZE) { + fprintf(stderr, "Error: message header too big\n"); + exit(-1); + } + } + } + + + } + perror("error"); + fprintf(stderr, "stop simple_msg_handler_in_thread\n"); + + return NULL; +} + +void* simple_msg_handler_out_thread(void *data) { + SimpleMessageHandler *handler = data; + + return NULL; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/message.h Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,81 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CLIENT_MESSAGE_H +#define CLIENT_MESSAGE_H + +#include <cx/string.h> +#include <cx/json.h> +#include <cx/buffer.h> + +#include <pthread.h> + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct MessageHandler MessageHandler; + +typedef void(*msg_received_callback)(cxmutstr msg); + +struct MessageHandler { + int (*start)(MessageHandler *handler); + int (*stop)(MessageHandler *handler); + int (*send)(MessageHandler *handler, cxstring msg); + + msg_received_callback callback; +}; + +typedef struct SimpleMessageHandler { + MessageHandler handler; + int in; + int out; + pthread_t in_thread; + pthread_t out_thread; + pthread_mutex_t queue_lock; + pthread_mutex_t avlbl_lock; + pthread_cond_t available; + CxBuffer *outbuf; + int stop; +} SimpleMessageHandler; + +MessageHandler* simple_msg_handler(int in, int out, msg_received_callback callback); +int simple_msg_handler_start(MessageHandler *handler); +int simple_msg_handler_stop(MessageHandler *handler); +int simple_msg_handler_send(MessageHandler *handler, cxstring msg); + +void* simple_msg_handler_in_thread(void *data); +void* simple_msg_handler_out_thread(void *data); + + +#ifdef __cplusplus +} +#endif + +#endif /* CLIENT_MESSAGE_H */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/uiclient.c Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,254 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "uiclient.h" +#include "args.h" + +#include <cx/hash_map.h> + +#include "../ui/common/args.h" + +static MessageHandler *io; + +static CxMap *msg_types; +static CxMap *objects; + +void client_init(MessageHandler *handler) { + io = handler; + + msg_types = cxHashMapCreateSimple(CX_STORE_POINTERS); + cxMapPut(msg_types, "simple_window", msg_simple_window); + cxMapPut(msg_types, "show", msg_show); + + cxMapPut(msg_types, "vbox", msg_vbox); + cxMapPut(msg_types, "hbox", msg_vbox); + cxMapPut(msg_types, "grid", msg_vbox); + cxMapPut(msg_types, "end", msg_end); + cxMapPut(msg_types, "button", msg_button); + + objects = cxHashMapCreateSimple(CX_STORE_POINTERS); +} + +static cxmutstr jsonobj_getstring(const CxJsonValue *obj, const char *name) { + CxJsonValue *value = cxJsonObjGet(obj, name); + if(value->type == CX_JSON_STRING) { + return value->value.string; + } else { + return (cxmutstr){ NULL, 0 }; + } +} + +/* + * UI thread callback + */ +static int msg_received(void *data) { + CxJsonValue *value = data; + if(client_handle_json(NULL, value)) { + fprintf(stderr, "Error: invalid json message\n"); + } + cxJsonValueFree(value); + return 0; +} + +/* + * This message callback is executed in the message handler input thread + */ +void client_msg_received(cxmutstr msg) { + // parse message + CxJson json; + cxJsonInit(&json, NULL); + + cxJsonFilln(&json, msg.ptr, msg.length); + CxJsonValue *value; + if(cxJsonNext(&json, &value) == CX_JSON_NO_ERROR && value) { + // handle json message in the UI thread + ui_call_mainthread(msg_received, value); + } else { + fprintf(stderr, "Error: invalid json message\n"); + } + cxJsonDestroy(&json); +} + +int client_handle_json(UiObject *obj, const CxJsonValue *value) { + if(value->type != CX_JSON_OBJECT) { + return 1; + } + + CxJsonValue *type = cxJsonObjGet(value, "type"); + if(!type || type->type != CX_JSON_STRING) { + return 1; + } + + json_msg_handler handler = cxMapGet(msg_types, type->value.string); + if(!handler) { + return 1; + } + + return handler(obj, value); +} + +int client_handle_children(UiObject *parent, const CxJsonValue *value) { + CxJsonValue *children = cxJsonObjGet(value, "children"); + if(children && children->type == CX_JSON_ARRAY) { + for(int i=0;i<children->value.array.array_size;i++) { + CxJsonValue *child = children->value.array.array[i]; + if(client_handle_json(parent, child)) { + fprintf(stderr, "Error: invalid child\n"); + return 1; + } + } + } + return 0; +} + +void client_add_obj_mapping(UiObject *obj, cxmutstr id) { + cxMapPut(objects, id, obj); + + CxAllocator *a = ui_allocator(obj->ctx); + + WindowData *wdata = cxMalloc(a, sizeof(WindowData)); + wdata->widgets = cxHashMapCreate(a, CX_STORE_POINTERS, 128); + obj->window = wdata; +} + +UiObject* client_get_mapped_obj(cxmutstr id) { + return cxMapGet(objects, id); +} + +void client_reg_widget(UiObject *obj, cxmutstr id, UIWIDGET w) { + WindowData *wdata = obj->window; + if(!wdata) { + fprintf(stderr, "Error: missing obj window data\n"); + return; + } + cxMapPut(wdata->widgets, id, w); +} + +UIWIDGET client_get_widget(UiObject *obj, cxmutstr id) { + WindowData *wdata = obj->window; + if(!wdata) { + fprintf(stderr, "Error: missing obj window data\n"); + return NULL; + } + return cxMapGet(wdata->widgets, id); +} + +static UiObject* get_msg_obj(UiObject *obj, const CxJsonValue *value) { + if(obj) { + return obj; + } + CxJsonValue *obj_id = cxJsonObjGet(value, "obj"); + if(!obj_id || obj_id->type != CX_JSON_STRING) { + return NULL; + } + return client_get_mapped_obj(obj_id->value.string); +} + +int msg_simple_window(UiObject *parent, const CxJsonValue *value) { + cxmutstr id = jsonobj_getstring(value, "id"); + cxmutstr title = jsonobj_getstring(value, "title"); + + if(!id.ptr) { + return 1; + } + + UiObject *obj = ui_simple_window(title.ptr, NULL); + client_add_obj_mapping(obj, id); + + return client_handle_children(obj, value); +} + +int msg_show(UiObject *parent, const CxJsonValue *value) { + UiObject *obj = client_get_mapped_obj(jsonobj_getstring(value, "obj")); + if(!obj) { + return 1; + } + ui_show(obj); + return 0; +} + +typedef UIWIDGET(*ctcreate_func)(UiObject *obj, UiContainerArgs *args); + +static int msg_container(UiObject *parent, const CxJsonValue *value, ctcreate_func create) { + CxJsonValue *args_value = cxJsonObjGet(value, "args"); + cxmutstr id = jsonobj_getstring(value, "id"); + if(!id.ptr) { + return 1; + } + UiObject *obj = get_msg_obj(parent, value); + if(!obj) { + return 1; + } + + UiContainerArgs *args = json2container_args(args_value); + UIWIDGET w = create(obj, args); + ui_container_args_free(args); + client_reg_widget(obj, id, w); + + return 0; +} + +int msg_vbox(UiObject *parent, const CxJsonValue *value) { + return msg_container(parent, value, ui_vbox_create); +} + +int msg_hbox(UiObject *parent, const CxJsonValue *value) { + return msg_container(parent, value, ui_hbox_create); +} + +int msg_grid(UiObject *parent, const CxJsonValue *value) { + return msg_container(parent, value, ui_grid_create); +} + +int msg_end(UiObject *parent, const CxJsonValue *value) { + UiObject *obj = get_msg_obj(parent, value); + if(!obj) { + return 1; + } + ui_end_new(obj); + return 0; +} + +int msg_button(UiObject *parent, const CxJsonValue *value) { + CxJsonValue *args_value = cxJsonObjGet(value, "args"); + cxmutstr id = jsonobj_getstring(value, "id"); + if(!id.ptr) { + return 1; + } + UiObject *obj = get_msg_obj(parent, value); + if(!obj) { + return 1; + } + + UiButtonArgs *args = json2button_args(args_value); + UIWIDGET w = ui_button_create(obj, args); + ui_button_args_free(args); + client_reg_widget(obj, id, w); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/client/uiclient.h Sun Nov 30 18:15:46 2025 +0100 @@ -0,0 +1,82 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2025 Olaf Wintermann. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef CLIENT_H +#define CLIENT_H + +#include <ui/ui.h> +#include <cx/string.h> +#include <cx/json.h> +#include <cx/map.h> + +#include "message.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct WindowData { + CxMap *widgets; +} WindowData; + +typedef int (*json_msg_handler)(UiObject *parent, const CxJsonValue *value); + +void client_init(MessageHandler *handler); + +void client_msg_received(cxmutstr msg); + +int client_handle_json(UiObject *obj, const CxJsonValue *value); + +int client_handle_children(UiObject *parent, const CxJsonValue *value); + +void client_add_obj_mapping(UiObject *obj, cxmutstr id); +UiObject* client_get_mapped_obj(cxmutstr id); +void client_reg_widget(UiObject *obj, cxmutstr id, UIWIDGET w); +UIWIDGET client_get_widget(UiObject *obj, cxmutstr id); + + +int msg_simple_window(UiObject *parent, const CxJsonValue *value); +int msg_show(UiObject *parent, const CxJsonValue *value); + +int msg_vbox(UiObject *parent, const CxJsonValue *value); +int msg_hbox(UiObject *parent, const CxJsonValue *value); +int msg_grid(UiObject *parent, const CxJsonValue *value); + +int msg_end(UiObject *parent, const CxJsonValue *value); + +int msg_button(UiObject *parent, const CxJsonValue *value); + + + + +#ifdef __cplusplus +} +#endif + +#endif /* CLIENT_H */ +
--- a/configure Sun Nov 30 14:40:47 2025 +0100 +++ b/configure Sun Nov 30 18:15:46 2025 +0100 @@ -114,6 +114,9 @@ Options: --toolkit=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|cocoa|motif) +Optional Features: + --enable-client + __EOF__ } @@ -180,6 +183,8 @@ "--release") BUILD_TYPE="release" ;; "--toolkit="*) OPT_TOOLKIT=${ARG#--toolkit=} ;; "--toolkit") echo "option '$ARG' needs a value:"; echo " $ARG=(libadwaita|gtk4|gtk3|gtk2|gtk2legacy|qt5|qt4|cocoa|motif)"; abort_configure ;; + "--enable-client") FEATURE_CLIENT=on ;; + "--disable-client") unset FEATURE_CLIENT ;; "-"*) echo "unknown option: $ARG"; abort_configure ;; esac done @@ -1103,6 +1108,40 @@ echo "TK_LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk" fi +echo >> "$TEMP_DIR/flags.mk" +echo "configuring target: client" +echo "# flags for target client" >> "$TEMP_DIR/flags.mk" +TEMP_CFLAGS= +TEMP_CXXFLAGS= +TEMP_LDFLAGS= + + +# Features +if [ -n "$FEATURE_CLIENT" ]; then + if [ -n "$DISABLE_FEATURE_CLIENT" ]; then + unset FEATURE_CLIENT + fi +fi +if [ -n "$FEATURE_CLIENT" ]; then + : +else + : + cat >> "$TEMP_DIR/make.mk" << __EOF__ +DISABLE_CLIENT=1 +__EOF__ +fi + + +if [ -n "${TEMP_CFLAGS}" ] && [ -n "$lang_c" ]; then + echo "CLIENT_CFLAGS += $TEMP_CFLAGS" >> "$TEMP_DIR/flags.mk" +fi +if [ -n "${TEMP_CXXFLAGS}" ] && [ -n "$lang_cpp" ]; then + echo "CLIENT_CXXFLAGS += $TEMP_CXXFLAGS" >> "$TEMP_DIR/flags.mk" +fi +if [ -n "${TEMP_LDFLAGS}" ]; then + echo "CLIENT_LDFLAGS += $TEMP_LDFLAGS" >> "$TEMP_DIR/flags.mk" +fi + # final result if [ $ERROR -ne 0 ]; then @@ -1178,6 +1217,13 @@ echo "Options:" cat "$TEMP_DIR/options" echo +echo "Features:" +if [ -n "$FEATURE_CLIENT" ]; then +echo " client: on" +else +echo " client: off" +fi +echo # generate the config.mk file pwd=`pwd`
--- a/make/Makefile.mk Sun Nov 30 14:40:47 2025 +0100 +++ b/make/Makefile.mk Sun Nov 30 18:15:46 2025 +0100 @@ -32,10 +32,10 @@ include config.mk BUILD_DIRS = build/bin build/lib -BUILD_DIRS += build/application build/ucx +BUILD_DIRS += build/application build/client build/ucx BUILD_DIRS += build/ui/common build/ui/$(TOOLKIT) -all: $(BUILD_DIRS) ucx ui application +all: $(BUILD_DIRS) ucx ui application client make/$(PACKAGE_SCRIPT) $(BUILD_DIRS): @@ -50,5 +50,8 @@ application: ui FORCE cd application; $(MAKE) +client: ui FORCE + cd client; $(MAKE) + FORCE:
--- a/make/project.xml Sun Nov 30 14:40:47 2025 +0100 +++ b/make/project.xml Sun Nov 30 18:15:46 2025 +0100 @@ -175,5 +175,13 @@ <default value="motif" /> </option> </target> + + <target name="client"> + <feature name="client" default="false"> + <disabled> + <make>DISABLE_CLIENT=1</make> + </disabled> + </feature> + </target> </project>
--- a/make/toolchain.sh Sun Nov 30 14:40:47 2025 +0100 +++ b/make/toolchain.sh Sun Nov 30 18:15:46 2025 +0100 @@ -3,12 +3,19 @@ # toolchain detection # +TAIL="tail" if isplatform "bsd" && notisplatform "openbsd"; then C_COMPILERS="clang gcc cc" CPP_COMPILERS="clang++ g++ CC" +elif isplatform "solaris"; then + C_COMPILERS="cc suncc gcc clang" + CPP_COMPILERS="CC sunCC g++ clang++" + if [ -f /usr/xpg4/bin/tail ]; then + TAIL=/usr/xpg4/bin/tail + fi else - C_COMPILERS="gcc clang suncc cc" - CPP_COMPILERS="g++ clang++ sunCC CC" + C_COMPILERS="gcc clang cc" + CPP_COMPILERS="g++ clang++ c++" fi unset TOOLCHAIN unset TOOLCHAIN_NAME @@ -17,14 +24,14 @@ check_c_compiler() { - command -v $1 2>&1 >/dev/null - if [ $? -ne 0 ]; then + command -v "$1" >/dev/null 2>&1 + if [ $? -ne 0 ] ; then return 1 fi cat > "$TEMP_DIR/test.c" << __EOF__ /* test file */ #include <stdio.h> -int main(int argc, char **argv) { +int main(void) { #if defined(_MSC_VER) printf("toolchain:msc\n"); #elif defined(__clang__) @@ -38,7 +45,7 @@ #endif printf("wsize:%d\n", (int)sizeof(void*)*8); #ifdef __STDC_VERSION__ - printf("stdcversion:%d\n", __STDC_VERSION__); + printf("stdcversion:%ld\n", (long int)__STDC_VERSION__); #endif return 0; } @@ -49,14 +56,14 @@ check_cpp_compiler() { - command -v $1 2>&1 >/dev/null - if [ $? -ne 0 ]; then + command -v "$1" >/dev/null 2>&1 + if [ $? -ne 0 ] ; then return 1 fi cat > "$TEMP_DIR/test.cpp" << __EOF__ /* test file */ #include <iostream> -int main(int argc, char **argv) { +int main(void) { #if defined(_MSC_VER) std::cout << "toolchain:msc" << std::endl; #elif defined(__clang__) @@ -76,62 +83,12 @@ $1 -o "$TEMP_DIR/checkcc" $CXXFLAGS $LDFLAGS "$TEMP_DIR/test.cpp" 2> /dev/null } -create_libtest_source() -{ - # $1: filename - # $2: optional include - cat > "$TEMP_DIR/$1" << __EOF__ -/* libtest file */ -int main(int argc, char **argv) { - return 0; -} -__EOF__ - if [ -n "$2" ]; then - echo "#include <$2>" >> "$TEMP_DIR/$1" - fi -} - -check_c_lib() -{ - # $1: libname - # $2: optional include - if [ -z "$TOOLCHAIN_CC" ]; then - return 1 - fi - create_libtest_source "test.c" "$2" - rm -f "$TEMP_DIR/checklib" - $TOOLCHAIN_CC -o "$TEMP_DIR/checklib" $CFLAGS $LDFLAGS "-l$1" "$TEMP_DIR/test.c" 2> /dev/null -} - -check_cpp_lib() -{ - # $1: libname - # $2: optional include - if [ -z "$TOOLCHAIN_CXX" ]; then - return 1 - fi - create_libtest_source "test.cpp" "$2" - rm -f "$TEMP_DIR/checklib" - $TOOLCHAIN_CXX -o "$TEMP_DIR/checklib" $CXXFLAGS $LDFLAGS "-l$1" "$TEMP_DIR/test.cpp" 2> /dev/null -} - -check_lib() -{ - # $1: libname - # $2: optional include - if [ -n "$TOOLCHAIN_CC" ]; then - check_c_lib "$1" "$2" - elif [ -n "$TOOLCHAIN_CXX" ]; then - check_cpp_lib "$1" "$2" - fi -} - parse_toolchain_properties() { info_file="$1" - TOOLCHAIN=`grep '^toolchain:' "$info_file" | tail -c +11` + TOOLCHAIN=`grep '^toolchain:' "$info_file" | $TAIL -c +11` TOOLCHAIN_NAME=`echo "$TOOLCHAIN" | cut -f1 -d' ' -` - TOOLCHAIN_WSIZE=`grep '^wsize:' "$info_file" | tail -c +7` + TOOLCHAIN_WSIZE=`grep '^wsize:' "$info_file" | $TAIL -c +7` } detect_c_compiler() @@ -145,7 +102,7 @@ TOOLCHAIN_CC=$CC "$TEMP_DIR/checkcc" > "$TEMP_DIR/checkcc_out" parse_toolchain_properties "$TEMP_DIR/checkcc_out" - TOOLCHAIN_CSTD=`grep '^stdcversion:' "$TEMP_DIR/checkcc_out" | tail -c +13` + TOOLCHAIN_CSTD=`grep '^stdcversion:' "$TEMP_DIR/checkcc_out" | $TAIL -c +13` echo "$CC" return 0 else @@ -159,7 +116,7 @@ TOOLCHAIN_CC=$COMP "$TEMP_DIR/checkcc" > "$TEMP_DIR/checkcc_out" parse_toolchain_properties "$TEMP_DIR/checkcc_out" - TOOLCHAIN_CSTD=`grep '^stdcversion:' "$TEMP_DIR/checkcc_out" | tail -c +13` + TOOLCHAIN_CSTD=`grep '^stdcversion:' "$TEMP_DIR/checkcc_out" | $TAIL -c +13` echo "$COMP" return 0 fi
--- a/ui/common/args.h Sun Nov 30 14:40:47 2025 +0100 +++ b/ui/common/args.h Sun Nov 30 18:15:46 2025 +0100 @@ -137,7 +137,7 @@ UIEXPORT void ui_container_args_set_margin_left(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_margin_right(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_margin_top(UiContainerArgs *args, int value); -UIEXPORT void ui_container_args_set_margin_right(UiContainerArgs *args, int value); +UIEXPORT void ui_container_args_set_margin_bottom(UiContainerArgs *args, int value); UIEXPORT void ui_container_args_set_colspan(UiContainerArgs *args, int colspan); UIEXPORT void ui_container_args_set_rowspan(UiContainerArgs *args, int rowspan); UIEXPORT void ui_container_args_set_def_hexpand(UiContainerArgs *args, UiBool value);
--- a/ui/gtk/toolkit.c Sun Nov 30 14:40:47 2025 +0100 +++ b/ui/gtk/toolkit.c Sun Nov 30 18:15:46 2025 +0100 @@ -164,7 +164,7 @@ #ifndef UI_GTK2 void ui_app_quit() { - g_application_quit(G_APPLICATION(app)); + g_application_quit(G_APPLICATION(app)); // TODO: fix, does not work } GtkApplication* ui_get_application() {