If we build Profanity without GTK we dont iterate the context so we will
never know when the editor exited. Calling `g_main_context_iteration()`
or finally switching to GMainLoop fixes this.
So far calling `tray_update()` does this for us in GTK builds.
So this went unnoticed.
The editor will inherit ignored signal handlers (SIGINT, SIGTSTP, SIGPIPE)
from us. Neovim uses libuv which seems to check SIGINT on startup to
determine whether the environment is interactive or not.
We now reset these signals to SIG_DFL in the child process before execvp.
Profanity uses readline which still competed for terminal input with the
editor. We only took care about other Profanity UI processes in our
previous commit. This led to misordered characters in the editor.
In my tests with vim everything worked fine and these bugs were
discovered when a user used neovim.
Fixes: https://github.com/profanity-im/profanity/issues/2148
Ref: 36ec2b0ae1
Signed-off-by: Michael Vetter <jubalh@iodoru.org>
315 lines
7.8 KiB
C
315 lines
7.8 KiB
C
/*
|
|
* profanity.c
|
|
* vim: expandtab:ts=4:sts=4:sw=4
|
|
*
|
|
* Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
|
|
* Copyright (C) 2019 - 2026 Michael Vetter <jubalh@iodoru.org>
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later WITH OpenSSL-exception
|
|
*/
|
|
#include "config.h"
|
|
#include <unistd.h>
|
|
|
|
#ifdef HAVE_GTK
|
|
#include "ui/tray.h"
|
|
#endif
|
|
|
|
#include <locale.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include "profanity.h"
|
|
#include "common.h"
|
|
#include "log.h"
|
|
#include "chatlog.h"
|
|
#include "config/files.h"
|
|
#include "config/tlscerts.h"
|
|
#include "config/accounts.h"
|
|
#include "config/preferences.h"
|
|
#include "config/theme.h"
|
|
#include "tools/spellcheck.h"
|
|
#include "config/tlscerts.h"
|
|
#include "config/scripts.h"
|
|
#include "command/cmd_defs.h"
|
|
#include "plugins/plugins.h"
|
|
#include "event/client_events.h"
|
|
#include "ui/ui.h"
|
|
#include "ui/window_list.h"
|
|
#include "xmpp/resource.h"
|
|
#include "xmpp/session.h"
|
|
#include "xmpp/xmpp.h"
|
|
#include "xmpp/muc.h"
|
|
#include "xmpp/chat_session.h"
|
|
#include "xmpp/chat_state.h"
|
|
#include "xmpp/contact.h"
|
|
#include "xmpp/roster_list.h"
|
|
|
|
#ifdef HAVE_LIBOTR
|
|
#include "otr/otr.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_LIBGPGME
|
|
#include "pgp/gpg.h"
|
|
#endif
|
|
|
|
#ifdef HAVE_OMEMO
|
|
#include "omemo/omemo.h"
|
|
#endif
|
|
|
|
static unsigned int min_runtime = 5;
|
|
|
|
static void _init(char* log_level, char* config_file, char* log_file, char* theme_name);
|
|
static void _shutdown(void);
|
|
static void _connect_default(const char* const account);
|
|
|
|
pthread_mutex_t lock;
|
|
static gboolean force_quit = FALSE;
|
|
|
|
void
|
|
prof_run(gchar* log_level, gchar* account_name, gchar* config_file, gchar* log_file, gchar* theme_name, gchar** commands)
|
|
{
|
|
gboolean cont = TRUE;
|
|
|
|
_init(log_level, config_file, log_file, theme_name);
|
|
plugins_on_start();
|
|
_connect_default(account_name);
|
|
|
|
ui_update();
|
|
|
|
log_info("Starting main event loop");
|
|
|
|
session_init_activity();
|
|
|
|
GTimer* runtime = g_timer_new();
|
|
|
|
char* line = NULL;
|
|
GTimer* waittimer = g_timer_new();
|
|
g_timer_stop(waittimer);
|
|
int waittime = 0;
|
|
while (cont && !force_quit) {
|
|
log_stderr_handler();
|
|
session_check_autoaway();
|
|
|
|
line = commands ? *commands : inp_readline();
|
|
if (commands && line && memcmp(line, "/sleep", 6) == 0) {
|
|
if (!g_timer_is_active(waittimer)) {
|
|
gchar* err_msg;
|
|
if (strtoi_range(line + 7, &waittime, 0, 300, &err_msg)) {
|
|
g_timer_start(waittimer);
|
|
/* Increase the minimal runtime by the waiting time
|
|
* so we can be sure there's runtime left after executing
|
|
* the last command.
|
|
*/
|
|
min_runtime += waittime;
|
|
} else {
|
|
log_error(err_msg);
|
|
g_free(err_msg);
|
|
commands = NULL;
|
|
}
|
|
} else if (g_timer_elapsed(waittimer, NULL) >= waittime) {
|
|
g_timer_stop(waittimer);
|
|
commands++;
|
|
if (!(*commands))
|
|
commands = NULL;
|
|
}
|
|
cont = TRUE;
|
|
} else if (line) {
|
|
ProfWin* window = wins_get_current();
|
|
cont = cmd_process_input(window, line);
|
|
if (commands) {
|
|
commands++;
|
|
if (!(*commands))
|
|
commands = NULL;
|
|
} else {
|
|
free(line);
|
|
line = NULL;
|
|
}
|
|
} else {
|
|
cont = TRUE;
|
|
}
|
|
|
|
#ifdef HAVE_LIBOTR
|
|
otr_poll();
|
|
#endif
|
|
plugins_run_timed();
|
|
notify_remind();
|
|
session_process_events();
|
|
iq_autoping_check();
|
|
ui_update();
|
|
while (g_main_context_iteration(NULL, FALSE))
|
|
;
|
|
#ifdef HAVE_GTK
|
|
tray_update();
|
|
#endif
|
|
}
|
|
g_timer_destroy(waittimer);
|
|
g_timer_elapsed(runtime, NULL) < min_runtime ? sleep(min_runtime) : (void)NULL;
|
|
g_timer_destroy(runtime);
|
|
}
|
|
|
|
gboolean
|
|
prof_set_quit(void)
|
|
{
|
|
gboolean ret = force_quit;
|
|
force_quit = TRUE;
|
|
return ret;
|
|
}
|
|
|
|
static void
|
|
_connect_default(const char* const account)
|
|
{
|
|
ProfWin* window = wins_get_current();
|
|
if (account) {
|
|
cmd_execute_connect(window, account);
|
|
} else {
|
|
auto_gchar gchar* pref_connect_account = prefs_get_string(PREF_CONNECT_ACCOUNT);
|
|
if (pref_connect_account) {
|
|
cmd_execute_connect(window, pref_connect_account);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
sigterm_handler(int sig)
|
|
{
|
|
log_info("Received signal %d, exiting", sig);
|
|
force_quit = TRUE;
|
|
}
|
|
|
|
static void
|
|
_init(char* log_level, char* config_file, char* log_file, char* theme_name)
|
|
{
|
|
setlocale(LC_ALL, "");
|
|
// ignore SIGPIPE
|
|
signal(SIGPIPE, SIG_IGN);
|
|
signal(SIGINT, SIG_IGN);
|
|
signal(SIGTSTP, SIG_IGN);
|
|
signal(SIGWINCH, ui_sigwinch_handler);
|
|
signal(SIGTERM, sigterm_handler);
|
|
signal(SIGHUP, sigterm_handler);
|
|
if (pthread_mutex_init(&lock, NULL) != 0) {
|
|
log_error("Mutex init failed");
|
|
exit(1);
|
|
}
|
|
|
|
pthread_mutex_lock(&lock);
|
|
files_create_directories();
|
|
log_level_t prof_log_level;
|
|
log_level_from_string(log_level, &prof_log_level);
|
|
prefs_load(config_file);
|
|
log_init(prof_log_level, log_file);
|
|
log_stderr_init(PROF_LEVEL_ERROR);
|
|
|
|
auto_gchar gchar* prof_version = prof_get_version();
|
|
log_info("Starting Profanity (%s)…", prof_version);
|
|
|
|
chatlog_init();
|
|
accounts_load();
|
|
|
|
if (theme_name) {
|
|
theme_init(theme_name);
|
|
} else {
|
|
auto_gchar gchar* theme = prefs_get_string(PREF_THEME);
|
|
theme_init(theme);
|
|
}
|
|
|
|
ui_init();
|
|
if (prof_log_level == PROF_LEVEL_DEBUG) {
|
|
ProfWin* console = wins_get_console();
|
|
win_println(console, THEME_DEFAULT, "-", "Debug mode enabled! Logging to: ");
|
|
win_println(console, THEME_DEFAULT, "-", get_log_file_location());
|
|
}
|
|
session_init();
|
|
cmd_init();
|
|
log_info("Initialising contact list");
|
|
muc_init();
|
|
tlscerts_init();
|
|
scripts_init();
|
|
#ifdef HAVE_LIBOTR
|
|
otr_init();
|
|
#endif
|
|
#ifdef HAVE_LIBGPGME
|
|
p_gpg_init();
|
|
#endif
|
|
#ifdef HAVE_OMEMO
|
|
omemo_init();
|
|
#endif
|
|
spellcheck_init();
|
|
atexit(_shutdown);
|
|
plugins_init();
|
|
#ifdef HAVE_GTK
|
|
tray_init();
|
|
#endif
|
|
inp_nonblocking(TRUE);
|
|
ui_resize();
|
|
}
|
|
|
|
static GList* shutdown_routines = NULL;
|
|
|
|
/* We have to encapsulate the function pointer, since the C standard does not guarantee
|
|
* that a void* can be converted to a function* and vice-versa.
|
|
*/
|
|
struct shutdown_routine
|
|
{
|
|
void (*routine)(void);
|
|
};
|
|
|
|
static gint
|
|
_cmp_shutdown_routine(const struct shutdown_routine* a, const struct shutdown_routine* b)
|
|
{
|
|
return a->routine != b->routine;
|
|
}
|
|
|
|
void
|
|
prof_add_shutdown_routine(void (*routine)(void))
|
|
{
|
|
struct shutdown_routine this = { .routine = routine };
|
|
if (g_list_find_custom(shutdown_routines, &this, (GCompareFunc)_cmp_shutdown_routine)) {
|
|
return;
|
|
}
|
|
struct shutdown_routine* r = g_new(struct shutdown_routine, 1);
|
|
r->routine = routine;
|
|
shutdown_routines = g_list_prepend(shutdown_routines, r);
|
|
}
|
|
|
|
static void
|
|
_call_and_free_shutdown_routine(struct shutdown_routine* r)
|
|
{
|
|
r->routine();
|
|
free(r);
|
|
}
|
|
|
|
void
|
|
prof_shutdown(void)
|
|
{
|
|
if (shutdown_routines) {
|
|
g_list_free_full(shutdown_routines, (GDestroyNotify)_call_and_free_shutdown_routine);
|
|
shutdown_routines = NULL;
|
|
}
|
|
}
|
|
|
|
static void
|
|
_shutdown(void)
|
|
{
|
|
if (prefs_get_boolean(PREF_WINTITLE_SHOW)) {
|
|
if (prefs_get_boolean(PREF_WINTITLE_GOODBYE)) {
|
|
ui_goodbye_title();
|
|
} else {
|
|
ui_clear_win_title();
|
|
}
|
|
}
|
|
|
|
jabber_conn_status_t conn_status = connection_get_status();
|
|
if (conn_status == JABBER_CONNECTED) {
|
|
cl_ev_disconnect();
|
|
}
|
|
prof_shutdown();
|
|
/* Prefs and logs have to be closed in swapped order, so they're no using the automatic
|
|
* shutdown mechanism. */
|
|
prefs_close();
|
|
log_close();
|
|
}
|