/* * JoeyDev * Copyright (C) 2018-2023 Scott Duensing * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ #include #include "common.h" #include "messages.h" #include "utils.h" static GtkWidget *_lstMessages = NULL; static char **_pendingMessages = NULL; static pthread_mutex_t _mtxMessage; static gboolean messageScroll(gpointer userData); static gboolean messagesUpdate(gpointer userData); EVENT gboolean winMessagesClose(GtkWidget *object, gpointer userData); static void winMessagesDelete(gpointer userData); void message(MessageTypesT level, char *format, ...) { va_list args; char *string; char *msg; char *tok; char *labels[MSG_COUNT] = { " Info:", "Warning:", " Error:", " Severe:" }; // Construct message. va_start(args, format); msg = utilCreateStringVArgs(format, args); va_end(args); debug("%s\n", msg); // Break multiline messages down into individual lines. tok = strtok(msg, "\n"); while (tok != NULL) { //***TODO*** Filter out things that could be mistaken as markup tags. string = utilCreateString("%s %s", labels[level], tok); // Save for UI thread. message() is not always called from the UI thread! pthread_mutex_lock(&_mtxMessage); arrput(_pendingMessages, string); // Do not free string. pthread_mutex_unlock(&_mtxMessage); tok = strtok(NULL, "\n"); } DEL(msg); } static gboolean messageScroll(gpointer userData) { GtkAdjustment *adjustment; static int count = 100; // Try this many times to scroll. (void)userData; //***TODO*** This doesn't always scroll to the very end. adjustment = gtk_list_box_get_adjustment(GTK_LIST_BOX(_lstMessages)); gtk_adjustment_set_value(adjustment, gtk_adjustment_get_upper(adjustment)); gtk_widget_show_all(_lstMessages); utilForceUpdate(); count--; if (count > 0) return G_SOURCE_CONTINUE; count = 100; return G_SOURCE_REMOVE; } void messageShutdown(void) { g_idle_remove_by_data(messagesUpdate); pthread_mutex_destroy(&_mtxMessage); } void messageStartup(void) { if (pthread_mutex_init(&_mtxMessage, NULL) != 0) { debug("Message mutex init failed!\n"); exit(1); } // Set up updates on the UI thread. g_idle_add(messagesUpdate, messagesUpdate); } static gboolean messagesUpdate(gpointer userData) { WindowDataT *self = NULL; char *widgetNames[] = { "winMessages", "lstMessages", NULL }; GtkWidget **widgets[] = { NULL, &_lstMessages }; GtkWidget *row; GtkWidget *box; GtkWidget *label; char *string = NULL; (void)userData; // Lock and access the pending message list. pthread_mutex_lock(&_mtxMessage); if (arrlen(_pendingMessages) > 0) { string = _pendingMessages[0]; // We just need the pointer. arrdel(_pendingMessages, 0); } pthread_mutex_unlock(&_mtxMessage); // Are there pending messages? if (string != NULL) { // Do we need to open the message window? if (!_lstMessages) { // Set up instance data. We only need WindowDataT since this is a "singleton" window. self = NEW(WindowDataT); self->closeWindow = winMessagesClose; widgets[0] = &self->window; utilGetWidgetsFromMemory("/com/kangaroopunch/joeydev/Messages.glade", widgetNames, widgets, self); // Register window. utilWindowRegister(self); // Show window. gtk_widget_show_all(self->window); } // Create new row with the message string. row = gtk_list_box_row_new(); box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6); gtk_widget_set_hexpand(box, TRUE); label = gtk_label_new(string); gtk_label_set_use_markup(GTK_LABEL(label), TRUE); // Add new row to the message box. gtk_box_pack_start(GTK_BOX(box), label, FALSE, FALSE, 0); gtk_container_add(GTK_CONTAINER(row), box); gtk_list_box_insert(GTK_LIST_BOX(_lstMessages), row, -1); gtk_widget_show_all(_lstMessages); utilForceUpdate(); // Scroll to show new row. g_idle_add(messageScroll, NULL); DEL(string); // Finally free the string allocated in message(). } return G_SOURCE_CONTINUE; } EVENT void toolMessagesClearClicked(GtkWidget *widget, gpointer userData) { GList *children; GList *iter; (void)widget; (void)userData; children = gtk_container_get_children(GTK_CONTAINER(_lstMessages)); for (iter = children; iter != NULL; iter = g_list_next(iter)) { gtk_widget_destroy(GTK_WIDGET(iter->data)); } g_list_free(children); } EVENT gboolean winMessagesClose(GtkWidget *object, gpointer userData) { // userData is not reliable due to util indirectly calling us. WindowDataT *self = (WindowDataT *)utilGetWindowData(object); (void)userData; winMessagesDelete(self); return FALSE; } static void winMessagesDelete(gpointer userData) { // Delete any pending messages. while (arrlen(_pendingMessages) > 0) { DEL(_pendingMessages[0]); arrdel(_pendingMessages, 0); } //ARRFREE(_pendingMessages); // This is making Memwatch mad. utilWindowUnRegister(userData); _lstMessages = NULL; DEL(userData); }