implement key management UI

Tue, 29 Oct 2024 17:51:02 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Tue, 29 Oct 2024 17:51:02 +0100
changeset 72
d5307e9ee384
parent 71
9e19ac687b9f
child 73
ede7885491b1

implement key management UI

application/config.c file | annotate | diff | comparison | revisions
application/settings.c file | annotate | diff | comparison | revisions
application/settings.h file | annotate | diff | comparison | revisions
libidav/config.c file | annotate | diff | comparison | revisions
libidav/config.h file | annotate | diff | comparison | revisions
ui/gtk/text.c file | annotate | diff | comparison | revisions
ui/gtk/text.h file | annotate | diff | comparison | revisions
ui/gtk/window.c file | annotate | diff | comparison | revisions
--- a/application/config.c	Tue Oct 29 11:51:25 2024 +0100
+++ b/application/config.c	Tue Oct 29 17:51:02 2024 +0100
@@ -189,6 +189,9 @@
 
 
 void set_config(DavConfig *cfg) {
+    if(davconfig) {
+        dav_config_free(davconfig);
+    }
     davconfig = cfg;
 }
 
@@ -214,7 +217,7 @@
     }
     
     // should only fail if we run out of disk space or something like that
-    // in that case, the config file is only destroyed
+    // in that case, the config file is destroyed
     // could only be prevented, if we write to a temp file first and than
     // rename it
     fwrite(buf->space, buf->size, 1, cout);
--- a/application/settings.c	Tue Oct 29 11:51:25 2024 +0100
+++ b/application/settings.c	Tue Oct 29 17:51:02 2024 +0100
@@ -39,6 +39,9 @@
 #define SETTINGS_STATE_REPO_ENCRYPTION 20
 #define SETTINGS_STATE_CREDENTIALS_SELECTED 30
 #define SETTINGS_STATE_CREDENTIALS_LOCATION_SELECTED 31
