webdav-gtk/gui.c

286 lines
8.7 KiB
C

/*
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 <http://www.gnu.org/licenses/>.
*/
#include <backend.h>
#include <glib.h>
#include <gtk/gtk.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
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;
}