/*
This file is part of GNUnet.
Copyright (C) 2021--2022 GNUnet e.V.
GNUnet is free software: you can redistribute it and/or modify it
under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
GNUnet 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
Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see .
SPDX-License-Identifier: AGPL3.0-or-later
*/
/*
* @author Tobias Frisch
* @file application.c
*/
#include "application.h"
#include "resources.h"
#include
#include
#include
#include
static void
_load_ui_stylesheets(MESSENGER_Application *app)
{
GdkScreen* screen = gdk_screen_get_default();
GtkCssProvider* provider = gtk_css_provider_new();
gtk_css_provider_load_from_resource(
provider,
application_get_resource_path(app, "css/style.css")
);
gtk_style_context_add_provider_for_screen(
screen,
GTK_STYLE_PROVIDER(provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION
);
}
static gboolean
_application_accounts(gpointer user_data)
{
MESSENGER_Application *app = (MESSENGER_Application*) user_data;
app->init = 0;
ui_accounts_dialog_init(app, &(app->ui.accounts));
ui_accounts_dialog_refresh(app, &(app->ui.accounts));
gtk_widget_show(GTK_WIDGET(app->ui.accounts.dialog));
return FALSE;
}
static void
_application_init(MESSENGER_Application *app)
{
ui_messenger_init(app, &(app->ui.messenger));
if (app->chat.identity)
gtk_widget_show(GTK_WIDGET(app->ui.messenger.main_window));
else
app->init = g_idle_add(G_SOURCE_FUNC(_application_accounts), app);
}
static void
_application_activate(GApplication* application,
gpointer user_data)
{
MESSENGER_Application *app = (MESSENGER_Application*) user_data;
g_application_hold(application);
_application_init(app);
g_application_release(application);
}
static void
_application_open(GApplication* application,
GFile **files,
gint n_files,
UNUSED gchar* hint,
gpointer user_data)
{
MESSENGER_Application *app = (MESSENGER_Application*) user_data;
g_application_hold(application);
_application_init(app);
for (gint i = 0; i < n_files; i++) {
if (!g_file_has_uri_scheme(files[i], "gnunet"))
continue;
gchar *uri_string = g_file_get_uri(files[i]);
if (!uri_string)
continue;
char *emsg = NULL;
struct GNUNET_CHAT_Uri *uri = GNUNET_CHAT_uri_parse(uri_string, &emsg);
if (emsg)
{
g_printerr("ERROR: %s\n", emsg);
GNUNET_free(emsg);
}
if (!uri)
goto free_string;
GNUNET_CHAT_lobby_join(app->chat.messenger.handle, uri);
GNUNET_CHAT_uri_destroy(uri);
free_string:
g_free(uri_string);
}
g_application_release(application);
}
void
application_init(MESSENGER_Application *app,
int argc,
char **argv)
{
memset(app, 0, sizeof(*app));
app->argc = argc;
app->argv = argv;
gst_init(&argc, &argv);
gtk_init(&argc, &argv);
hdy_init();
app->application = gtk_application_new(
MESSENGER_APPLICATION_ID,
G_APPLICATION_HANDLES_OPEN |
G_APPLICATION_NON_UNIQUE
);
resources_register();
notify_init(MESSENGER_APPLICATION_NAME);
app->notifications = NULL;
_load_ui_stylesheets(app);
app->chat.status = EXIT_FAILURE;
app->chat.tid = 0;
pipe(app->chat.pipe);
pthread_mutex_init(&(app->chat.mutex), NULL);
app->quarks.widget = g_quark_from_string("messenger_widget");
app->quarks.data = g_quark_from_string("messenger_data");
app->quarks.ui = g_quark_from_string("messenger_ui");
g_application_add_main_option(
G_APPLICATION(app->application),
"mobile",
'm',
G_OPTION_FLAG_NONE,
G_OPTION_ARG_NONE,
"Optimize UI spacing for mobile devices",
NULL
);
g_application_add_main_option(
G_APPLICATION(app->application),
"ego",
'e',
G_OPTION_FLAG_NONE,
G_OPTION_ARG_STRING,
"Identity to select for messaging",
"IDENTITY"
);
g_signal_connect(
app->application,
"activate",
G_CALLBACK(_application_activate),
app
);
g_signal_connect(
app->application,
"open",
G_CALLBACK(_application_open),
app
);
}
const gchar*
application_get_resource_path(MESSENGER_Application *app,
const char *path)
{
static gchar resource_path [PATH_MAX];
const gchar *base_path = g_application_get_resource_base_path(
G_APPLICATION(app->application)
);
snprintf(resource_path, PATH_MAX, "%s/%s", base_path, path);
return resource_path;
}
static void*
_application_chat_thread(void *args)
{
MESSENGER_Application *app = (MESSENGER_Application*) args;
struct GNUNET_GETOPT_CommandLineOption options[] = {
GNUNET_GETOPT_option_flag (
'm',
"mobile",
"Optimize UI spacing for mobile devices",
&(app->settings.mobile_design)
),
GNUNET_GETOPT_option_string (
'e',
"ego",
"IDENTITY",
"Identity to select for messaging",
&(app->chat.identity)
),
GNUNET_GETOPT_OPTION_END
};
app->chat.status = (GNUNET_PROGRAM_run(
app->argc,
app->argv,
MESSENGER_APPLICATION_BINARY,
gettext_noop(MESSENGER_APPLICATION_DESCRIPTION),
options,
&chat_messenger_run,
app
) == GNUNET_OK? EXIT_SUCCESS : EXIT_FAILURE);
return NULL;
}
void
application_run(MESSENGER_Application *app)
{
// Start thread to run GNUnet scheduler
pthread_create(&(app->chat.tid), NULL, _application_chat_thread, app);
app->ui.status = g_application_run(
G_APPLICATION(app->application),
app->argc,
app->argv
);
if (app->ui.status != 0)
application_exit(app, MESSENGER_FAIL);
// Wait for other thread to stop properly
pthread_join(app->chat.tid, NULL);
close(app->chat.pipe[0]);
close(app->chat.pipe[1]);
pthread_mutex_destroy(&(app->chat.mutex));
// Get rid of open notifications
GList *list = app->notifications;
while (list) {
if (list->data)
notify_notification_close(NOTIFY_NOTIFICATION(list->data), NULL);
list = list->next;
}
if (app->notifications)
g_list_free(app->notifications);
notify_uninit();
resources_unregister();
g_object_unref(app->application);
}
typedef struct MESSENGER_ApplicationEventCall
{
MESSENGER_Application *app;
MESSENGER_ApplicationEvent event;
} MESSENGER_ApplicationEventCall;
static gboolean
_application_event_call(gpointer user_data)
{
MESSENGER_ApplicationEventCall *call;
call = (MESSENGER_ApplicationEventCall*) user_data;
// Locking the mutex for synchronization
pthread_mutex_lock(&(call->app->chat.mutex));
call->event(call->app);
pthread_mutex_unlock(&(call->app->chat.mutex));
GNUNET_free(call);
return FALSE;
}
void
application_call_event(MESSENGER_Application *app,
MESSENGER_ApplicationEvent event)
{
MESSENGER_ApplicationEventCall *call;
call = (MESSENGER_ApplicationEventCall*) GNUNET_malloc(
sizeof(MESSENGER_ApplicationEventCall)
);
call->app = app;
call->event = event;
g_timeout_add(0, G_SOURCE_FUNC(_application_event_call), call);
}
typedef struct MESSENGER_ApplicationMessageEventCall
{
MESSENGER_Application *app;
MESSENGER_ApplicationMessageEvent event;
struct GNUNET_CHAT_Context *context;
const struct GNUNET_CHAT_Message *message;
} MESSENGER_ApplicationMessageEventCall;
static gboolean
_application_message_event_call(gpointer user_data)
{
MESSENGER_ApplicationMessageEventCall *call;
call = (MESSENGER_ApplicationMessageEventCall*) user_data;
// Locking the mutex for synchronization
pthread_mutex_lock(&(call->app->chat.mutex));
call->event(call->app, call->context, call->message);
pthread_mutex_unlock(&(call->app->chat.mutex));
GNUNET_free(call);
return FALSE;
}
void
application_call_message_event(MESSENGER_Application *app,
MESSENGER_ApplicationMessageEvent event,
struct GNUNET_CHAT_Context *context,
const struct GNUNET_CHAT_Message *message)
{
MESSENGER_ApplicationMessageEventCall *call;
if (!event)
return;
call = (MESSENGER_ApplicationMessageEventCall*) GNUNET_malloc(
sizeof(MESSENGER_ApplicationMessageEventCall)
);
call->app = app;
call->event = event;
call->context = context;
call->message = message;
g_idle_add(G_SOURCE_FUNC(_application_message_event_call), call);
}
void
application_exit(MESSENGER_Application *app,
MESSENGER_ApplicationSignal signal)
{
// Forward a signal to the other thread causing it to shutdown the
// GNUnet handles of the application.
write(app->chat.pipe[1], &signal, sizeof(signal));
}
int
application_status(MESSENGER_Application *app)
{
if (EXIT_SUCCESS != app->chat.status)
return app->chat.status;
return app->ui.status;
}