+#define SETTINGS_STATE_KEYS_SELECTED 40
+
+#define SETTINGS_STATE_DISABLED 9999
 
 
 static void repolist_activate(UiEvent *event, void *userdata) {
@@ -186,6 +189,9 @@
 static void credentials_location_edit(UiEvent *event, void *userdata) {
     SettingsWindow *settings = event->window;
     char *location = ui_list_get(settings->credentials_locations, settings->credentials_location_selected_index);
+    if(!location) {
+        return;
+    }
     ui_dialog(event->obj,
             .title = "Edit Location",
             .content = "Location URL",
@@ -267,6 +273,178 @@
             .closebutton_label = "Cancel");
 }
 
+
+
+
+#define ADDKEY_DIALOG_STATE_NO_IMPORT 10
+
+static void addkey_onclick(UiEvent *event, void *userdata) {
+    SettingsNewKeyDialog *wdata = event->window;
+    if(event->intval == 4) {
+        ui_set(wdata->message, "");
+        
+        // validate form
+        char *name = ui_get(wdata->name);
+        if(strlen(name) == 0) {
+            ui_set(wdata->message, "A name must be specified!");
+            return;
+        }
+        char *file = ui_get(wdata->file);
+        if(strlen(file) == 0) {
+            ui_set(wdata->message, "A file path must be specified!");
+            return;
+        }
+        UiListSelection sel = ui_list_getselection(wdata->type);
+        if(sel.count == 0) {
+            ui_set(wdata->message, "No type selected!");
+            return;
+        }
+        DavCfgKeyType type = sel.rows[0];
+        
+        // generate key file
+        char *path = NULL;
+        char *cfg_path = NULL;
+        if(file[0] == '/') {
+            path = file;
+        } else {
+            cfg_path = config_file_path(file);
+            path = cfg_path;
+        }
+        FILE *f = fopen(path, "w");
+        if(!f) {
+            cxmutstr msg = cx_asprintf("Error: cannot write file '%s': %s", path, strerror(errno));
+            ui_set(wdata->message, msg.ptr);
+            free(msg.ptr);
+            free(cfg_path);
+            return;
+        }
+        free(cfg_path);
+        
+        unsigned char key[32];
+        dav_rand_bytes(key, 32);
+        
+        size_t len;
+        switch(type) {
+            default:
+            case DAV_KEY_TYPE_AES256: len = 32; break;
+            case DAV_KEY_TYPE_AES128: len = 16; break;
+        }
+        size_t w = fwrite(key, 1, len, f);
+        fclose(f);
+        
+        if(w != len) {
+            cxmutstr msg = cx_asprintf("Could not write key data: %s", strerror(errno));
+            ui_set(wdata->message, msg.ptr);
+            free(msg.ptr);
+            return;
+        }
+        
+        // add key to config
+        DavCfgKey *newkey = dav_key_new(wdata->settings->config);
+        const CxAllocator *a = wdata->settings->config->mp->allocator;
+        newkey->name.value = cx_strdup_a(a, cx_str(name));
+        newkey->file.value = cx_strdup_a(a, cx_str(file));
+        newkey->type = type;
+        
+        dav_config_add_key(wdata->settings->config, newkey);
+        settings_reload_repo_keys(wdata->settings);
+        settings_reload_keys(wdata->settings);
+    }
+    
+    ui_close(event->obj);
+}
+
+static void addkey_name_changed(UiEvent *event, void *userdata) {
+    SettingsNewKeyDialog *wdata = event->window;
+    char *name = ui_get(wdata->name);
+    cxmutstr file = cx_asprintf("keys/%s", name);
+    ui_set(wdata->file, file.ptr);
+    free(file.ptr);
+}
+
+static void keys_add(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    
+    UiObject *dialog = ui_dialog_window(event->obj,
+            .modal = UI_ON,
+            .title = "Add Key",
+            .show_closebutton = UI_OFF,
+            .lbutton1 = "Cancel",
+            .rbutton4 = "Add",
+            .default_button = 4,
+            .onclick = addkey_onclick);
+    SettingsNewKeyDialog *wdata = ui_malloc(dialog->ctx, sizeof(SettingsNewKeyDialog));
+    wdata->settings = settings;
+    wdata->name = ui_string_new(dialog->ctx, NULL);
+    wdata->file = ui_string_new(dialog->ctx, NULL);
+    wdata->import_path = ui_string_new(dialog->ctx, NULL);
+    wdata->message = ui_string_new(dialog->ctx, NULL);
+    wdata->secretstore = ui_int_new(dialog->ctx, NULL);
+    wdata->type = ui_list_new(dialog->ctx, NULL);
+    dialog->window = wdata;
+    
+    ui_list_append(wdata->type, "AES256");
+    ui_list_append(wdata->type, "AES128");
+    
+    ui_grid(dialog, .margin = 16, .columnspacing = 40, .rowspacing = 10) {
+        /*
+        ui_hbox(dialog, .colspan = 2) {
+            ui_button(dialog, .label = "Import Key...");
+        }
+        ui_newline(dialog);
+        ui_llabel(dialog, .value = wdata->import_path);
+        ui_newline(dialog);
+        */
+        
+        ui_llabel(dialog, .label = "Name");
+        ui_textfield(dialog, .value = wdata->name, .onchange = addkey_name_changed, .hexpand = TRUE);
+        ui_newline(dialog);
+        
+        ui_llabel(dialog, .label = "File");
+        ui_textfield(dialog, .value = wdata->file);
+        ui_newline(dialog);
+        
+        ui_llabel(dialog, .label = "Type");
+        ui_combobox(dialog, .list = wdata->type, .groups = UI_GROUPS(ADDKEY_DIALOG_STATE_NO_IMPORT));
+        ui_newline(dialog);
+        
+        ui_llabel(dialog, .value = wdata->message, .colspan = 2);
+    }
+    
+    ui_set_group(dialog->ctx, ADDKEY_DIALOG_STATE_NO_IMPORT);
+    ui_list_setselection(wdata->type, 0);
+    
+    ui_show(dialog);
+}
+
+static void keys_remove(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    DavCfgKey *key = ui_list_get(settings->keys_list, settings->keys_selected_index);
+    if(key) {
+        dav_key_remove_and_free(settings->config, key);
+        ui_list_remove(settings->keys_list, settings->keys_selected_index);
+        ui_list_update(settings->keys_list);
+        settings_reload_repo_keys(settings);
+    }
+}
+
+static void keys_onselect(UiEvent *event, void *userdata) {
+    SettingsWindow *settings = event->window;
+    UiListSelection *sel = event->eventdata;
+    if(sel->count > 0) {
+        settings->keys_selected_index = sel->rows[0];
+        DavCfgKey *key = ui_list_get(settings->keys_list, sel->rows[0]);
+        if(key) {
+            settings_edit_key(settings, key);
+            ui_set_group(event->obj->ctx, SETTINGS_STATE_KEYS_SELECTED);
+        }
+    } else {
+        settings->keys_selected_index = -1;
+        settings_clear_key(settings);
+        ui_unset_group(event->obj->ctx, SETTINGS_STATE_KEYS_SELECTED);
+    }
+}
+
 static void list_str_destructor(void *data, void *ptr) {
     UiContext *ctx = data;
     char *s = ptr;
@@ -290,7 +468,7 @@
     settings_store_repository(settings);
     settings_credentials_save(settings);
     
-    set_config(settings->config); // TODO: free old config
+    set_config(settings->config);
     if(store_config()) {
         ui_dialog(event->obj, .title = "Error", .content = "Cannot store settings", .closebutton_label = "OK");
     }
@@ -461,13 +639,13 @@
         }
         
         ui_tab(obj, "Credentials") {
-            ui_hbox(obj, .margin = 16, .spacing = 10) {
-                ui_vbox(obj, .fill = UI_OFF) {
-                    ui_listview(obj, .list = wdata->credentials_users, .fill = UI_ON, .onselection = credentials_onselect);
+            ui_hbox(obj, .margin = 16, .spacing = 30) {
+                ui_vbox(obj, .fill = UI_OFF, .spacing = 4) {
                     ui_hbox(obj, .fill = UI_OFF, .spacing = 4) {
                         ui_button(obj, .label = "Add", .onclick = credentials_add);
                         ui_button(obj, .label = "Remove", .onclick = credentials_remove, .groups = UI_GROUPS(SETTINGS_STATE_CREDENTIALS_SELECTED));
                     }
+                    ui_listview(obj, .list = wdata->credentials_users, .fill = UI_ON, .onselection = credentials_onselect);
                 }
                 
                 ui_grid(obj, .columnspacing = 30, .rowspacing = 10) {
@@ -506,16 +684,41 @@
         }
         
         ui_tab(obj, "Keys") {
-            ui_grid(obj, .margin = 10) {
-                ui_label(obj, .label = "TODO");
+            ui_hbox(obj, .margin = 16, .spacing = 30) {
+                ui_vbox(obj, .fill = UI_OFF, .spacing = 4) {
+                    ui_hbox(obj, .fill = UI_OFF, .spacing = 4) {
+                        ui_button(obj, .label = "Add", .onclick = keys_add);
+                        ui_button(obj, .label = "Remove", .onclick = keys_remove, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED));
+                    }
+                    ui_listview(obj, .list = wdata->keys_list, .fill = UI_ON, .onselection = keys_onselect, .getvalue = keylist_getvalue);
+                }
+                
+                ui_grid(obj, .columnspacing = 30, .rowspacing = 10) {
+                    ui_llabel(obj, .label = "Identifier");
+                    ui_hbox0(obj) {
+                        ui_textfield(obj, .value = wdata->key_name, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED));
+                    }
+                    ui_newline(obj);
+                    ui_llabel(obj, .label = "Type");
+                    ui_hbox0(obj) {
+                        ui_textfield(obj, .value = wdata->key_type, .groups = UI_GROUPS(SETTINGS_STATE_DISABLED));
+                    }
+                    ui_newline(obj);
+                    ui_llabel(obj, .label = "File");
+                    ui_hbox0(obj) {
+                        ui_textfield(obj, .value = wdata->key_file, .groups = UI_GROUPS(SETTINGS_STATE_KEYS_SELECTED));
+                    }
+                }
             }
         }
         
+        /*
         ui_tab(obj, "Properties") {
             ui_grid(obj, .margin = 10) {
                 ui_label(obj, .label = "TODO");
             }
         }
+        */
     }
         
     ui_hbox(obj, .fill = UI_OFF, .margin = 10) {
@@ -564,11 +767,18 @@
     credentials_locations->collection.destructor_data = settings->obj->ctx;
     credentials_locations->collection.cmpfunc = (cx_compare_func)strcmp;
     
+    settings->keys_list = ui_list_new(obj->ctx, NULL);
+    settings->key_name = ui_string_new(obj->ctx, NULL);
+    settings->key_type = ui_string_new(obj->ctx, NULL);
+    settings->key_file = ui_string_new(obj->ctx, NULL);
+    settings->keys_selected_index = -1;
+    
     // load some list values, that can be reused
     settings_update_repolist(settings);
-    settings_reload_keys(settings);
+    settings_reload_repo_keys(settings);
     settings_reload_credentials(settings);
     settings_reload_repo_credentials(settings);
+    settings_reload_keys(settings);
     
     settings->selected_repo = -1;
 }
@@ -796,7 +1006,7 @@
     return NULL;
 }
 
-void settings_reload_keys(SettingsWindow *settings) {
+void settings_reload_repo_keys(SettingsWindow *settings) {
     DavConfig *config = settings->config;
     DavCfgKey *key = config->keys; 
     ui_list_clear(settings->repo_keys);
@@ -807,9 +1017,20 @@
         }
         key = key->next;
     }
-    if(settings->repo_keys->update) {
-        ui_list_update(settings->repo_keys);
+    ui_list_update(settings->repo_keys);
+}
+
+void settings_reload_keys(SettingsWindow *settings) {
+    DavConfig *config = settings->config;
+    DavCfgKey *key = config->keys; 
+    ui_list_clear(settings->keys_list);
+    while(key) {
+        if(key->name.value.ptr) {
+            ui_list_append(settings->keys_list, key);
+        }
+        key = key->next;
     }
+    ui_list_update(settings->keys_list);
 }
 
 const char* dav_tlsversion2str(int value) {
@@ -1013,3 +1234,42 @@
     
     return 0;
 }
+
+void *keylist_getvalue(void *data, int col) {
+    DavCfgKey *key = data;
+    return key->name.value.ptr;
+}
+
+void settings_edit_key(SettingsWindow *settings, DavCfgKey *key) {
+    if(key->name.value.ptr) {
+        ui_set(settings->key_name, key->name.value.ptr);
+    }
+    const char *key_type;
+    switch(key->type) {
+        default: key_type = ""; break;
+        case DAV_KEY_TYPE_AES256: {
+            key_type = "AES256";
+            break;
+        }
+        case DAV_KEY_TYPE_AES128: {
+            key_type = "AES128";
+            break;
+        }
+        case DAV_KEY_TYPE_UNKNOWN: {
+            key_type = "unknown";
+            break;
+        }
+    }
+    ui_set(settings->key_type, key_type);
+    
+    if(key->file.value.ptr) {
+        ui_set(settings->key_file, key->file.value.ptr);
+    }
+}
+
+void settings_clear_key(SettingsWindow *settings) {
+    ui_set(settings->key_name, "");
+    ui_set(settings->key_type, "");
+    ui_set(settings->key_file, "");
+}
+
--- a/application/settings.h	Tue Oct 29 11:51:25 2024 +0100
+++ b/application/settings.h	Tue Oct 29 17:51:02 2024 +0100
@@ -103,8 +103,37 @@
      */
     UiList *credentials_locations;
     
+    /*
+     * value: char*   key name
+     * 
+     * List of encryption keys (DavKey)
+     * The value is a copy (allocated by the UiContext)
+     */
+    UiList *keys_list;
+    UiString *key_name;
+    UiString *key_file;
+    UiString *key_type;
+    UiInteger *key_secretstore;
+    int keys_selected_index;
+    
     int selected_repo;
 } SettingsWindow;
+
+typedef struct SettingsNewKeyDialog {
+    SettingsWindow *settings;
+    
+    UiString *name;
+    UiString *file;
+    UiString *import_path;
+    UiString *message;
+    /*
+     * value: char*   key type name
+     * 
+     * Static list of DavKey type names
+     */
+    UiList *type;
+    UiInteger *secretstore;
+} SettingsNewKeyDialog;
     
 void settings_window_open();
 
@@ -137,6 +166,7 @@
 
 void* settings_repolist_getvalue(DavCfgRepository *repo, int col);
 
+void settings_reload_repo_keys(SettingsWindow *settings);
 void settings_reload_keys(SettingsWindow *settings);
 
 const char* dav_tlsversion2str(int value);
@@ -160,6 +190,12 @@
 
 int settings_credentials_save(SettingsWindow *settings);
 
+void *keylist_getvalue(void *data, int col);
+
+void settings_edit_key(SettingsWindow *settings, DavCfgKey *key);
+
+void settings_clear_key(SettingsWindow *settings);
+
 
 #ifdef __cplusplus
 }
