From ac2708bf6f83dfb965694381c4e9d0c71f61bd0c Mon Sep 17 00:00:00 2001 From: Po Lu Date: Fri, 8 Apr 2022 13:37:16 +0800 Subject: [PATCH] Implement support for reporting device names on PGTK * lisp/frame.el (device-class): Add new function. * lisp/term/pgtk-win.el (pgtk-device-class): New function. * src/pgtkterm.c (pgtk_device_added_or_removal_cb) (pgtk_seat_added_cb, pgtk_seat_removed_cb) (pgtk_enumerate_devices) (pgtk_free_devices, pgtk_regenerate_devices) (pgtk_get_device_for_event): New functions. (mark_pgtkterm): Mark device data. (pgtk_delete_terminal): Delete device data. (pgtk_handle_event, key_press_event, note_mouse_movement) (construct_mouse_click, button_event, scroll_event) (drag_data_received): Set device correctly. (pgtk_term_init): Initialize device data and seat tracking. (pgtk_delete_display): Delete device data. * src/pgtkterm.h (struct pgtk_device_t): New struct. (struct pgtk_display_info): New field `devices'. Delete lots of unused macros and reformat comments. --- lisp/frame.el | 3 + lisp/term/pgtk-win.el | 21 ++++ src/pgtkterm.c | 256 ++++++++++++++++++++++++++++++++++-------- src/pgtkterm.h | 77 +++---------- 4 files changed, 250 insertions(+), 107 deletions(-) diff --git a/lisp/frame.el b/lisp/frame.el index 395fe8daad8..7b19b8b5d32 100644 --- a/lisp/frame.el +++ b/lisp/frame.el @@ -2434,6 +2434,7 @@ monitors." (frames . ,(frames-on-display-list display))))))))) (declare-function x-device-class (name) "x-win.el") +(declare-function pgtk-device-class (name) "pgtk-win.el") (defun device-class (frame name) "Return the class of the device NAME for an event generated on FRAME. @@ -2488,6 +2489,8 @@ symbols." (let ((frame-type (framep-on-display frame))) (cond ((eq frame-type 'x) (x-device-class name)) + ((eq frame-type 'pgtk) + (pgtk-device-class name)) (t (cond ((string= name "Virtual core pointer") 'core-pointer) diff --git a/lisp/term/pgtk-win.el b/lisp/term/pgtk-win.el index a9d6db2d45b..5317f6ba01a 100644 --- a/lisp/term/pgtk-win.el +++ b/lisp/term/pgtk-win.el @@ -367,6 +367,27 @@ This uses `icon-map-list' to map icon file names to stock icon names." (t (popup-menu (mouse-menu-bar-map) last-nonmenu-event)))) +(defun pgtk-device-class (name) + "Return the device class of NAME. +Users should not call this function; see `device-class' instead." + (cond + ((string-match-p "XTEST" name) 'test) + ((string= "Virtual core pointer" name) 'core-pointer) + ((string= "Virtual core keyboard" name) 'core-keyboard) + (t (let ((number (ignore-errors + (string-to-number name)))) + (when number + (cl-case number + (0 'mouse) + (1 'pen) + (2 'eraser) + (3 'puck) + (4 'keyboard) + (5 'touchscreen) + (6 'touchpad) + (7 'trackpoint) + (8 'pad))))))) + (defvaralias 'x-gtk-use-system-tooltips 'use-system-tooltips) (provide 'pgtk-win) diff --git a/src/pgtkterm.c b/src/pgtkterm.c index b2816aa04af..d8c6dad2f9d 100644 --- a/src/pgtkterm.c +++ b/src/pgtkterm.c @@ -98,15 +98,124 @@ static Time ignore_next_mouse_click_timeout; static Lisp_Object xg_default_icon_file; -static void pgtk_delete_display (struct pgtk_display_info *dpyinfo); -static void pgtk_clear_frame_area (struct frame *f, int x, int y, int width, - int height); -static void pgtk_fill_rectangle (struct frame *f, unsigned long color, int x, - int y, int width, int height, - bool respect_alpha_background); -static void pgtk_clip_to_row (struct window *w, struct glyph_row *row, - enum glyph_row_area area, cairo_t * cr); -static struct frame *pgtk_any_window_to_frame (GdkWindow *window); +static void pgtk_delete_display (struct pgtk_display_info *); +static void pgtk_clear_frame_area (struct frame *, int, int, int, int); +static void pgtk_fill_rectangle (struct frame *, unsigned long, int, int, + int, int, bool); +static void pgtk_clip_to_row (struct window *, struct glyph_row *, + enum glyph_row_area, cairo_t *); +static struct frame *pgtk_any_window_to_frame (GdkWindow *); +static void pgtk_regenerate_devices (struct pgtk_display_info *); + +static void +pgtk_device_added_or_removal_cb (GdkSeat *seat, GdkDevice *device, + gpointer user_data) +{ + pgtk_regenerate_devices (user_data); +} + +static void +pgtk_seat_added_cb (GdkDisplay *dpy, GdkSeat *seat, + gpointer user_data) +{ + pgtk_regenerate_devices (user_data); + + g_signal_connect (G_OBJECT (seat), "device-added", + G_CALLBACK (pgtk_device_added_or_removal_cb), + user_data); + g_signal_connect (G_OBJECT (seat), "device-removed", + G_CALLBACK (pgtk_device_added_or_removal_cb), + user_data); +} + +static void +pgtk_seat_removed_cb (GdkDisplay *dpy, GdkSeat *seat, + gpointer user_data) +{ + pgtk_regenerate_devices (user_data); + + g_signal_handlers_disconnect_by_func (G_OBJECT (seat), + G_CALLBACK (pgtk_device_added_or_removal_cb), + user_data); +} + +static void +pgtk_enumerate_devices (struct pgtk_display_info *dpyinfo, + bool initial_p) +{ + struct pgtk_device_t *rec; + GList *all_seats, *devices_on_seat, *tem, *t1; + GdkSeat *seat; + char printbuf[1026]; /* Believe it or not, some device names are + actually almost this long. */ + + block_input (); + all_seats = gdk_display_list_seats (dpyinfo->gdpy); + + for (tem = all_seats; tem; tem = tem->next) + { + seat = GDK_SEAT (tem->data); + + if (initial_p) + { + g_signal_connect (G_OBJECT (seat), "device-added", + G_CALLBACK (pgtk_device_added_or_removal_cb), + dpyinfo); + g_signal_connect (G_OBJECT (seat), "device-removed", + G_CALLBACK (pgtk_device_added_or_removal_cb), + dpyinfo); + } + + /* We only want slaves, not master devices. */ + devices_on_seat = gdk_seat_get_slaves (seat, + GDK_SEAT_CAPABILITY_ALL); + + for (t1 = devices_on_seat; t1; t1 = t1->next) + { + rec = xmalloc (sizeof *rec); + rec->seat = g_object_ref (seat); + rec->device = GDK_DEVICE (t1->data); + + snprintf (printbuf, 1026, "%u:%s", + gdk_device_get_source (rec->device), + gdk_device_get_name (rec->device)); + + rec->name = build_string (printbuf); + rec->next = dpyinfo->devices; + dpyinfo->devices = rec; + } + + g_list_free (devices_on_seat); + } + + g_list_free (all_seats); + unblock_input (); +} + +static void +pgtk_free_devices (struct pgtk_display_info *dpyinfo) +{ + struct pgtk_device_t *last, *tem; + + tem = dpyinfo->devices; + while (tem) + { + last = tem; + tem = tem->next; + + g_object_unref (last->seat); + xfree (last); + } + + dpyinfo->devices = NULL; +} + +static void +pgtk_regenerate_devices (struct pgtk_display_info *dpyinfo) +{ + pgtk_free_devices (dpyinfo); + pgtk_enumerate_devices (dpyinfo, false); +} static void pgtk_toolkit_position (struct frame *f, int x, int y, @@ -136,6 +245,27 @@ pgtk_toolkit_position (struct frame *f, int x, int y, } } +static Lisp_Object +pgtk_get_device_for_event (struct pgtk_display_info *dpyinfo, + GdkEvent *event) +{ + struct pgtk_device_t *tem; + GdkDevice *device; + + device = gdk_event_get_source_device (event); + + if (!device) + return Qt; + + for (tem = dpyinfo->devices; tem; tem = tem->next) + { + if (tem->device == device) + return tem->name; + } + + return Qt; +} + /* This is not a flip context in the same sense as gpu rendering scenes, it only occurs when a new context was required due to a resize or other fundamental change. This is called when that @@ -205,8 +335,11 @@ evq_flush (struct input_event *hold_quit) void mark_pgtkterm (void) { + struct pgtk_display_info *dpyinfo; + struct pgtk_device_t *device; struct event_queue_t *evq = &event_q; int i, n = evq->nr; + for (i = 0; i < n; i++) { union buffered_input_event *ev = &evq->q[i]; @@ -215,6 +348,14 @@ mark_pgtkterm (void) mark_object (ev->ie.frame_or_window); mark_object (ev->ie.arg); } + + for (dpyinfo = x_display_list; dpyinfo; + dpyinfo = dpyinfo->next) + { + for (device = dpyinfo->devices; device; + device = device->next) + mark_object (device->name); + } } char * @@ -4460,11 +4601,20 @@ pgtk_delete_terminal (struct terminal *terminal) g_clear_object (&dpyinfo->vertical_scroll_bar_cursor); g_clear_object (&dpyinfo->horizontal_scroll_bar_cursor); g_clear_object (&dpyinfo->invisible_cursor); - if (dpyinfo->last_click_event != NULL) { - gdk_event_free (dpyinfo->last_click_event); - dpyinfo->last_click_event = NULL; - } + if (dpyinfo->last_click_event != NULL) + { + gdk_event_free (dpyinfo->last_click_event); + dpyinfo->last_click_event = NULL; + } + /* Disconnect these handlers before the display closes so + useless removal signals don't fire. */ + g_signal_handlers_disconnect_by_func (G_OBJECT (dpyinfo->gdpy), + G_CALLBACK (pgtk_seat_added_cb), + dpyinfo); + g_signal_handlers_disconnect_by_func (G_OBJECT (dpyinfo->gdpy), + G_CALLBACK (pgtk_seat_removed_cb), + dpyinfo); xg_display_close (dpyinfo->gdpy); dpyinfo->gdpy = NULL; @@ -4889,6 +5039,8 @@ pgtk_handle_event (GtkWidget *widget, GdkEvent *event, gpointer *data) make_float (event->touchpad_pinch.angle_delta)); inev.ie.modifiers = pgtk_gtk_to_emacs_modifiers (FRAME_DISPLAY_INFO (f), event->touchpad_pinch.state); + inev.ie.device + = pgtk_get_device_for_event (FRAME_DISPLAY_INFO (f), event); evq_enqueue (&inev); } @@ -5227,7 +5379,7 @@ pgtk_enqueue_preedit (struct frame *f, Lisp_Object preedit) } static gboolean -key_press_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) +key_press_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) { struct coding_system coding; union buffered_input_event inev; @@ -5237,8 +5389,6 @@ key_press_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) USE_SAFE_ALLOCA; EVENT_INIT (inev.ie); - inev.ie.kind = NO_EVENT; - inev.ie.arg = Qnil; struct frame *f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); hlinfo = MOUSE_HL_INFO (f); @@ -5321,6 +5471,9 @@ key_press_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) { inev.ie.kind = ASCII_KEYSTROKE_EVENT; inev.ie.code = keysym; + + inev.ie.device + = pgtk_get_device_for_event (FRAME_DISPLAY_INFO (f), event); goto done; } @@ -5332,6 +5485,9 @@ key_press_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) else inev.ie.kind = MULTIBYTE_CHAR_KEYSTROKE_EVENT; inev.ie.code = keysym & 0xFFFFFF; + + inev.ie.device + = pgtk_get_device_for_event (FRAME_DISPLAY_INFO (f), event); goto done; } @@ -5344,6 +5500,9 @@ key_press_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) ? ASCII_KEYSTROKE_EVENT : MULTIBYTE_CHAR_KEYSTROKE_EVENT); inev.ie.code = XFIXNAT (c); + + inev.ie.device + = pgtk_get_device_for_event (FRAME_DISPLAY_INFO (f), event); goto done; } @@ -5427,6 +5586,9 @@ key_press_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) key. */ inev.ie.kind = NON_ASCII_KEYSTROKE_EVENT; inev.ie.code = keysym; + + inev.ie.device + = pgtk_get_device_for_event (FRAME_DISPLAY_INFO (f), event); goto done; } @@ -5478,6 +5640,8 @@ key_press_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) ? ASCII_KEYSTROKE_EVENT : MULTIBYTE_CHAR_KEYSTROKE_EVENT); inev.ie.code = ch; + inev.ie.device + = pgtk_get_device_for_event (FRAME_DISPLAY_INFO (f), event); evq_enqueue (&inev); } @@ -5859,7 +6023,8 @@ focus_out_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) another motion event, so we can check again the next time it moves. */ static bool -note_mouse_movement (struct frame *frame, const GdkEventMotion * event) +note_mouse_movement (struct frame *frame, + const GdkEventMotion *event) { XRectangle *r; struct pgtk_display_info *dpyinfo; @@ -5879,6 +6044,9 @@ note_mouse_movement (struct frame *frame, const GdkEventMotion * event) dpyinfo->last_mouse_scroll_bar = NULL; note_mouse_highlight (frame, -1, -1); dpyinfo->last_mouse_glyph_frame = NULL; + frame->last_mouse_device + = pgtk_get_device_for_event (FRAME_DISPLAY_INFO (frame), + (GdkEvent *) event); return true; } @@ -5895,6 +6063,9 @@ note_mouse_movement (struct frame *frame, const GdkEventMotion * event) /* Remember which glyph we're now on. */ remember_mouse_glyph (frame, event->x, event->y, r); dpyinfo->last_mouse_glyph_frame = frame; + frame->last_mouse_device + = pgtk_get_device_for_event (FRAME_DISPLAY_INFO (frame), + (GdkEvent *) event); return true; } @@ -6010,26 +6181,6 @@ motion_notify_event (GtkWidget * widget, GdkEvent * event, return TRUE; } -/* Mouse clicks and mouse movement. Rah. - - Formerly, we used PointerMotionHintMask (in standard_event_mask) - so that we would have to call XQueryPointer after each MotionNotify - event to ask for another such event. However, this made mouse tracking - slow, and there was a bug that made it eventually stop. - - Simply asking for MotionNotify all the time seems to work better. - - In order to avoid asking for motion events and then throwing most - of them away or busy-polling the server for mouse positions, we ask - the server for pointer motion hints. This means that we get only - one event per group of mouse movements. "Groups" are delimited by - other kinds of events (focus changes and button clicks, for - example), or by XQueryPointer calls; when one of these happens, we - get another MotionNotify event the next time the mouse moves. This - is at least as efficient as getting motion events when mouse - tracking is on, and I suspect only negligibly worse when tracking - is off. */ - /* Prepare a mouse-event in *RESULT for placement in the input queue. If the event is a button press, then note that we have grabbed @@ -6037,7 +6188,8 @@ motion_notify_event (GtkWidget * widget, GdkEvent * event, static Lisp_Object construct_mouse_click (struct input_event *result, - const GdkEventButton * event, struct frame *f) + const GdkEventButton *event, + struct frame *f) { /* Make the event type NO_EVENT; we'll change that when we decide otherwise. */ @@ -6052,11 +6204,15 @@ construct_mouse_click (struct input_event *result, XSETINT (result->y, event->y); XSETFRAME (result->frame_or_window, f); result->arg = Qnil; + result->device = pgtk_get_device_for_event (FRAME_DISPLAY_INFO (f), + (GdkEvent *) event); return Qnil; } static gboolean -button_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) +button_event (GtkWidget *widget, + GdkEvent *event, + gpointer *user_data) { union buffered_input_event inev; struct frame *f, *frame; @@ -6175,7 +6331,7 @@ button_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) } static gboolean -scroll_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) +scroll_event (GtkWidget *widget, GdkEvent *event, gpointer *user_data) { union buffered_input_event inev; struct frame *f, *frame; @@ -6207,6 +6363,8 @@ scroll_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) if (gdk_event_is_scroll_stop_event (event)) { inev.ie.kind = TOUCH_END_EVENT; + inev.ie.device + = pgtk_get_device_for_event (FRAME_DISPLAY_INFO (f), event); evq_enqueue (&inev); return TRUE; } @@ -6300,14 +6458,17 @@ scroll_event (GtkWidget * widget, GdkEvent * event, gpointer * user_data) } if (inev.ie.kind != NO_EVENT) - evq_enqueue (&inev); + { + inev.ie.device + = pgtk_get_device_for_event (FRAME_DISPLAY_INFO (f), event); + evq_enqueue (&inev); + } return TRUE; } static void -drag_data_received (GtkWidget * widget, GdkDragContext * context, - gint x, gint y, - GtkSelectionData * data, +drag_data_received (GtkWidget *widget, GdkDragContext *context, + gint x, gint y, GtkSelectionData *data, guint info, guint time, gpointer user_data) { struct frame *f = pgtk_any_window_to_frame (gtk_widget_get_window (widget)); @@ -6716,6 +6877,12 @@ pgtk_term_init (Lisp_Object display_name, char *resource_name) pgtk_im_init (dpyinfo); + g_signal_connect (G_OBJECT (dpyinfo->gdpy), "seat-added", + G_CALLBACK (pgtk_seat_added_cb), dpyinfo); + g_signal_connect (G_OBJECT (dpyinfo->gdpy), "seat-removed", + G_CALLBACK (pgtk_seat_removed_cb), dpyinfo); + pgtk_enumerate_devices (dpyinfo, true); + unblock_input (); return dpyinfo; @@ -6749,6 +6916,7 @@ pgtk_delete_display (struct pgtk_display_info *dpyinfo) tail->next = tail->next->next; } + pgtk_free_devices (dpyinfo); xfree (dpyinfo); } diff --git a/src/pgtkterm.h b/src/pgtkterm.h index b1165752aba..56c5d22e54e 100644 --- a/src/pgtkterm.h +++ b/src/pgtkterm.h @@ -40,8 +40,6 @@ along with GNU Emacs. If not, see . */ #include #endif -/* could use list to store these, but rest of emacs has a big infrastructure - for managing a table of bitmap "records" */ struct pgtk_bitmap_record { void *img; @@ -51,6 +49,15 @@ struct pgtk_bitmap_record cairo_pattern_t *pattern; }; +struct pgtk_device_t +{ + GdkSeat *seat; + GdkDevice *device; + + Lisp_Object name; + struct pgtk_device_t *next; +}; + #define RGB_TO_ULONG(r, g, b) (((r) << 16) | ((g) << 8) | (b)) #define ARGB_TO_ULONG(a, r, g, b) (((a) << 24) | ((r) << 16) | ((g) << 8) | (b)) @@ -112,8 +119,6 @@ struct scroll_bar bool horizontal; }; - -/* init'd in pgtk_initialize_display_info () */ struct pgtk_display_info { /* Chain of all pgtk_display_info structures. */ @@ -208,13 +213,14 @@ struct pgtk_display_info /* The scroll bar in which the last motion event occurred. */ void *last_mouse_scroll_bar; - /* The invisible cursor used for pointer blanking. - Unused if this display supports Xfixes extension. */ + /* The invisible cursor used for pointer blanking. */ Emacs_Cursor invisible_cursor; /* The GDK cursor for scroll bars and popup menus. */ GdkCursor *xg_cursor; + /* List of all devices for all seats on this display. */ + struct pgtk_device_t *devices; /* The frame where the mouse was last time we reported a mouse position. */ struct frame *last_mouse_glyph_frame; @@ -225,7 +231,7 @@ struct pgtk_display_info /* The last click event. */ GdkEvent *last_click_event; - /* input method */ + /* IM context data. */ struct { GtkIMContext *context; @@ -246,10 +252,6 @@ extern struct pgtk_display_info *x_display_list; struct pgtk_output { -#if 0 - void *view; - void *miniimage; -#endif unsigned long foreground_color; unsigned long background_color; void *toolbar; @@ -406,7 +408,7 @@ struct pgtk_output struct atimer *scale_factor_atimer; }; -/* this dummy decl needed to support TTYs */ +/* Satisfy term.c. */ struct x_output { int unused; @@ -452,59 +454,8 @@ enum /* Turning a lisp vector value into a pointer to a struct scroll_bar. */ #define XSCROLL_BAR(vec) ((struct scroll_bar *) XVECTOR (vec)) -#define PGTK_FACE_FOREGROUND(f) ((f)->foreground) -#define PGTK_FACE_BACKGROUND(f) ((f)->background) #define FRAME_DEFAULT_FACE(f) FACE_FROM_ID_OR_NULL (f, DEFAULT_FACE_ID) - -/* Compute pixel height of the frame's titlebar. */ -#define FRAME_PGTK_TITLEBAR_HEIGHT(f) 0 - -/* Compute pixel size for vertical scroll bars */ -#define PGTK_SCROLL_BAR_WIDTH(f) \ - (FRAME_HAS_VERTICAL_SCROLL_BARS (f) \ - ? rint (FRAME_CONFIG_SCROLL_BAR_WIDTH (f) > 0 \ - ? FRAME_CONFIG_SCROLL_BAR_WIDTH (f) \ - : (FRAME_SCROLL_BAR_COLS (f) * FRAME_COLUMN_WIDTH (f))) \ - : 0) - -/* Compute pixel size for horizontal scroll bars */ -#define PGTK_SCROLL_BAR_HEIGHT(f) \ - (FRAME_HAS_HORIZONTAL_SCROLL_BARS (f) \ - ? rint (FRAME_CONFIG_SCROLL_BAR_HEIGHT (f) > 0 \ - ? FRAME_CONFIG_SCROLL_BAR_HEIGHT (f) \ - : (FRAME_SCROLL_BAR_LINES (f) * FRAME_LINE_HEIGHT (f))) \ - : 0) - -/* Difference btwn char-column-calculated and actual SB widths. - This is only a concern for rendering when SB on left. */ -#define PGTK_SCROLL_BAR_ADJUST(w, f) \ - (WINDOW_HAS_VERTICAL_SCROLL_BAR_ON_LEFT (w) ? \ - (FRAME_SCROLL_BAR_COLS (f) * FRAME_COLUMN_WIDTH (f) \ - - PGTK_SCROLL_BAR_WIDTH (f)) : 0) - -/* Difference btwn char-line-calculated and actual SB heights. - This is only a concern for rendering when SB on top. */ -#define PGTK_SCROLL_BAR_ADJUST_HORIZONTALLY(w, f) \ - (WINDOW_HAS_HORIZONTAL_SCROLL_BARS (w) ? \ - (FRAME_SCROLL_BAR_LINES (f) * FRAME_LINE_HEIGHT (f) \ - - PGTK_SCROLL_BAR_HEIGHT (f)) : 0) - #define FRAME_MENUBAR_HEIGHT(f) (FRAME_X_OUTPUT (f)->menubar_height) - -/* Calculate system coordinates of the left and top of the parent - window or, if there is no parent window, the screen. */ -#define PGTK_PARENT_WINDOW_LEFT_POS(f) \ - (FRAME_PARENT_FRAME (f) != NULL \ - ? [[FRAME_PGTK_VIEW (f) window] parentWindow].frame.origin.x : 0) -#define PGTK_PARENT_WINDOW_TOP_POS(f) \ - (FRAME_PARENT_FRAME (f) != NULL \ - ? ([[FRAME_PGTK_VIEW (f) window] parentWindow].frame.origin.y \ - + [[FRAME_PGTK_VIEW (f) window] parentWindow].frame.size.height \ - - FRAME_PGTK_TITLEBAR_HEIGHT (FRAME_PARENT_FRAME (f))) \ - : [[[PGTKScreen screepgtk] objectAtIndex: 0] frame].size.height) - -#define FRAME_PGTK_FONT_TABLE(f) (FRAME_DISPLAY_INFO (f)->font_table) - #define FRAME_TOOLBAR_TOP_HEIGHT(f) ((f)->output_data.pgtk->toolbar_top_height) #define FRAME_TOOLBAR_BOTTOM_HEIGHT(f) \ ((f)->output_data.pgtk->toolbar_bottom_height)