Sun, 03 Nov 2024 00:05:44 -0500
remove purple3-url-handler.desktop.in.in
The application that did the launching was removed long ago. Also this should
be implemented by the user interfaces and they might provide separate files to
groups schemas as well.
Testing Done:
Called in the turtles.
Reviewed at https://reviews.imfreedom.org/r/3640/
/* pidgin * * Pidgin is the legal property of its developers, whose names are too numerous * to list here. Please refer to the COPYRIGHT file distributed with this * source distribution. * * 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 2 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA */ #include <purpleconfig.h> #include <glib/gi18n-lib.h> #include <glib/gstdio.h> #include <gtk/gtk.h> #include <purple.h> #include "pidginapplication.h" #include "pidgincore.h" #include "pidgindebug.h" #include <gdk/gdkkeysyms.h> struct _PidginDebugWindow { GtkWindow parent; GtkWidget *textview; GtkTextBuffer *buffer; GtkTextMark *start_mark; GtkTextMark *end_mark; struct { GtkTextTag *level[PURPLE_DEBUG_FATAL + 1]; GtkTextTag *category; GtkTextTag *filtered_invisible; GtkTextTag *filtered_visible; GtkTextTag *match; GtkTextTag *paused; } tags; GtkWidget *filter; GtkWidget *expression; GtkWidget *filterlevel; gboolean paused; GtkWidget *popover; GtkWidget *popover_invert; GtkWidget *popover_highlight; GRegex *regex; }; typedef struct { GDateTime *timestamp; PurpleDebugLevel level; gchar *domain; gchar *message; } PidginDebugMessage; static gboolean debug_print_enabled = FALSE; static GSettings *settings = NULL; static PidginDebugWindow *debug_win = NULL; static gulong pref_callback_id = 0; G_DEFINE_FINAL_TYPE(PidginDebugWindow, pidgin_debug_window, GTK_TYPE_WINDOW) static gboolean view_near_bottom(PidginDebugWindow *win) { GtkAdjustment *adj = gtk_scrollable_get_vadjustment( GTK_SCROLLABLE(win->textview)); return (gtk_adjustment_get_value(adj) >= (gtk_adjustment_get_upper(adj) - gtk_adjustment_get_page_size(adj) * 1.5)); } static void save_response_cb(GObject *obj, GAsyncResult *result, gpointer data) { PidginDebugWindow *win = (PidginDebugWindow *)data; GFile *file = NULL; GFileOutputStream *output = NULL; GtkTextIter start, end; GDateTime *date = NULL; char *date_str = NULL; char *tmp = NULL; GError *error = NULL; file = gtk_file_dialog_save_finish(GTK_FILE_DIALOG(obj), result, NULL); if(file == NULL) { return; } output = g_file_replace(file, NULL, FALSE, G_FILE_CREATE_NONE, NULL, &error); g_clear_object(&file); if(output == NULL) { purple_debug_error("debug", "Unable to open file to save debug log: %s", error->message); g_error_free(error); return; } date = g_date_time_new_now_local(); date_str = g_date_time_format(date, "%c"); g_date_time_unref(date); tmp = g_strdup_printf("Pidgin Debug Log : %s\n", date_str); g_output_stream_write_all(G_OUTPUT_STREAM(output), tmp, strlen(tmp), NULL, NULL, &error); g_free(tmp); g_free(date_str); if(error != NULL) { purple_debug_error("debug", "Unable to save debug log: %s", error->message); g_error_free(error); g_object_unref(output); return; } gtk_text_buffer_get_bounds(win->buffer, &start, &end); tmp = gtk_text_buffer_get_text(win->buffer, &start, &end, TRUE); g_output_stream_write_all(G_OUTPUT_STREAM(output), tmp, strlen(tmp), NULL, NULL, &error); g_free(tmp); if(error != NULL) { purple_debug_error("debug", "Unable to save debug log: %s", error->message); g_error_free(error); g_object_unref(output); return; } g_object_unref(output); } static void save_cb(G_GNUC_UNUSED GtkWidget *w, PidginDebugWindow *win) { GtkFileDialog *dialog; dialog = gtk_file_dialog_new(); gtk_file_dialog_set_title(dialog, _("Save Debug Log")); gtk_file_dialog_set_modal(dialog, TRUE); gtk_file_dialog_set_initial_name(dialog, "purple-debug.log"); gtk_file_dialog_save(dialog, GTK_WINDOW(win), NULL, save_response_cb, win); g_clear_object(&dialog); } static void clear_cb(G_GNUC_UNUSED GtkWidget *w, PidginDebugWindow *win) { gtk_text_buffer_set_text(win->buffer, "", 0); } static void pause_cb(GtkWidget *w, PidginDebugWindow *win) { win->paused = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(w)); if (!win->paused) { GtkTextIter start, end; gtk_text_buffer_get_bounds(win->buffer, &start, &end); gtk_text_buffer_remove_tag(win->buffer, win->tags.paused, &start, &end); gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(win->textview), win->end_mark, 0, TRUE, 0, 1); } } /****************************************************************************** * regex stuff *****************************************************************************/ static void regex_clear_color(GtkWidget *w) { gtk_widget_remove_css_class(w, "error"); gtk_widget_remove_css_class(w, "success"); } static void regex_change_color(GtkWidget *w, gboolean success) { if (success) { gtk_widget_remove_css_class(w, "error"); gtk_widget_add_css_class(w, "success"); } else { gtk_widget_remove_css_class(w, "success"); gtk_widget_add_css_class(w, "error"); } } static void do_regex(PidginDebugWindow *win, GtkTextIter *start, GtkTextIter *end) { gboolean highlight, invert; GMatchInfo *match; gint initial_position; gint start_pos, end_pos; GtkTextIter match_start, match_end; gchar *text; if (!win->regex) { return; } highlight = g_settings_get_boolean(settings, "highlight"); invert = g_settings_get_boolean(settings, "invert"); initial_position = gtk_text_iter_get_offset(start); if(!invert) { /* First hide everything. */ gtk_text_buffer_apply_tag(win->buffer, win->tags.filtered_invisible, start, end); } text = gtk_text_buffer_get_text(win->buffer, start, end, TRUE); g_regex_match(win->regex, text, 0, &match); while (g_match_info_matches(match)) { g_match_info_fetch_pos(match, 0, &start_pos, &end_pos); start_pos += initial_position; end_pos += initial_position; /* Expand match to full line of message. */ gtk_text_buffer_get_iter_at_offset(win->buffer, &match_start, start_pos); gtk_text_iter_set_line_index(&match_start, 0); gtk_text_buffer_get_iter_at_offset(win->buffer, &match_end, end_pos); gtk_text_iter_forward_line(&match_end); if(invert) { /* Make invisible. */ gtk_text_buffer_apply_tag(win->buffer, win->tags.filtered_invisible, &match_start, &match_end); } else { /* Make visible again (with higher priority.) */ gtk_text_buffer_apply_tag(win->buffer, win->tags.filtered_visible, &match_start, &match_end); if(highlight) { gtk_text_buffer_get_iter_at_offset( win->buffer, &match_start, start_pos); gtk_text_buffer_get_iter_at_offset( win->buffer, &match_end, end_pos); gtk_text_buffer_apply_tag(win->buffer, win->tags.match, &match_start, &match_end); } } g_match_info_next(match, NULL); } g_match_info_free(match); g_free(text); } static void regex_toggle_filter(PidginDebugWindow *win, gboolean filter) { GtkTextIter start, end; gtk_text_buffer_get_bounds(win->buffer, &start, &end); gtk_text_buffer_remove_tag(win->buffer, win->tags.match, &start, &end); gtk_text_buffer_remove_tag(win->buffer, win->tags.filtered_invisible, &start, &end); gtk_text_buffer_remove_tag(win->buffer, win->tags.filtered_visible, &start, &end); if (filter) { do_regex(win, &start, &end); } } static void regex_changed_cb(G_GNUC_UNUSED GtkWidget *w, PidginDebugWindow *win) { const gchar *text; if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(win->filter))) { gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(win->filter), FALSE); } text = gtk_editable_get_text(GTK_EDITABLE(win->expression)); if(purple_strempty(text)) { regex_clear_color(win->expression); gtk_widget_set_sensitive(win->filter, FALSE); return; } g_clear_pointer(&win->regex, g_regex_unref); win->regex = g_regex_new(text, G_REGEX_CASELESS, 0, NULL); if (win->regex == NULL) { /* failed to compile */ regex_change_color(win->expression, FALSE); gtk_widget_set_sensitive(win->filter, FALSE); } else { /* compiled successfully */ regex_change_color(win->expression, TRUE); gtk_widget_set_sensitive(win->filter, TRUE); } } static void regex_key_released_cb(G_GNUC_UNUSED GtkEventControllerKey *controller, guint keyval, G_GNUC_UNUSED guint keycode, G_GNUC_UNUSED GdkModifierType state, gpointer data) { PidginDebugWindow *win = data; if (gtk_widget_is_sensitive(win->filter)) { GtkToggleButton *tb = GTK_TOGGLE_BUTTON(win->filter); if ((keyval == GDK_KEY_Return || keyval == GDK_KEY_KP_Enter) && !gtk_toggle_button_get_active(tb)) { gtk_toggle_button_set_active(tb, TRUE); } if (keyval == GDK_KEY_Escape && gtk_toggle_button_get_active(tb)) { gtk_toggle_button_set_active(tb, FALSE); } } } static void regex_popup_cb(G_GNUC_UNUSED GtkGestureClick* self, G_GNUC_UNUSED gint n_press, gdouble x, gdouble y, gpointer data) { PidginDebugWindow *win = data; gtk_popover_set_pointing_to(GTK_POPOVER(win->popover), &(const GdkRectangle){(int)x, (int)y, 0, 0}); gtk_popover_popup(GTK_POPOVER(win->popover)); } static void debug_window_set_filter_level(PidginDebugWindow *win, int level) { gboolean scroll; int i; if (level != (int)gtk_drop_down_get_selected(GTK_DROP_DOWN(win->filterlevel))) { gtk_drop_down_set_selected(GTK_DROP_DOWN(win->filterlevel), level); } scroll = view_near_bottom(win); for (i = 0; i <= PURPLE_DEBUG_FATAL; i++) { g_object_set(win->tags.level[i], "invisible", i < level, NULL); } if (scroll) { gtk_text_view_scroll_to_mark(GTK_TEXT_VIEW(win->textview), win->end_mark, 0, TRUE, 0, 1); } } static void filter_level_changed_cb(GObject *obj, G_GNUC_UNUSED GParamSpec *pspec) { GtkDropDown *dropdown = GTK_DROP_DOWN(obj); g_settings_set_enum(settings, "filterlevel", gtk_drop_down_get_selected(dropdown)); } static void pidgin_debug_settings_changed_cb(GSettings *settings, char *key, gpointer data) { PidginDebugWindow *win = data; if(purple_strequal(key, "active")) { gboolean active = g_settings_get_boolean(settings, key); regex_toggle_filter(win, active); } else if(purple_strequal(key, "highlight") || purple_strequal(key, "invert")) { if(g_settings_get_boolean(settings, "active")) { regex_toggle_filter(win, TRUE); } } else if(purple_strequal(key, "filterlevel")) { int level = g_settings_get_enum(settings, key); debug_window_set_filter_level(win, level); } } static void pidgin_debug_window_dispose(GObject *object) { PidginDebugWindow *win = PIDGIN_DEBUG_WINDOW(object); gtk_widget_unparent(win->popover); G_OBJECT_CLASS(pidgin_debug_window_parent_class)->dispose(object); } static void pidgin_debug_window_finalize(GObject *object) { PidginDebugWindow *win = PIDGIN_DEBUG_WINDOW(object); g_clear_pointer(&win->regex, g_regex_unref); debug_win = NULL; g_settings_set_boolean(settings, "visible", FALSE); G_OBJECT_CLASS(pidgin_debug_window_parent_class)->finalize(object); } static void pidgin_debug_window_class_init(PidginDebugWindowClass *klass) { GObjectClass *obj_class = G_OBJECT_CLASS(klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass); obj_class->dispose = pidgin_debug_window_dispose; obj_class->finalize = pidgin_debug_window_finalize; gtk_widget_class_set_template_from_resource( widget_class, "/im/pidgin/Pidgin3/Debug/debug.ui" ); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, textview); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, buffer); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, tags.category); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, tags.filtered_invisible); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, tags.filtered_visible); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, tags.level[0]); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, tags.level[1]); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, tags.level[2]); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, tags.level[3]); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, tags.level[4]); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, tags.level[5]); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, tags.paused); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, filter); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, filterlevel); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, expression); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, tags.match); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, popover); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, popover_invert); gtk_widget_class_bind_template_child( widget_class, PidginDebugWindow, popover_highlight); gtk_widget_class_bind_template_callback(widget_class, save_cb); gtk_widget_class_bind_template_callback(widget_class, clear_cb); gtk_widget_class_bind_template_callback(widget_class, pause_cb); gtk_widget_class_bind_template_callback(widget_class, regex_changed_cb); gtk_widget_class_bind_template_callback(widget_class, regex_popup_cb); gtk_widget_class_bind_template_callback(widget_class, regex_key_released_cb); gtk_widget_class_bind_template_callback(widget_class, filter_level_changed_cb); } static void pidgin_debug_window_init(PidginDebugWindow *win) { GtkTextIter end; gtk_widget_init_template(GTK_WIDGET(win)); gtk_widget_set_parent(win->popover, win->filter); g_settings_bind(settings, "width", win, "default-width", G_SETTINGS_BIND_DEFAULT); g_settings_bind(settings, "height", win, "default-height", G_SETTINGS_BIND_DEFAULT); /* We purposely disable the toggle button here in case the regex setting * has an empty string. If it does not have an empty string, the change * signal will get called and make the toggle button sensitive. */ gtk_widget_set_sensitive(win->filter, FALSE); g_settings_bind(settings, "active", win->filter, "active", G_SETTINGS_BIND_DEFAULT); g_settings_bind(settings, "regex", win->expression, "text", G_SETTINGS_BIND_DEFAULT); /* This setting doesn't use binding because the uint-typed "selected" * property, and the enum-typed setting don't have a mapping. Since we have * to do some processing in these cases, just use manual setting instead. */ gtk_drop_down_set_selected(GTK_DROP_DOWN(win->filterlevel), g_settings_get_enum(settings, "filterlevel")); g_settings_bind(settings, "invert", win->popover_invert, "active", G_SETTINGS_BIND_DEFAULT); g_settings_bind(settings, "highlight", win->popover_highlight, "active", G_SETTINGS_BIND_DEFAULT); g_signal_connect(settings, "changed", G_CALLBACK(pidgin_debug_settings_changed_cb), win); /* The *start* and *end* marks bound the beginning and end of an insertion, used for filtering. The *end* mark is also used for auto-scrolling. */ gtk_text_buffer_get_end_iter(win->buffer, &end); win->start_mark = gtk_text_buffer_create_mark(win->buffer, "start", &end, TRUE); win->end_mark = gtk_text_buffer_create_mark(win->buffer, "end", &end, FALSE); /* Set active filter level in textview */ debug_window_set_filter_level(win, g_settings_get_enum(settings, "filterlevel")); clear_cb(NULL, win); } static void debug_visible_cb(GSettings *settings, char *key, G_GNUC_UNUSED gpointer data) { gboolean visible = g_settings_get_boolean(settings, key); g_signal_handler_block(settings, pref_callback_id); if(visible) { pidgin_debug_window_show(); } else { pidgin_debug_window_hide(); } g_signal_handler_unblock(settings, pref_callback_id); } static gboolean pidgin_debug_g_log_handler_cb(gpointer data) { PidginDebugMessage *message = data; GtkTextTag *level_tag = NULL; gchar *local_time = NULL; GtkTextIter end; gboolean scroll; if(debug_win == NULL || !g_settings_get_boolean(settings, "visible")) { /* The Debug Window may have been closed/disabled after the thread that * sent this message. */ g_date_time_unref(message->timestamp); g_free(message->domain); g_free(message->message); g_free(message); return FALSE; } scroll = view_near_bottom(debug_win); gtk_text_buffer_get_end_iter(debug_win->buffer, &end); gtk_text_buffer_move_mark(debug_win->buffer, debug_win->start_mark, &end); level_tag = debug_win->tags.level[message->level]; local_time = g_date_time_format(message->timestamp, "(%H:%M:%S) "); gtk_text_buffer_insert_with_tags( debug_win->buffer, &end, local_time, -1, level_tag, debug_win->paused ? debug_win->tags.paused : NULL, NULL); if(!purple_strempty(message->domain)) { gtk_text_buffer_insert_with_tags( debug_win->buffer, &end, message->domain, -1, level_tag, debug_win->tags.category, debug_win->paused ? debug_win->tags.paused : NULL, NULL); gtk_text_buffer_insert_with_tags( debug_win->buffer, &end, ": ", 2, level_tag, debug_win->tags.category, debug_win->paused ? debug_win->tags.paused : NULL, NULL); } gtk_text_buffer_insert_with_tags( debug_win->buffer, &end, message->message, -1, level_tag, debug_win->paused ? debug_win->tags.paused : NULL, NULL); gtk_text_buffer_insert_with_tags( debug_win->buffer, &end, "\n", 1, level_tag, debug_win->paused ? debug_win->tags.paused : NULL, NULL); if(g_settings_get_boolean(settings, "active") && debug_win->regex) { /* Filter out any new messages. */ GtkTextIter start; gtk_text_buffer_get_iter_at_mark(debug_win->buffer, &start, debug_win->start_mark); gtk_text_buffer_get_iter_at_mark(debug_win->buffer, &end, debug_win->end_mark); do_regex(debug_win, &start, &end); } if (scroll) { gtk_text_view_scroll_to_mark( GTK_TEXT_VIEW(debug_win->textview), debug_win->end_mark, 0, TRUE, 0, 1); } g_free(local_time); g_date_time_unref(message->timestamp); g_free(message->domain); g_free(message->message); g_free(message); return FALSE; } static GLogWriterOutput pidgin_debug_g_log_handler(GLogLevelFlags log_level, const GLogField *fields, gsize n_fields, G_GNUC_UNUSED gpointer user_data) { PidginDebugMessage *message = NULL; gsize i; if (debug_win == NULL) { if (debug_print_enabled) { return g_log_writer_default(log_level, fields, n_fields, user_data); } else { return G_LOG_WRITER_UNHANDLED; } } message = g_new0(PidginDebugMessage, 1); message->timestamp = g_date_time_new_now_local(); for (i = 0; i < n_fields; i++) { if (purple_strequal(fields[i].key, "GLIB_DOMAIN")) { message->domain = g_strdup(fields[i].value); } else if (purple_strequal(fields[i].key, "MESSAGE")) { message->message = g_strdup(fields[i].value); } } if((log_level & G_LOG_LEVEL_ERROR) != 0) { message->level = PURPLE_DEBUG_ERROR; } else if((log_level & G_LOG_LEVEL_CRITICAL) != 0) { message->level = PURPLE_DEBUG_FATAL; } else if((log_level & G_LOG_LEVEL_WARNING) != 0) { message->level = PURPLE_DEBUG_WARNING; } else if((log_level & G_LOG_LEVEL_MESSAGE) != 0) { message->level = PURPLE_DEBUG_INFO; } else if((log_level & G_LOG_LEVEL_INFO) != 0) { message->level = PURPLE_DEBUG_INFO; } else if((log_level & G_LOG_LEVEL_DEBUG) != 0) { message->level = PURPLE_DEBUG_MISC; } else { message->level = PURPLE_DEBUG_MISC; } g_timeout_add(0, pidgin_debug_g_log_handler_cb, message); if (debug_print_enabled) { return g_log_writer_default(log_level, fields, n_fields, user_data); } else { return G_LOG_WRITER_HANDLED; } } void pidgin_debug_window_show(void) { if (debug_win == NULL) { GApplication *application = NULL; PidginApplication *pidgin_application = NULL; GtkWindow *parent = NULL; application = g_application_get_default(); pidgin_application = PIDGIN_APPLICATION(application); parent = pidgin_application_get_active_window(pidgin_application); debug_win = PIDGIN_DEBUG_WINDOW( g_object_new(PIDGIN_TYPE_DEBUG_WINDOW, NULL)); gtk_window_set_transient_for(GTK_WINDOW(debug_win), parent); } gtk_window_present(GTK_WINDOW(debug_win)); g_settings_set_boolean(settings, "visible", TRUE); } void pidgin_debug_window_hide(void) { if (debug_win != NULL) { gtk_window_destroy(GTK_WINDOW(debug_win)); } } GSettings * pidgin_debug_get_settings(void) { return settings; } void pidgin_debug_init_handler(void) { g_log_set_writer_func(pidgin_debug_g_log_handler, NULL, NULL); } void pidgin_debug_set_print_enabled(gboolean enable) { debug_print_enabled = enable; } void pidgin_debug_init(void) { /* Debug window preferences. */ GSettingsBackend *backend = purple_core_get_settings_backend(); settings = g_settings_new_with_backend("im.pidgin.Pidgin3.Debug", backend); pref_callback_id = g_signal_connect(settings, "changed::visible", G_CALLBACK(debug_visible_cb), NULL); } void pidgin_debug_uninit(void) { g_clear_signal_handler(&pref_callback_id, settings); g_clear_object(&settings); }