UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2025 Olaf Wintermann. All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "uiclient.h" 30 #include "args.h" 31 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <cx/hash_map.h> 35 36 #include "../ui/common/args.h" 37 38 #ifndef _WIN32 39 40 static UiMessageHandler *io; 41 42 static CxMap *msg_types; 43 static CxMap *objects; 44 45 void client_init(UiMessageHandler *handler) { 46 io = handler; 47 48 msg_types = cxHashMapCreateSimple(CX_STORE_POINTERS); 49 cxMapPut(msg_types, "window", msg_window); 50 cxMapPut(msg_types, "sidebar_window", msg_window); 51 cxMapPut(msg_types, "splitview_window", msg_window); 52 cxMapPut(msg_types, "simple_window", msg_window); 53 cxMapPut(msg_types, "show", msg_show); 54 55 cxMapPut(msg_types, "vbox", msg_vbox); 56 cxMapPut(msg_types, "hbox", msg_vbox); 57 cxMapPut(msg_types, "grid", msg_vbox); 58 cxMapPut(msg_types, "end", msg_end); 59 cxMapPut(msg_types, "button", msg_button); 60 cxMapPut(msg_types, "toggle", msg_togglebutton); 61 62 objects = cxHashMapCreateSimple(CX_STORE_POINTERS); 63 } 64 65 static cxmutstr jsonobj_getstring(const CxJsonValue *obj, const char *name) { 66 CxJsonValue *value = cxJsonObjGet(obj, name); 67 if(value->type == CX_JSON_STRING) { 68 return value->value.string; 69 } else { 70 return (cxmutstr){ NULL, 0 }; 71 } 72 } 73 74 static UiBool jsonobj_getbool(const CxJsonValue *obj, const char *name, int *error) { 75 CxJsonValue *value = cxJsonObjGet(obj, name); 76 if(value->type == CX_JSON_LITERAL) { 77 if(*error) { 78 *error = 0; 79 } 80 return value->value.literal == CX_JSON_TRUE ? 1 : 0; 81 } else { 82 if(error) { 83 *error = 1; 84 } 85 return FALSE; 86 } 87 } 88 89 /* 90 * UI thread callback 91 */ 92 static int msg_received(void *data) { 93 CxJsonValue *value = data; 94 if(client_handle_json(NULL, value)) { 95 fprintf(stderr, "Error: invalid json message\n"); 96 } 97 cxJsonValueFree(value); 98 return 0; 99 } 100 101 /* 102 * This message callback is executed in the message handler input thread 103 */ 104 void client_msg_received(cxstring msg) { 105 // parse message 106 CxJson json; 107 cxJsonInit(&json, NULL); 108 109 cxJsonFilln(&json, msg.ptr, msg.length); 110 CxJsonValue *value; 111 if(cxJsonNext(&json, &value) == CX_JSON_NO_ERROR && value) { 112 // handle json message in the UI thread 113 ui_call_mainthread(msg_received, value); 114 } else { 115 fprintf(stderr, "Error: invalid json message\n"); 116 } 117 cxJsonDestroy(&json); 118 } 119 120 int client_handle_json(UiObject *obj, const CxJsonValue *value) { 121 if(value->type != CX_JSON_OBJECT) { 122 return 1; 123 } 124 125 CxJsonValue *type = cxJsonObjGet(value, "type"); 126 if(!type || type->type != CX_JSON_STRING) { 127 return 1; 128 } 129 130 json_msg_handler handler = cxMapGet(msg_types, type->value.string); 131 if(!handler) { 132 return 1; 133 } 134 135 return handler(obj, value, type->value.string); 136 } 137 138 int client_handle_children(UiObject *parent, const CxJsonValue *value) { 139 CxJsonValue *children = cxJsonObjGet(value, "children"); 140 if(children && children->type == CX_JSON_ARRAY) { 141 for(int i=0;i<children->value.array.array_size;i++) { 142 CxJsonValue *child = children->value.array.array[i]; 143 if(client_handle_json(parent, child)) { 144 fprintf(stderr, "Error: invalid child\n"); 145 return 1; 146 } 147 } 148 } 149 return 0; 150 } 151 152 void client_add_obj_mapping(UiObject *obj, cxmutstr id) { 153 cxMapPut(objects, id, obj); 154 155 CxAllocator *a = ui_allocator(obj->ctx); 156 157 WindowData *wdata = cxMalloc(a, sizeof(WindowData)); 158 wdata->widgets = cxHashMapCreate(a, CX_STORE_POINTERS, 128); 159 obj->window = wdata; 160 } 161 162 UiObject* client_get_mapped_obj(cxmutstr id) { 163 return cxMapGet(objects, id); 164 } 165 166 void client_reg_widget(UiObject *obj, cxmutstr id, UIWIDGET w) { 167 WindowData *wdata = obj->window; 168 if(!wdata) { 169 fprintf(stderr, "Error: missing obj window data\n"); 170 return; 171 } 172 cxMapPut(wdata->widgets, id, w); 173 } 174 175 UIWIDGET client_get_widget(UiObject *obj, cxmutstr id) { 176 WindowData *wdata = obj->window; 177 if(!wdata) { 178 fprintf(stderr, "Error: missing obj window data\n"); 179 return NULL; 180 } 181 return cxMapGet(wdata->widgets, id); 182 } 183 184 static UiObject* get_msg_obj(UiObject *obj, const CxJsonValue *value) { 185 if(obj) { 186 return obj; 187 } 188 CxJsonValue *obj_id = cxJsonObjGet(value, "obj"); 189 if(!obj_id || obj_id->type != CX_JSON_STRING) { 190 return NULL; 191 } 192 return client_get_mapped_obj(obj_id->value.string); 193 } 194 195 int msg_window(UiObject *parent, const CxJsonValue *value, cxmutstr type) { 196 cxmutstr obj_id = jsonobj_getstring(value, "obj"); 197 cxmutstr id = jsonobj_getstring(value, "id"); 198 cxmutstr title = jsonobj_getstring(value, "title"); 199 200 if(!obj_id.ptr) { 201 return 1; 202 } 203 if(!id.ptr) { 204 return 1; 205 } 206 207 UiObject *obj; 208 if(!cx_strcmp(type, "window")) { 209 obj = ui_window(title.ptr, NULL); 210 } else if(!cx_strcmp(type, "sidebar_window")) { 211 obj = ui_sidebar_window(title.ptr, NULL); 212 } else if(!cx_strcmp(type, "splitview_window")) { 213 int err; 214 bool sidebar = jsonobj_getbool(value, "sidebar", &err); 215 if(err) { 216 return 1; 217 } 218 obj = ui_splitview_window(title.ptr, sidebar); 219 } else if(!cx_strcmp(type, "simple_window")) { 220 obj = ui_simple_window(title.ptr, NULL); 221 } 222 223 client_add_obj_mapping(obj, obj_id); 224 225 if(obj->widget) { 226 client_reg_widget(obj, id, obj->widget); 227 } 228 229 return client_handle_children(obj, value); 230 } 231 232 int msg_show(UiObject *parent, const CxJsonValue *value, cxmutstr type) { 233 UiObject *obj = client_get_mapped_obj(jsonobj_getstring(value, "obj")); 234 if(!obj) { 235 return 1; 236 } 237 ui_show(obj); 238 return 0; 239 } 240 241 typedef UIWIDGET(*ctcreate_func)(UiObject *obj, UiContainerArgs *args); 242 243 static int msg_container(UiObject *parent, const CxJsonValue *value, ctcreate_func create) { 244 CxJsonValue *args_value = cxJsonObjGet(value, "args"); 245 cxmutstr id = jsonobj_getstring(value, "id"); 246 if(!id.ptr) { 247 return 1; 248 } 249 UiObject *obj = get_msg_obj(parent, value); 250 if(!obj) { 251 return 1; 252 } 253 254 UiContainerArgs *args = json2container_args(args_value); 255 UIWIDGET w = create(obj, args); 256 ui_container_args_free(args); 257 client_reg_widget(obj, id, w); 258 259 return 0; 260 } 261 262 int msg_vbox(UiObject *parent, const CxJsonValue *value, cxmutstr type) { 263 return msg_container(parent, value, ui_vbox_create); 264 } 265 266 int msg_hbox(UiObject *parent, const CxJsonValue *value, cxmutstr type) { 267 return msg_container(parent, value, ui_hbox_create); 268 } 269 270 int msg_grid(UiObject *parent, const CxJsonValue *value, cxmutstr type) { 271 return msg_container(parent, value, ui_grid_create); 272 } 273 274 int msg_end(UiObject *parent, const CxJsonValue *value, cxmutstr type) { 275 UiObject *obj = get_msg_obj(parent, value); 276 if(!obj) { 277 return 1; 278 } 279 ui_end_new(obj); 280 return 0; 281 } 282 283 int msg_button(UiObject *parent, const CxJsonValue *value, cxmutstr type) { 284 CxJsonValue *args_value = cxJsonObjGet(value, "args"); 285 cxmutstr id = jsonobj_getstring(value, "id"); 286 if(!id.ptr) { 287 return 1; 288 } 289 UiObject *obj = get_msg_obj(parent, value); 290 if(!obj) { 291 return 1; 292 } 293 294 UiButtonArgs *args = json2button_args(args_value); 295 UIWIDGET w = ui_button_create(obj, args); 296 ui_button_args_free(args); 297 client_reg_widget(obj, id, w); 298 299 return 0; 300 } 301 302 int msg_togglebutton(UiObject *parent, const CxJsonValue *value, cxmutstr type) { 303 CxJsonValue *args_value = cxJsonObjGet(value, "args"); 304 cxmutstr id = jsonobj_getstring(value, "id"); 305 if(!id.ptr) { 306 return 1; 307 } 308 UiObject *obj = get_msg_obj(parent, value); 309 if(!obj) { 310 return 1; 311 } 312 313 CxJsonValue *button_type = cxJsonObjGet(value, "button_type"); 314 if(!button_type || button_type->type != CX_JSON_INTEGER) { 315 return 1; 316 } 317 318 CxJsonValue *val = cxJsonObjGet(value, "value"); 319 UiInteger *i = NULL; 320 if(val && val->type == CX_JSON_STRING) { 321 i = ui_get_int_var(obj->ctx, val->value.string.ptr); 322 if(!i) { 323 i = ui_int_new(obj->ctx, val->value.string.ptr); 324 } 325 } 326 327 UiToggleArgs *args = json2toggle_args(args_value); 328 UIWIDGET w; 329 switch(button_type->value.integer) { 330 default: { 331 w = ui_togglebutton_create(obj, args); 332 break; 333 } 334 case 1: { 335 w = ui_checkbox_create(obj, args); 336 break; 337 } 338 case 2: { 339 w = ui_switch_create(obj, args); 340 break; 341 } 342 case 3: { 343 w = ui_radiobutton_create(obj, args); 344 break; 345 } 346 } 347 ui_toggle_args_free(args); 348 client_reg_widget(obj, id, w); 349 350 return 0; 351 } 352 353 #endif 354