--- a/libidav/config.c	Tue Oct 29 11:51:25 2024 +0100
+++ b/libidav/config.c	Tue Oct 29 17:51:02 2024 +0100
@@ -611,6 +611,74 @@
 }
 
 
+void dav_config_add_key(DavConfig *config, DavCfgKey *key) {
+    cx_linked_list_add(
+            (void**)&config->keys,
+            NULL,
+            offsetof(DavCfgKey, prev),
+            offsetof(DavCfgKey, next),
+            key);
+    
+    if(key->node) {
+        fprintf(stderr, "Error: dav_config_add_key: node already exists\n");
+        return;
+    }
+    
+    xmlNode *keyNode = xmlNewNode(NULL, BAD_CAST "key");
+    xmlNode *rtext1 = xmlNewDocText(config->doc, BAD_CAST "\n");
+    xmlAddChild(keyNode, rtext1);
+    key->node = keyNode;
+    
+    if(key->name.value.ptr) {
+        key->name.node = addXmlNode(keyNode, "name", key->name.value);
+    }
+    const char *type = dav_config_keytype_str(key->type);
+    if(type) {
+        key->type_node = addXmlNode(keyNode, "type", cx_mutstr((char*)type));
+    }
+    if(key->file.value.ptr) {
+        key->file.node = addXmlNode(keyNode, "file", key->file.value);
+    }
+    
+     // indent closing tag
+    xmlNode *rtext2 = xmlNewDocText(config->doc, BAD_CAST "\t");
+    xmlAddChild(keyNode, rtext2);
+    
+    // add key element to the xml document
+    xmlNode *xml_root = xmlDocGetRootElement(config->doc);
+    
+    xmlNode *text1 = xmlNewDocText(config->doc, BAD_CAST "\n\t");
+    xmlAddChild(xml_root, text1);
+    
+    xmlAddChild(xml_root, keyNode);
+    
+    xmlNode *text2 = xmlNewDocText(config->doc, BAD_CAST "\n");
+    xmlAddChild(xml_root, text2);
+}
+
+DavCfgKey* dav_key_new(DavConfig *config) {
+    DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey));
+    memset(key, 0, sizeof(DavCfgKey));
+    key->type = DAV_KEY_TYPE_AES256;
+    return key;
+}
+
+void dav_key_remove_and_free(DavConfig *config, DavCfgKey *key) {
+    cx_linked_list_remove(
+            (void**)&config->keys,
+            NULL,
+            offsetof(DavCfgKey, prev),
+            offsetof(DavCfgKey, next),
+            key);
+    if(key->node) {
+        // TODO: remove newline after key node
+        
+        xmlUnlinkNode(key->node);
+        xmlFreeNode(key->node);
+    }
+}
+
+
 static int load_key(
         DavConfig *config,
         DavCfgKey **list_begin,
@@ -621,6 +689,7 @@
     DavCfgKey *key = cxMalloc(config->mp->allocator, sizeof(DavCfgKey));
     memset(key, 0, sizeof(DavCfgKey));
     key->type = DAV_KEY_TYPE_AES256;
+    key->node = keynode;
     
     int error = 0;
     while(node) {
@@ -922,6 +991,15 @@
     return 0;
 }
 
+const char* dav_config_keytype_str(DavCfgKeyType type) {
+    switch(type) {
+        default: break;
+        case DAV_KEY_TYPE_AES256: return "aes256";
+        case DAV_KEY_TYPE_AES128: return "aes128";
+    }
+    return NULL;
+}
+
 int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey) {
     for(DavCfgKey *key=config->keys;key;key=key->next) {
         char *file = cx_strdup_m(key->file.value).ptr;
--- a/libidav/config.h	Tue Oct 29 11:51:25 2024 +0100
+++ b/libidav/config.h	Tue Oct 29 17:51:02 2024 +0100
@@ -131,6 +131,8 @@
 };
 
 struct DavCfgKey {
+    xmlNode *node;
+    
     CfgString  name;
     CfgString  file;
     DavCfgKeyType type;
@@ -178,6 +180,11 @@
 void dav_repository_set_auth(DavConfig *config, DavCfgRepository *repo, cxstring user, cxstring password);
 cxmutstr dav_repository_get_decodedpassword(DavCfgRepository *repo);
 
+void dav_config_add_key(DavConfig *config, DavCfgKey *key);
+
+DavCfgKey* dav_key_new(DavConfig *config);
+void dav_key_remove_and_free(DavConfig *config, DavCfgKey *key);
+
 int dav_str2ssl_version(const char *str);
 
 int dav_cfg_string_set_node_value(DavConfig *config, CfgString *str, xmlNode *node);
@@ -198,6 +205,7 @@
 DavCfgRepository* dav_config_url2repo_s(DavConfig *config, cxstring url, cxmutstr *path);
 
 int dav_config_keytype(DavCfgKeyType type);
+const char* dav_config_keytype_str(DavCfgKeyType type);
 int dav_config_register_keys(DavConfig *config, DavContext *ctx, dav_loadkeyfile_func loadkey);
 
 int dav_config_register_namespaces(DavConfig *config, DavContext *ctx);
--- a/ui/gtk/text.c	Tue Oct 29 11:51:25 2024 +0100
+++ b/ui/gtk/text.c	Tue Oct 29 17:51:02 2024 +0100
@@ -536,8 +536,10 @@
     UiVar* var = uic_widget_var(obj->ctx, current->ctx, args.value, args.varname, UI_VAR_STRING);
     
     UiTextField *uitext = malloc(sizeof(UiTextField));
-    uitext->ctx = obj->ctx;
+    uitext->obj = obj;
     uitext->var = var;
+    uitext->onchange = args.onchange;
+    uitext->onchangedata = args.onchangedata;
     
     g_signal_connect(
                 textfield,
@@ -576,7 +578,9 @@
         value->value.ptr = NULL;
         value->value.free = NULL;
         value->obj = GTK_ENTRY(textfield);
-        
+    }
+    
+    if(args.onchange || var) {
         g_signal_connect(
                 textfield,
                 "changed",
@@ -605,15 +609,20 @@
 }
 
 void ui_textfield_changed(GtkEditable *editable, UiTextField *textfield) {
-    // changed event is only registered, if the textfield->var != NULL
     UiString *value = textfield->var->value;
-    if(value->observers) {
-        UiEvent e;
-        e.obj = textfield->ctx->obj;
-        e.window = e.obj->window;
-        e.document = textfield->ctx->document;
-        e.eventdata = value;
-        e.intval = 0;
+    
+    UiEvent e;
+    e.obj = textfield->obj;
+    e.window = e.obj->window;
+    e.document = textfield->obj->ctx->document;
+    e.eventdata = value;
+    e.intval = 0;
+    
+    if(textfield->onchange) {
+        textfield->onchange(&e, textfield->onchangedata);
+    }
+    
+    if(textfield->var) {
         ui_notify_evt(value->observers, &e);
     }
 }
--- a/ui/gtk/text.h	Tue Oct 29 11:51:25 2024 +0100
+++ b/ui/gtk/text.h	Tue Oct 29 17:51:02 2024 +0100
@@ -67,9 +67,10 @@
 } UiTextArea;
 
 typedef struct UiTextField {
-    UiContext *ctx;
-    UiVar    *var;
-    // TODO: validatefunc
+    UiObject    *obj;
+    UiVar       *var;
+    ui_callback onchange;
+    void        *onchangedata;
 } UiTextField;
 
 typedef struct UiPathTextField {
--- a/ui/gtk/window.c	Tue Oct 29 11:51:25 2024 +0100
+++ b/ui/gtk/window.c	Tue Oct 29 17:51:02 2024 +0100
@@ -659,13 +659,21 @@
 #define DEFAULT_BUTTON(window, button) gtk_window_set_default_widget(GTK_WINDOW(window), button)
 #else
 #define HEADERBAR_SHOW_CLOSEBUTTON(headerbar, set) gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(headerbar), set)
-#define DEFAULT_BUTTON(window, button) gtk_window_set_default(GTK_WINDOW(window), button)
+#define DEFAULT_BUTTON(window, button) gtk_widget_set_can_default(button, TRUE); gtk_window_set_default(GTK_WINDOW(window), button)
 #endif
 
 
 
 UiObject* ui_dialog_window_create(UiObject *parent, UiDialogWindowArgs args) {
     GtkWidget *dialog = DIALOG_NEW();
+    if(args.width > 0 || args.height > 0) {
+        gtk_window_set_default_size(
+                GTK_WINDOW(dialog),
+                args.width,
+                args.height);
+    }
+    
+    
     gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(parent->widget));
     if(args.modal != UI_OFF) {
         gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);

mercurial