You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 14:25:17