如何通过GtkGestureLongPress实现GtkWindow拖动?终端卡顿排查
基于VTE的终端窗口长按拖动问题排查
问题现象
在基于VTE的终端应用中,通过GtkGestureLongPress实现窗口拖动功能时,执行ping 127.0.0.1命令后长按拖动窗口,终端会停止更新(ping输出暂停),且无法退出长按状态,必须点击窗口/终端才能恢复输出。
原始代码
#include <gtk/gtk.h> #include <vte/vte.h> GtkWidget *window; GtkWidget *terminal; static void child_ready(VteTerminal *terminal, GPid pid, GError *error, gpointer user_data) { if (!terminal) return; if (pid == -1) gtk_window_destroy((GtkWindow *)window); } static void run_terminal() { /* Start a new shell */ gchar **envp = g_get_environ(); //gchar **command = (gchar *[]){g_strdup(g_environ_getenv(envp, "SHELL")), NULL}; gchar **command = (gchar *[]){g_strdup(g_environ_getenv(envp, "SHELL")), "-c", "/bin/bash", NULL}; g_strfreev(envp); /* Spawn terminal asynchronously */ vte_terminal_spawn_async(VTE_TERMINAL(terminal), VTE_PTY_DEFAULT, /* pty flag */ NULL, /* working directory */ command, /* argv */ NULL, /* environment variables */ G_SPAWN_DEFAULT, /* spawn flag */ NULL, /* child setup function */ NULL, /* child setup data */ NULL, /* child setup data destroy */ -1, /* timeout */ NULL, /* cancellable */ child_ready, /* async callback */ NULL); /* callback data */ } void drag_on_long_press (GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer user_data) { GdkSurface *surface; GdkEvent *event; guint btn; const graphene_point_t *point = graphene_point_init (graphene_point_alloc (), x, y); graphene_point_t *point1 = graphene_point_init (graphene_point_alloc (), x, y); GdkEventSequence *sequence = gtk_gesture_get_last_updated_sequence ((GtkGesture *)gesture); event = gtk_gesture_get_last_event ((GtkGesture *)gesture, sequence); GtkWidget *widget = gtk_event_controller_get_widget ((GtkEventController *)gesture); GdkDevice *device = gtk_gesture_get_device ((GtkGesture *)gesture); guint32 timestamp = gdk_event_get_time (event); if (gdk_event_get_event_type (event) == GDK_BUTTON_PRESS){ btn = gdk_button_event_get_button (event); if (!gtk_widget_compute_point (widget, GTK_WIDGET (gtk_widget_get_root (widget)), point, point1)) return; surface = gtk_native_get_surface (gtk_widget_get_native (widget)); gdk_toplevel_begin_move(GDK_TOPLEVEL (surface), device, btn, x+12, y+12, timestamp); gtk_event_controller_reset ((GtkEventController *)gesture); }else{ btn = 0; } graphene_point_free(point1); free((void*)point); } static void activate (GtkApplication *app, gpointer user_data) { window = gtk_application_window_new (app); gtk_window_set_title (GTK_WINDOW (window), "Window"); gtk_window_set_default_size (GTK_WINDOW (window), 660, 300); GtkGesture* ges = gtk_gesture_long_press_new(); gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(ges), GDK_BUTTON_PRIMARY); gtk_event_controller_set_propagation_phase((GtkEventController *)ges, GTK_PHASE_CAPTURE); gtk_widget_add_controller (window, GTK_EVENT_CONTROLLER (ges)); g_signal_connect (ges, "pressed", G_CALLBACK (drag_on_long_press), NULL); GtkWidget *scrollview; scrollview = gtk_scrolled_window_new(); gtk_window_set_child ((GtkWindow *)window, scrollview); terminal = vte_terminal_new(); gtk_scrolled_window_set_child ((GtkScrolledWindow *)scrollview, terminal); run_terminal(); gtk_window_present ((GtkWindow *)window); } int main (int argc, char **argv) { GtkApplication *app; int status; app = gtk_application_new ("org.gtk.example", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect (app, "activate", G_CALLBACK (activate), NULL); status = g_application_run (G_APPLICATION (app), argc, argv); g_object_unref (app); return status; }
问题根源
- 手势状态未正确收尾:触发拖动后,手势的事件序列未标记为完成,导致后续鼠标事件被手势拦截,终端无法接收更新信号。
- 坐标计算错误:调用
gdk_toplevel_begin_move时使用窗口局部坐标而非屏幕全局坐标,导致拖动逻辑异常,阻塞事件循环。 - 事件传播阶段冲突:手势设置为
GTK_PHASE_CAPTURE优先拦截事件,终端无法正常处理输入和输出更新。 - 内存操作不规范:手动分配
graphene_point后混用free和graphene_point_free,存在内存管理风险。
修复后的代码
#include <gtk/gtk.h> #include <vte/vte.h> GtkWidget *window; GtkWidget *terminal; static void child_ready(VteTerminal *terminal, GPid pid, GError *error, gpointer user_data) { if (!terminal) return; if (pid == -1) gtk_window_destroy(GTK_WINDOW(window)); } static void run_terminal() { gchar **envp = g_get_environ(); gchar **command = (gchar *[]){g_strdup(g_environ_getenv(envp, "SHELL")), "-c", "/bin/bash", NULL}; g_strfreev(envp); vte_terminal_spawn_async(VTE_TERMINAL(terminal), VTE_PTY_DEFAULT, NULL, command, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, -1, NULL, child_ready, NULL); g_strfreev(command); // 修复内存泄漏 } void drag_on_long_press (GtkGestureLongPress* gesture, gdouble x, gdouble y, gpointer user_data) { GdkEventSequence *sequence = gtk_gesture_get_last_updated_sequence(GTK_GESTURE(gesture)); if (!sequence) return; GdkEvent *event = gtk_gesture_get_last_event(GTK_GESTURE(gesture), sequence); if (gdk_event_get_event_type(event) != GDK_BUTTON_PRESS) return; guint btn = gdk_button_event_get_button(event); if (btn != GDK_BUTTON_PRIMARY) return; GtkWidget *widget = gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture)); GdkDevice *device = gtk_gesture_get_device(GTK_GESTURE(gesture)); guint32 timestamp = gdk_event_get_time(event); // 转换为屏幕全局坐标 graphene_point_t window_point = GRAPHENE_POINT_INIT(x, y); graphene_point_t screen_point; gtk_widget_translate_coordinates(widget, NULL, window_point.x, window_point.y, &screen_point.x, &screen_point.y); GdkSurface *surface = gtk_native_get_surface(gtk_widget_get_native(widget)); gdk_toplevel_begin_move(GDK_TOPLEVEL(surface), device, btn, screen_point.x, screen_point.y, timestamp); // 标记手势序列完成,释放事件拦截 gtk_gesture_set_state(GTK_GESTURE(gesture), sequence, GTK_EVENT_SEQUENCE_DONE); } static void activate (GtkApplication *app, gpointer user_data) { window = gtk_application_window_new(app); gtk_window_set_title(GTK_WINDOW(window), "终端窗口"); gtk_window_set_default_size(GTK_WINDOW(window), 660, 300); GtkGesture* ges = gtk_gesture_long_press_new(); gtk_gesture_single_set_button(GTK_GESTURE_SINGLE(ges), GDK_BUTTON_PRIMARY); // 调整为冒泡阶段,避免拦截终端事件 gtk_event_controller_set_propagation_phase(GTK_EVENT_CONTROLLER(ges), GTK_PHASE_BUBBLE); gtk_widget_add_controller(window, GTK_EVENT_CONTROLLER(ges)); g_signal_connect(ges, "pressed", G_CALLBACK(drag_on_long_press), NULL); GtkWidget *scrollview = gtk_scrolled_window_new(); gtk_window_set_child(GTK_WINDOW(window), scrollview); terminal = vte_terminal_new(); gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scrollview), terminal); run_terminal(); gtk_window_present(GTK_WINDOW(window)); } int main (int argc, char **argv) { GtkApplication *app = gtk_application_new("org.gtk.terminal", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(activate), NULL); int status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); return status; }
关键修改说明
- 手势状态收尾:调用
gtk_gesture_set_state将序列标记为GTK_EVENT_SEQUENCE_DONE,释放事件拦截,让终端正常接收更新。 - 坐标修正:用
gtk_widget_translate_coordinates转换为屏幕全局坐标,确保拖动起始位置正确。 - 事件传播调整:将手势传播阶段改为
GTK_PHASE_BUBBLE,避免优先拦截终端的输入事件。 - 内存泄漏修复:释放
command数组中动态分配的字符串,规范内存管理。 - 代码简化:移除冗余的
graphene_point手动分配,使用栈上结构减少错误。
内容的提问来源于stack exchange,提问作者dibyendu
相关产品推荐
相关产品推荐

