diff --git a/tools/build/Makefile.feature b/tools/build/Makefile.feature index 99eb0ea09537..331f5cdfc34b 100644 --- a/tools/build/Makefile.feature +++ b/tools/build/Makefile.feature @@ -113,8 +113,7 @@ FEATURE_TESTS_EXTRA := \ compile-x32 \ cplus-demangle \ cxa-demangle \ - gtk2 \ - gtk2-infobar \ + gtk4 \ hello \ babeltrace2-ctf-writer \ libcapstone \ diff --git a/tools/build/feature/Makefile b/tools/build/feature/Makefile index 7d165018116a..01c48e6ef021 100644 --- a/tools/build/feature/Makefile +++ b/tools/build/feature/Makefile @@ -11,8 +11,7 @@ FILES= \ test-eventfd.bin \ test-fortify-source.bin \ test-glibc.bin \ - test-gtk2.bin \ - test-gtk2-infobar.bin \ + test-gtk4.bin \ test-hello.bin \ test-libbfd.bin \ test-libbfd-threadsafe.bin \ @@ -240,11 +239,8 @@ $(OUTPUT)test-libcpupower.bin: $(OUTPUT)test-libtracefs.bin: $(BUILD) $(shell $(PKG_CONFIG) --cflags libtracefs 2>/dev/null) -ltracefs -$(OUTPUT)test-gtk2.bin: - $(BUILD) $(shell $(PKG_CONFIG) --libs --cflags gtk+-2.0 2>/dev/null) -Wno-deprecated-declarations - -$(OUTPUT)test-gtk2-infobar.bin: - $(BUILD) $(shell $(PKG_CONFIG) --libs --cflags gtk+-2.0 2>/dev/null) +$(OUTPUT)test-gtk4.bin: + $(BUILD) $(shell $(PKG_CONFIG) --libs --cflags gtk4 2>/dev/null) grep-libs = $(filter -l%,$(1)) strip-libs = $(filter-out -l%,$(1)) diff --git a/tools/build/feature/test-gtk2-infobar.c b/tools/build/feature/test-gtk2-infobar.c deleted file mode 100644 index b1b716dd5733..000000000000 --- a/tools/build/feature/test-gtk2-infobar.c +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -#pragma GCC diagnostic ignored "-Wstrict-prototypes" -#include -#pragma GCC diagnostic error "-Wstrict-prototypes" - -int main(int argc, char *argv[]) -{ - gtk_init(&argc, &argv); - gtk_info_bar_new(); - - return 0; -} diff --git a/tools/build/feature/test-gtk2.c b/tools/build/feature/test-gtk4.c similarity index 76% rename from tools/build/feature/test-gtk2.c rename to tools/build/feature/test-gtk4.c index 2aaf4bfc2055..b9520e7408b9 100644 --- a/tools/build/feature/test-gtk2.c +++ b/tools/build/feature/test-gtk4.c @@ -3,9 +3,9 @@ #include #pragma GCC diagnostic error "-Wstrict-prototypes" -int main(int argc, char *argv[]) +int main(void) { - gtk_init(&argc, &argv); + gtk_init(); return 0; } diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index ae68ca402d0b..1a4706329c6c 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -354,7 +354,7 @@ OPTIONS requires a tty, if one is not present, as when piping to other commands, the stdio interface is used. ---gtk:: Use the GTK2 interface. +--gtk:: Use the GTK4 interface. --weights:: Show or hide (with `--no-weights`) weight columns in annotation output. By default, weight columns are shown when samples contain non-zero weights. diff --git a/tools/perf/Makefile b/tools/perf/Makefile index 5b713837eede..56014106479a 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -106,7 +106,7 @@ clean: # make -C tools/perf -f tests/make # build-test: - @$(MAKE) SHUF=1 -f tests/make REUSE_FEATURES_DUMP=1 MK=Makefile SET_PARALLEL=1 --no-print-directory tarpkg make_static make_with_gtk2 out + @$(MAKE) SHUF=1 -f tests/make REUSE_FEATURES_DUMP=1 MK=Makefile SET_PARALLEL=1 --no-print-directory tarpkg make_static make_with_gtk4 out build-test-tarball: @$(MAKE) -f tests/make REUSE_FEATURES_DUMP=1 MK=Makefile SET_PARALLEL=1 --no-print-directory out diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config index 4d5993da9f94..3e59e2b7eaec 100644 --- a/tools/perf/Makefile.config +++ b/tools/perf/Makefile.config @@ -774,20 +774,16 @@ ifndef NO_SLANG endif endif -ifdef GTK2 - FLAGS_GTK2=$(CFLAGS) $(LDFLAGS) $(EXTLIBS) $(shell $(PKG_CONFIG) --libs --cflags gtk+-2.0 2>/dev/null) - $(call feature_check,gtk2) - ifneq ($(feature-gtk2), 1) - $(warning GTK2 not found, disables GTK2 support. Please install gtk2-devel or libgtk2.0-dev) - NO_GTK2 := 1 +ifdef GTK4 + FLAGS_GTK4=$(CFLAGS) $(LDFLAGS) $(EXTLIBS) $(shell $(PKG_CONFIG) --libs --cflags gtk4 2>/dev/null) + $(call feature_check,gtk4) + ifneq ($(feature-gtk4), 1) + $(warning GTK4 not found, disables GTK4 support. Please install gtk4-devel or libgtk-4-dev) + NO_GTK4 := 1 else - $(call feature_check,gtk2-infobar) - ifeq ($(feature-gtk2-infobar), 1) - GTK_CFLAGS := -DHAVE_GTK_INFO_BAR_SUPPORT - endif - CFLAGS += -DHAVE_GTK2_SUPPORT - GTK_CFLAGS += $(shell $(PKG_CONFIG) --cflags gtk+-2.0 2>/dev/null) - GTK_LIBS := $(shell $(PKG_CONFIG) --libs gtk+-2.0 2>/dev/null) + CFLAGS += -DHAVE_GTK4_SUPPORT + GTK_CFLAGS += $(shell $(PKG_CONFIG) --cflags gtk4 2>/dev/null) + GTK_LIBS := $(shell $(PKG_CONFIG) --libs gtk4 2>/dev/null) EXTLIBS += -ldl endif endif diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf index 29cfd44c427f..2438b40eaaec 100644 --- a/tools/perf/Makefile.perf +++ b/tools/perf/Makefile.perf @@ -49,7 +49,7 @@ include ../scripts/utilities.mak # # Define NO_SLANG if you do not want TUI support. # -# Define GTK2 if you want GTK+ GUI support. +# Define GTK4 if you want GTK+ GUI support. # # Define NO_DEMANGLE if you do not want C++ symbol demangling. # @@ -473,7 +473,7 @@ ifneq ($(OUTPUT),) CFLAGS += -I$(OUTPUT) endif -ifdef GTK2 +ifdef GTK4 ALL_PROGRAMS += $(OUTPUT)libperf-gtk.so GTK_IN := $(OUTPUT)gtk-in.o endif @@ -811,7 +811,7 @@ check: prepare ### Installation rules -ifdef GTK2 +ifdef GTK4 install-gtk: $(OUTPUT)libperf-gtk.so $(call QUIET_INSTALL, 'GTK UI') \ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(libdir_SQ)'; \ diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index 4638e6fdc39b..850fc72fa75f 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -61,7 +61,7 @@ struct perf_annotate { bool use_tui; #endif bool use_stdio, use_stdio2; -#ifdef HAVE_GTK2_SUPPORT +#ifdef HAVE_GTK4_SUPPORT bool use_gtk; #endif bool skip_missing; @@ -721,7 +721,7 @@ int cmd_annotate(int argc, const char **argv) OPT_BOOLEAN('q', "quiet", &quiet, "do now show any warnings or messages"), OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, "dump raw trace in ASCII"), -#ifdef HAVE_GTK2_SUPPORT +#ifdef HAVE_GTK4_SUPPORT OPT_BOOLEAN(0, "gtk", &annotate.use_gtk, "Use the GTK interface"), #endif #ifdef HAVE_SLANG_SUPPORT @@ -840,7 +840,7 @@ int cmd_annotate(int argc, const char **argv) if (annotate_check_args() < 0) return -EINVAL; -#ifdef HAVE_GTK2_SUPPORT +#ifdef HAVE_GTK4_SUPPORT if (symbol_conf.show_nr_samples && annotate.use_gtk) { pr_err("--show-nr-samples is not available in --gtk mode at this time\n"); return ret; @@ -910,7 +910,7 @@ int cmd_annotate(int argc, const char **argv) else if (annotate.use_tui) use_browser = 1; #endif -#ifdef HAVE_GTK2_SUPPORT +#ifdef HAVE_GTK4_SUPPORT else if (annotate.use_gtk) use_browser = 2; #endif diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index b280ff9ff45e..279e61c2366c 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -83,7 +83,7 @@ struct report { #ifdef HAVE_SLANG_SUPPORT bool use_tui; #endif -#ifdef HAVE_GTK2_SUPPORT +#ifdef HAVE_GTK4_SUPPORT bool use_gtk; #endif bool use_stdio; @@ -1360,8 +1360,8 @@ int cmd_report(int argc, const char **argv) #ifdef HAVE_SLANG_SUPPORT OPT_BOOLEAN(0, "tui", &report.use_tui, "Use the TUI interface"), #endif -#ifdef HAVE_GTK2_SUPPORT - OPT_BOOLEAN(0, "gtk", &report.use_gtk, "Use the GTK2 interface"), +#ifdef HAVE_GTK4_SUPPORT + OPT_BOOLEAN(0, "gtk", &report.use_gtk, "Use the GTK4 interface"), #endif OPT_BOOLEAN(0, "stdio", &report.use_stdio, "Use the stdio interface"), @@ -1714,7 +1714,7 @@ int cmd_report(int argc, const char **argv) else if (report.use_tui) use_browser = 1; #endif -#ifdef HAVE_GTK2_SUPPORT +#ifdef HAVE_GTK4_SUPPORT else if (report.use_gtk) use_browser = 2; #endif diff --git a/tools/perf/scripts/install-build-deps.sh b/tools/perf/scripts/install-build-deps.sh index d003e7fab2be..a601a5260c17 100755 --- a/tools/perf/scripts/install-build-deps.sh +++ b/tools/perf/scripts/install-build-deps.sh @@ -199,8 +199,8 @@ fedora_pkg_for() { # opt-in features, which a default build does not enable: the libbfd # disassembler family (libbfd, libbfd-threadsafe, libbfd-liberty, # disassembler-*, cplus-demangle), only linked on BUILD_NONDISTRO - # builds and deprecated in favor of capstone, GTK2, LIBPERL and - # LIBUNWIND support (ifdef GTK2 / ifdef LIBPERL / LIBUNWIND=1), + # builds and deprecated in favor of capstone, GTK4, LIBPERL and + # LIBUNWIND support (ifdef GTK4 / ifdef LIBPERL / LIBUNWIND=1), # and CoreSight (ifdef CORESIGHT), are deliberately not mapped. # libaio is not mapped either: its # test uses the POSIX AIO API (aio.h, aio_*, -lrt), provided by diff --git a/tools/perf/tests/make b/tools/perf/tests/make index d2c2f526e1db..202ab5501916 100644 --- a/tools/perf/tests/make +++ b/tools/perf/tests/make @@ -95,7 +95,7 @@ make_no_babeltrace2 := NO_BABELTRACE2=1 make_with_coresight := CORESIGHT=1 make_no_sdt := NO_SDT=1 make_no_libpfm4 := NO_LIBPFM4=1 -make_with_gtk2 := GTK2=1 +make_with_gtk4 := GTK4=1 make_refcnt_check := EXTRA_CFLAGS="-DREFCNT_CHECKING=1" make_tags := tags make_cscope := cscope @@ -318,7 +318,7 @@ $(run): $(call test,$@) && \ rm -rf $@ $$TMP_DEST || (cat $@ ; false) -make_with_gtk2: +make_with_gtk4: $(call clean) @TMP_DEST=$$(mktemp -d); \ cmd="cd $(PERF) && $(MAKE_F) $($@) $(PARALLEL_OPT) $(O_OPT) DESTDIR=$$TMP_DEST"; \ diff --git a/tools/perf/ui/gtk/annotate.c b/tools/perf/ui/gtk/annotate.c index 8920e298420a..094dfa045b77 100644 --- a/tools/perf/ui/gtk/annotate.c +++ b/tools/perf/ui/gtk/annotate.c @@ -11,6 +11,7 @@ #include "ui/helpline.h" #include #include +#include enum { ANN_COL__PERCENT, @@ -161,7 +162,7 @@ static int perf_gtk__annotate_symbol(GtkWidget *window, struct map_symbol *ms, gtk_list_store_set(store, &iter, ANN_COL__LINE, s, -1); } - gtk_container_add(GTK_CONTAINER(window), view); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(window), view); list_for_each_entry_safe(pos, n, ¬es->src->source, al.node) { list_del_init(&pos->al.node); @@ -205,40 +206,34 @@ static int symbol__gtk_annotate(struct map_symbol *ms, struct evsel *evsel, GtkWidget *infobar; GtkWidget *statbar; - signal(SIGSEGV, perf_gtk__signal); - signal(SIGFPE, perf_gtk__signal); - signal(SIGINT, perf_gtk__signal); - signal(SIGQUIT, perf_gtk__signal); - signal(SIGTERM, perf_gtk__signal); + signal(SIGSEGV, perf_gtk__fatal_signal); + signal(SIGFPE, perf_gtk__fatal_signal); + perf_gtk__install_quit_signals(); - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + window = gtk_window_new(); gtk_window_set_title(GTK_WINDOW(window), "perf annotate"); - g_signal_connect(window, "delete_event", gtk_main_quit, NULL); - pgctx = perf_gtk__activate_context(window); if (!pgctx) return -1; - vbox = gtk_vbox_new(FALSE, 0); + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); notebook = gtk_notebook_new(); pgctx->notebook = notebook; - gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); + gtk_widget_set_vexpand(notebook, TRUE); + gtk_box_append(GTK_BOX(vbox), notebook); infobar = perf_gtk__setup_info_bar(); - if (infobar) { - gtk_box_pack_start(GTK_BOX(vbox), infobar, - FALSE, FALSE, 0); - } + gtk_box_append(GTK_BOX(vbox), infobar); statbar = perf_gtk__setup_statusbar(); - gtk_box_pack_start(GTK_BOX(vbox), statbar, FALSE, FALSE, 0); + gtk_box_append(GTK_BOX(vbox), statbar); - gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_window_set_child(GTK_WINDOW(window), vbox); } - scrolled_window = gtk_scrolled_window_new(NULL, NULL); + scrolled_window = gtk_scrolled_window_new(); tab_label = gtk_label_new(sym->name); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), @@ -267,12 +262,11 @@ void perf_gtk__show_annotations(void) return; window = pgctx->main_window; - gtk_widget_show_all(window); perf_gtk__resize_window(window); - gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_widget_set_visible(window, TRUE); - gtk_main(); + perf_gtk__run_main_loop(window); perf_gtk__deactivate_context(&pgctx); } diff --git a/tools/perf/ui/gtk/browser.c b/tools/perf/ui/gtk/browser.c index d2dadf3873fb..98f1d6bfcb24 100644 --- a/tools/perf/ui/gtk/browser.c +++ b/tools/perf/ui/gtk/browser.c @@ -5,32 +5,92 @@ #include "../hist.h" #include "../helpline.h" +#include #include -void perf_gtk__signal(int sig) +/* + * SIGINT/SIGQUIT/SIGTERM are asynchronous: deferring the actual exit to a + * GLib source dispatched from the main loop means it always runs on the + * main thread, serialized with everything else the main loop does + * (including perf_gtk__error()'s updates to perf_gtk__error_loops), instead + * of racing them from arbitrary signal-handler context. + */ +static gboolean perf_gtk__quit_signal(gpointer data __maybe_unused) { perf_gtk__exit(false); + return G_SOURCE_REMOVE; +} + +void perf_gtk__install_quit_signals(void) +{ + g_unix_signal_add(SIGINT, perf_gtk__quit_signal, NULL); + g_unix_signal_add(SIGQUIT, perf_gtk__quit_signal, NULL); + g_unix_signal_add(SIGTERM, perf_gtk__quit_signal, NULL); +} + +/* + * SIGSEGV/SIGFPE are synchronous faults: there's no "later" to defer to, + * and no safe way to run GTK/GLib code (or anything else non-async-signal- + * safe) from the faulting context. Report and let the default disposition + * (core dump) happen instead of trying to tear down GTK state here. + */ +void perf_gtk__fatal_signal(int sig) +{ psignal(sig, "perf"); + signal(sig, SIG_DFL); + raise(sig); } void perf_gtk__resize_window(GtkWidget *window) { GdkRectangle rect; - GdkScreen *screen; - int monitor; + GdkMonitor *monitor; + GdkDisplay *display; + GListModel *monitors; int height; int width; - screen = gtk_widget_get_screen(window); + display = gtk_widget_get_display(window); + monitors = gdk_display_get_monitors(display); + monitor = g_list_model_get_item(monitors, 0); + if (!monitor) { + gtk_window_set_default_size(GTK_WINDOW(window), 800, 600); + return; + } - monitor = gdk_screen_get_monitor_at_window(screen, window->window); - - gdk_screen_get_monitor_geometry(screen, monitor, &rect); + gdk_monitor_get_geometry(monitor, &rect); + g_object_unref(monitor); width = rect.width * 3 / 4; height = rect.height * 3 / 4; - gtk_window_resize(GTK_WINDOW(window), width, height); + gtk_window_set_default_size(GTK_WINDOW(window), width, height); +} + +static GMainLoop *perf_gtk__main_loop; + +void perf_gtk__quit_main_loop(void) +{ + if (perf_gtk__main_loop) + g_main_loop_quit(perf_gtk__main_loop); +} + +static gboolean perf_gtk__close_request(GtkWidget *widget __maybe_unused, + gpointer data __maybe_unused) +{ + perf_gtk__quit_main_loop(); + + return FALSE; +} + +void perf_gtk__run_main_loop(GtkWidget *window) +{ + g_signal_connect(window, "close-request", + G_CALLBACK(perf_gtk__close_request), NULL); + + perf_gtk__main_loop = g_main_loop_new(NULL, FALSE); + g_main_loop_run(perf_gtk__main_loop); + g_clear_pointer(&perf_gtk__main_loop, g_main_loop_unref); } const char *perf_gtk__get_percent_color(double percent) @@ -42,33 +102,35 @@ const char *perf_gtk__get_percent_color(double percent) return NULL; } -#ifdef HAVE_GTK_INFO_BAR_SUPPORT +static void perf_gtk__hide_widget(GtkWidget *widget, gint response_id __maybe_unused, + gpointer data __maybe_unused) +{ + gtk_widget_set_visible(widget, FALSE); +} + GtkWidget *perf_gtk__setup_info_bar(void) { GtkWidget *info_bar; GtkWidget *label; - GtkWidget *content_area; info_bar = gtk_info_bar_new(); - gtk_widget_set_no_show_all(info_bar, TRUE); + gtk_widget_set_visible(info_bar, FALSE); label = gtk_label_new(""); - gtk_widget_show(label); + gtk_widget_set_visible(label, TRUE); - content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_bar)); - gtk_container_add(GTK_CONTAINER(content_area), label); + gtk_info_bar_add_child(GTK_INFO_BAR(info_bar), label); - gtk_info_bar_add_button(GTK_INFO_BAR(info_bar), GTK_STOCK_OK, + gtk_info_bar_add_button(GTK_INFO_BAR(info_bar), "_OK", GTK_RESPONSE_OK); g_signal_connect(info_bar, "response", - G_CALLBACK(gtk_widget_hide), NULL); + G_CALLBACK(perf_gtk__hide_widget), NULL); pgctx->info_bar = info_bar; pgctx->message_label = label; return info_bar; } -#endif GtkWidget *perf_gtk__setup_statusbar(void) { diff --git a/tools/perf/ui/gtk/gtk.h b/tools/perf/ui/gtk/gtk.h index a2b497f03fd6..6b37fd9943c9 100644 --- a/tools/perf/ui/gtk/gtk.h +++ b/tools/perf/ui/gtk/gtk.h @@ -13,10 +13,8 @@ struct perf_gtk_context { GtkWidget *main_window; GtkWidget *notebook; -#ifdef HAVE_GTK_INFO_BAR_SUPPORT GtkWidget *info_bar; GtkWidget *message_label; -#endif GtkWidget *statbar; guint statbar_ctx_id; }; @@ -38,19 +36,15 @@ void perf_gtk__init_helpline(void); void gtk_ui_progress__init(void); void perf_gtk__init_hpp(void); -void perf_gtk__signal(int sig); +void perf_gtk__install_quit_signals(void); +void perf_gtk__fatal_signal(int sig); void perf_gtk__resize_window(GtkWidget *window); +void perf_gtk__run_main_loop(GtkWidget *window); +void perf_gtk__quit_main_loop(void); +void perf_gtk__quit_error_dialog(void); const char *perf_gtk__get_percent_color(double percent); GtkWidget *perf_gtk__setup_statusbar(void); - -#ifdef HAVE_GTK_INFO_BAR_SUPPORT GtkWidget *perf_gtk__setup_info_bar(void); -#else -static inline GtkWidget *perf_gtk__setup_info_bar(void) -{ - return NULL; -} -#endif struct evsel; struct evlist; diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c index bae21f336ae6..716dcf02bd0e 100644 --- a/tools/perf/ui/gtk/hists.c +++ b/tools/perf/ui/gtk/hists.c @@ -8,6 +8,8 @@ #include "../helpline.h" #include "../string2.h" #include +#include +#include #include #include @@ -395,11 +397,9 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, } } - gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); - g_signal_connect(view, "row-activated", G_CALLBACK(on_row_activated), NULL); - gtk_container_add(GTK_CONTAINER(window), view); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(window), view); } static void perf_gtk__add_hierarchy_entries(struct hists *hists, @@ -583,11 +583,9 @@ static void perf_gtk__show_hierarchy(GtkWidget *window, struct hists *hists, perf_gtk__add_hierarchy_entries(hists, &hists->entries, store, NULL, &hpp, min_pcnt); - gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); - g_signal_connect(view, "row-activated", G_CALLBACK(on_row_activated), NULL); - gtk_container_add(GTK_CONTAINER(window), view); + gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(window), view); } int evlist__gtk_browse_hists(struct evlist *evlist, const char *help, @@ -600,36 +598,32 @@ int evlist__gtk_browse_hists(struct evlist *evlist, const char *help, GtkWidget *statbar; GtkWidget *window; - signal(SIGSEGV, perf_gtk__signal); - signal(SIGFPE, perf_gtk__signal); - signal(SIGINT, perf_gtk__signal); - signal(SIGQUIT, perf_gtk__signal); - signal(SIGTERM, perf_gtk__signal); + signal(SIGSEGV, perf_gtk__fatal_signal); + signal(SIGFPE, perf_gtk__fatal_signal); + perf_gtk__install_quit_signals(); - window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + window = gtk_window_new(); gtk_window_set_title(GTK_WINDOW(window), "perf report"); - g_signal_connect(window, "delete_event", gtk_main_quit, NULL); - pgctx = perf_gtk__activate_context(window); if (!pgctx) return -1; - vbox = gtk_vbox_new(FALSE, 0); + vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); notebook = gtk_notebook_new(); - gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0); + gtk_widget_set_vexpand(notebook, TRUE); + gtk_box_append(GTK_BOX(vbox), notebook); info_bar = perf_gtk__setup_info_bar(); - if (info_bar) - gtk_box_pack_start(GTK_BOX(vbox), info_bar, FALSE, FALSE, 0); + gtk_box_append(GTK_BOX(vbox), info_bar); statbar = perf_gtk__setup_statusbar(); - gtk_box_pack_start(GTK_BOX(vbox), statbar, FALSE, FALSE, 0); + gtk_box_append(GTK_BOX(vbox), statbar); - gtk_container_add(GTK_CONTAINER(window), vbox); + gtk_window_set_child(GTK_WINDOW(window), vbox); evlist__for_each_entry(evlist, pos) { struct hists *hists = evsel__hists(pos); @@ -649,7 +643,7 @@ int evlist__gtk_browse_hists(struct evlist *evlist, const char *help, } } - scrolled_window = gtk_scrolled_window_new(NULL, NULL); + scrolled_window = gtk_scrolled_window_new(); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window), GTK_POLICY_AUTOMATIC, @@ -665,15 +659,12 @@ int evlist__gtk_browse_hists(struct evlist *evlist, const char *help, gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window, tab_label); } - gtk_widget_show_all(window); - perf_gtk__resize_window(window); - - gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER); + gtk_widget_set_visible(window, TRUE); ui_helpline__push(help); - gtk_main(); + perf_gtk__run_main_loop(window); perf_gtk__deactivate_context(&pgctx); diff --git a/tools/perf/ui/gtk/progress.c b/tools/perf/ui/gtk/progress.c index eea6fcde518a..770f9251b54b 100644 --- a/tools/perf/ui/gtk/progress.c +++ b/tools/perf/ui/gtk/progress.c @@ -1,49 +1,65 @@ // SPDX-License-Identifier: GPL-2.0 #include +#include #include "gtk.h" #include "../progress.h" +#include static GtkWidget *dialog; static GtkWidget *progress; +static void gtk_ui_progress__destroyed(GtkWidget *widget __maybe_unused, + gpointer data __maybe_unused) +{ + dialog = NULL; + progress = NULL; +} + static void gtk_ui_progress__update(struct ui_progress *p) { double fraction = p->total ? 1.0 * p->curr / p->total : 0.0; char buf[1024]; if (dialog == NULL) { - GtkWidget *vbox = gtk_vbox_new(TRUE, 5); + GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); GtkWidget *label = gtk_label_new(p->title); - dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL); + dialog = gtk_window_new(); progress = gtk_progress_bar_new(); - gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, FALSE, 3); - gtk_box_pack_start(GTK_BOX(vbox), progress, TRUE, TRUE, 3); + gtk_widget_set_vexpand(label, TRUE); + gtk_box_append(GTK_BOX(vbox), label); + gtk_widget_set_vexpand(progress, TRUE); + gtk_box_append(GTK_BOX(vbox), progress); - gtk_container_add(GTK_CONTAINER(dialog), vbox); + gtk_window_set_child(GTK_WINDOW(dialog), vbox); + + g_signal_connect(dialog, "destroy", + G_CALLBACK(gtk_ui_progress__destroyed), NULL); gtk_window_set_title(GTK_WINDOW(dialog), "perf"); - gtk_window_resize(GTK_WINDOW(dialog), 300, 80); - gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER); + gtk_window_set_default_size(GTK_WINDOW(dialog), 300, 80); - gtk_widget_show_all(dialog); + gtk_widget_set_visible(dialog, TRUE); } gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction); snprintf(buf, sizeof(buf), "%"PRIu64" / %"PRIu64, p->curr, p->total); gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress), buf); - /* we didn't call gtk_main yet, so do it manually */ - while (gtk_events_pending()) - gtk_main_iteration(); + /* we didn't start a main loop yet, so pump events manually */ + while (g_main_context_pending(NULL)) + g_main_context_iteration(NULL, FALSE); } static void gtk_ui_progress__finish(void) { + if (dialog == NULL) + return; + /* this will also destroy all of its children */ - gtk_widget_destroy(dialog); + gtk_window_destroy(GTK_WINDOW(dialog)); dialog = NULL; } diff --git a/tools/perf/ui/gtk/setup.c b/tools/perf/ui/gtk/setup.c index f5eee4d66873..9b44f3719747 100644 --- a/tools/perf/ui/gtk/setup.c +++ b/tools/perf/ui/gtk/setup.c @@ -12,7 +12,7 @@ int perf_gtk__init(void) gtk_ui_progress__init(); perf_gtk__init_hpp(); - return gtk_init_check(NULL, NULL) ? 0 : -1; + return gtk_init_check() ? 0 : -1; } void perf_gtk__exit(bool wait_for_ok __maybe_unused) @@ -20,5 +20,6 @@ void perf_gtk__exit(bool wait_for_ok __maybe_unused) if (!perf_gtk__is_active_context(pgctx)) return; perf_error__unregister(&perf_gtk_eops); - gtk_main_quit(); + perf_gtk__quit_error_dialog(); + perf_gtk__quit_main_loop(); } diff --git a/tools/perf/ui/gtk/util.c b/tools/perf/ui/gtk/util.c index c47f5c387838..5823ff0d7c2a 100644 --- a/tools/perf/ui/gtk/util.c +++ b/tools/perf/ui/gtk/util.c @@ -2,8 +2,10 @@ #include "../util.h" #include "gtk.h" +#include +#include #include -#include +#include #include struct perf_gtk_context *pgctx; @@ -28,86 +30,103 @@ int perf_gtk__deactivate_context(struct perf_gtk_context **ctx) return 0; } +/* + * perf_gtk__error() can be called re-entrantly, since the dialog isn't + * modal and its nested loop still pumps events for the main window. + * Track every currently running loop instead of a single pointer, so a + * nested call can't clobber an outer call's loop and leak it. + */ +static GSList *perf_gtk__error_loops; + +static void perf_gtk__quit_loop(gpointer data, gpointer user_data __maybe_unused) +{ + g_main_loop_quit(data); +} + +void perf_gtk__quit_error_dialog(void) +{ + g_slist_foreach(perf_gtk__error_loops, perf_gtk__quit_loop, NULL); +} + +static void perf_gtk__dialog_response(GtkDialog *dialog, + gint response_id __maybe_unused, + gpointer data __maybe_unused) +{ + gtk_window_destroy(GTK_WINDOW(dialog)); +} + static int perf_gtk__error(const char *format, va_list args) { char *msg; GtkWidget *dialog; + GMainLoop *loop; + va_list args_copy; + va_copy(args_copy, args); if (!perf_gtk__is_active_context(pgctx) || - vasprintf(&msg, format, args) < 0) { + vasprintf(&msg, format, args_copy) < 0) { + va_end(args_copy); fprintf(stderr, "Error:\n"); vfprintf(stderr, format, args); fprintf(stderr, "\n"); return -1; } + va_end(args_copy); dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(pgctx->main_window), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "Error\n\n%s", msg); - gtk_dialog_run(GTK_DIALOG(dialog)); - gtk_widget_destroy(dialog); + /* + * "response" only fires when a button is clicked; DESTROY_WITH_PARENT + * destroys the dialog directly without it. Quit from "destroy" + * instead, which fires either way, so the nested loop below can't + * outlive the dialog and hang. + */ + loop = g_main_loop_new(NULL, FALSE); + perf_gtk__error_loops = g_slist_prepend(perf_gtk__error_loops, loop); + g_signal_connect(dialog, "response", + G_CALLBACK(perf_gtk__dialog_response), NULL); + g_signal_connect_swapped(dialog, "destroy", + G_CALLBACK(g_main_loop_quit), loop); + + gtk_widget_set_visible(dialog, TRUE); + g_main_loop_run(loop); + perf_gtk__error_loops = g_slist_remove(perf_gtk__error_loops, loop); + g_main_loop_unref(loop); + free(msg); return 0; } -#ifdef HAVE_GTK_INFO_BAR_SUPPORT static int perf_gtk__warning_info_bar(const char *format, va_list args) { char *msg; + va_list args_copy; + va_copy(args_copy, args); if (!perf_gtk__is_active_context(pgctx) || - vasprintf(&msg, format, args) < 0) { + vasprintf(&msg, format, args_copy) < 0) { + va_end(args_copy); fprintf(stderr, "Warning:\n"); vfprintf(stderr, format, args); fprintf(stderr, "\n"); return -1; } + va_end(args_copy); gtk_label_set_text(GTK_LABEL(pgctx->message_label), msg); gtk_info_bar_set_message_type(GTK_INFO_BAR(pgctx->info_bar), GTK_MESSAGE_WARNING); - gtk_widget_show(pgctx->info_bar); + gtk_widget_set_visible(pgctx->info_bar, TRUE); free(msg); return 0; } -#else -static int perf_gtk__warning_statusbar(const char *format, va_list args) -{ - char *msg, *p; - - if (!perf_gtk__is_active_context(pgctx) || - vasprintf(&msg, format, args) < 0) { - fprintf(stderr, "Warning:\n"); - vfprintf(stderr, format, args); - fprintf(stderr, "\n"); - return -1; - } - - gtk_statusbar_pop(GTK_STATUSBAR(pgctx->statbar), - pgctx->statbar_ctx_id); - - /* Only first line can be displayed */ - p = strchr(msg, '\n'); - if (p) - *p = '\0'; - - gtk_statusbar_push(GTK_STATUSBAR(pgctx->statbar), - pgctx->statbar_ctx_id, msg); - - free(msg); - return 0; -} -#endif struct perf_error_ops perf_gtk_eops = { .error = perf_gtk__error, -#ifdef HAVE_GTK_INFO_BAR_SUPPORT .warning = perf_gtk__warning_info_bar, -#else - .warning = perf_gtk__warning_statusbar, -#endif }; diff --git a/tools/perf/ui/setup.c b/tools/perf/ui/setup.c index ff800047e697..d887346c7a63 100644 --- a/tools/perf/ui/setup.c +++ b/tools/perf/ui/setup.c @@ -14,7 +14,7 @@ int use_browser = -1; #define PERF_GTK_DSO "libperf-gtk.so" -#ifdef HAVE_GTK2_SUPPORT +#ifdef HAVE_GTK4_SUPPORT static int setup_gtk_browser(void) {