/* webdav-gtk A WebDAV client written with gtk-3.0 and libneon Copyright (C) 2022 Xavier Del Campo This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include #include #include #include #include #include static backend_session dav_session; /* Based on https://github.com/Miqueas/c-gtk3-examples/blob/main/GtkBuilder.c * Under zlib license: * https://github.com/Miqueas/C-GTK3-Examples/blob/main/LICENSE */ void on_close(GtkAssistant *const assistant, const gpointer data) { gtk_widget_destroy(GTK_WIDGET(assistant)); } #define ELEMENTS \ X(hostname) \ X(username) \ X(password) enum { #define X(x) x, ELEMENTS #undef X N_ELEMENTS }; static const gchar *const names[] = { #define X(x) [x] = #x, ELEMENTS #undef X }; void on_text_changed(GtkEntry *const entry, const gpointer user_data) { gboolean ready = TRUE; GtkBuilder *const builder = GTK_BUILDER(user_data); for (size_t i = 0; i < sizeof names / sizeof *names; i++) { GtkEntry *const e = GTK_ENTRY(gtk_builder_get_object(builder, names[i])); if (!gtk_entry_get_text_length(e)) ready = FALSE; } GtkButton *const connect = GTK_BUTTON( gtk_builder_get_object(builder, "connect")); gtk_widget_set_sensitive(GTK_WIDGET(connect), ready); } static void conn_status(const char *const status, void *const user_data) { GtkBuilder *const builder = user_data; g_signal_emit_by_name(builder, "progress-updated", (const gchar *)status, user_data); } void on_conn_status(GtkBuilder *const builder, const gchar *const status, void *const user_data) { GtkLabel *const label = GTK_LABEL(gtk_builder_get_object(builder, "connection_status")); gtk_label_set_text(label, (const gchar *)status); } void on_connection_finished(GtkBuilder *const builder, const gpointer p, const gchar *const error) { GtkLabel *const error_label = GTK_LABEL(gtk_builder_get_object(builder, "error_label")); GtkSpinner *const spinner = GTK_SPINNER(gtk_builder_get_object(builder, "spinner")); gtk_label_set_text(error_label, error ? error : ""); gtk_spinner_stop(spinner); const backend_session session = p; g_debug("session=%p, error=%s (%p)", session, error, error); if (session) { GtkLabel *const status = GTK_LABEL(gtk_builder_get_object( builder, "connection_status")); GtkAssistant *const assistant = GTK_ASSISTANT(gtk_builder_get_object( builder, "intro_assistant")); GtkWidget *const page1 = GTK_WIDGET(gtk_builder_get_object( builder, "page1")), *const page2 = GTK_WIDGET(gtk_builder_get_object( builder, "page2")); gtk_assistant_set_page_complete(assistant, page1, TRUE); gtk_assistant_set_page_complete(assistant, page2, TRUE); gtk_label_set_text(status, "Session established successfully"); gtk_label_set_text(error_label, ""); dav_session = session; } } struct connect_args { struct backend_cfg cfg; GtkBuilder *builder; }; static gboolean do_connect(const gpointer args) { struct connect_args *const ca = args; const struct backend_cfg *const cfg = &ca->cfg; const char *error; const backend_session session = backend_connect(cfg, &error); g_signal_emit_by_name(ca->builder, "connection-finished", session, error); return G_SOURCE_REMOVE; } static void connect_free(const gpointer args) { free(args); } static gpointer connect_thread_main(const gpointer data) { /* Based on GNOME Developer Documentation, "Main Contexts" tutorial. */ GMainContext *const context = data; GMainLoop *loop; g_main_context_push_thread_default(context); loop = g_main_loop_new(context, FALSE); g_main_loop_run(loop); g_main_loop_unref(loop); g_main_context_pop_thread_default(context); g_main_context_unref(context); return NULL; } void on_connect(GtkButton *const button, const gpointer user_data) { GtkBuilder *const builder = GTK_BUILDER(user_data); struct connect_args *const args = calloc(1, sizeof *args); if (!args) { fprintf(stderr, "%s: calloc(3): %s\n", __func__, strerror(errno)); return; } *args = (const struct connect_args) { .builder = builder, .cfg = { .hostname = gtk_entry_get_text(GTK_ENTRY( gtk_builder_get_object(builder, "hostname"))), .username = gtk_entry_get_text(GTK_ENTRY( gtk_builder_get_object(builder, "username"))), .password = gtk_entry_get_text(GTK_ENTRY( gtk_builder_get_object(builder, "password"))), .use_encryption = gtk_switch_get_active(GTK_SWITCH( gtk_builder_get_object(builder, "tls_enabled"))), .conn_status = conn_status, .user_data = builder } }; GtkSpinner *const spinner = GTK_SPINNER(gtk_builder_get_object(builder, "spinner")); GtkLabel *const status = GTK_LABEL(gtk_builder_get_object(builder, "connection_status")); GtkLabel *const error = GTK_LABEL(gtk_builder_get_object(builder, "error_label")); gtk_spinner_start(spinner); gtk_label_set_text(status, ""); gtk_label_set_text(error, ""); GMainContext *const context = g_main_context_new(); g_thread_new("backend-connect", connect_thread_main, g_main_context_ref(context)); g_main_context_invoke_full(context, G_PRIORITY_DEFAULT, do_connect, args, connect_free); } static void startup(GtkApplication *const app, const gpointer user_data) { GtkBuilder *const builder = gtk_builder_new(); GError *error = NULL; if (!gtk_builder_add_from_file(builder, "ui/intro.ui", &error)) { fprintf(stderr, "gtk_builder_add_from_file: %s\n", error->message); return; } gtk_builder_connect_signals(builder, NULL); GObject *const assistant = gtk_builder_get_object(builder, "intro_assistant"), *const page0 = gtk_builder_get_object(builder, "page0"); for (size_t i = 0; i < sizeof names / sizeof *names; i++) g_signal_connect(gtk_builder_get_object(builder, names[i]), "changed", G_CALLBACK(on_text_changed), builder); g_signal_connect(gtk_builder_get_object(builder, "connect"), "released", G_CALLBACK(on_connect), builder); /* Based on https://libsoup.org/gobject/howto-signals.html */ g_signal_newv("progress-updated", GTK_TYPE_BUILDER, G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, NULL, /* closure */ NULL, /* accumulator */ NULL, /* accumulator data */ NULL, /* C marshaller */ G_TYPE_NONE, /* return type */ 2, /* n_params */ (GType[]){G_TYPE_STRING, G_TYPE_POINTER}); g_signal_newv("connection-finished", GTK_TYPE_BUILDER, G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS, NULL, /* closure */ NULL, /* accumulator */ NULL, /* accumulator data */ NULL, /* C marshaller */ G_TYPE_NONE, /* return type */ 2, /* n_params */ (GType[]){G_TYPE_POINTER, G_TYPE_STRING}); g_signal_connect(builder, "progress-updated", G_CALLBACK(on_conn_status), builder); g_signal_connect(builder, "connection-finished", G_CALLBACK(on_connection_finished), builder); gtk_assistant_set_page_complete(GTK_ASSISTANT(assistant), GTK_WIDGET(page0), TRUE); gtk_application_add_window(GTK_APPLICATION(app), GTK_WINDOW(assistant)); } static void activate(GtkApplication *const app, const gpointer user_data) { GtkWindow *const window = gtk_application_get_active_window(GTK_APPLICATION(app)); gtk_window_present(window); } int gui_main(const int argc, char **const argv) { int ret; GtkApplication *const app = gtk_application_new("org.web.dav", G_APPLICATION_FLAGS_NONE); g_signal_connect(app, "startup", G_CALLBACK(startup), NULL); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); ret = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); return ret; }