From b6b2f797d9df3e5562b946d5f4c690f8967c1b88 Mon Sep 17 00:00:00 2001 From: Po Lu Date: Tue, 28 Dec 2021 10:46:58 +0800 Subject: [PATCH] Fix menu placement on multiple-display setups when using lwlib * lwlib/xlwmenu.c (fit_to_screen): (pop_up_menu): Adjust menu position based on dimensions of the current monitor's workarea. (bug#52809) * src/xfns.c (x_get_monitor_attributes): Stop testing for the RandR extension here. (xlw_monitor_dimensions_at_pos_1): (xlw_monitor_dimensions_at_pos): New functions. * src/xterm.c (x_term_init): Query for the RandR extension when connecting to a display. * src/xterm.h (xlw_monitor_dimensions_at_pos): New prototype. --- lwlib/xlwmenu.c | 73 ++++++++++++++++++-------- src/xfns.c | 137 ++++++++++++++++++++++++++++++++++++++++++++---- src/xterm.c | 15 ++++++ src/xterm.h | 4 ++ 4 files changed, 195 insertions(+), 34 deletions(-) diff --git a/lwlib/xlwmenu.c b/lwlib/xlwmenu.c index a0a10d13db5..a065c53310d 100644 --- a/lwlib/xlwmenu.c +++ b/lwlib/xlwmenu.c @@ -1390,27 +1390,40 @@ fit_to_screen (XlwMenuWidget mw, window_state *previous_ws, Boolean horizontal_p) { - unsigned int screen_width = WidthOfScreen (XtScreen (mw)); - unsigned int screen_height = HeightOfScreen (XtScreen (mw)); + int screen_width, screen_height; + int screen_x, screen_y; + +#ifdef emacs + xlw_monitor_dimensions_at_pos (XtDisplay (mw), XtScreen (mw), + ws->x, ws->y, &screen_x, &screen_y, + &screen_width, &screen_height); +#else + screen_width = WidthOfScreen (XtScreen (mw)); + screen_height = HeightOfScreen (XtScreen (mw)); + screen_x = 0; + screen_y = 0; +#endif /* 1 if we are unable to avoid an overlap between this menu and the parent menu in the X dimension. */ int horizontal_overlap = 0; - if (ws->x < 0) + if (ws->x < screen_x) ws->x = 0; - else if (ws->x + ws->width > screen_width) + else if (ws->x + ws->width > screen_x + screen_width) { if (!horizontal_p) /* The addition of shadow-thickness for a sub-menu's position is to reflect a similar adjustment when the menu is displayed to the right of the invoking menu-item; it makes the sub-menu look more `attached' to the menu-item. */ - ws->x = previous_ws->x - ws->width + mw->menu.shadow_thickness; + ws->x = screen_x + (previous_ws->x + - ws->width + + mw->menu.shadow_thickness); else - ws->x = screen_width - ws->width; - if (ws->x < 0) + ws->x = screen_x + (screen_width - ws->width); + if (ws->x < screen_x) { - ws->x = 0; + ws->x = screen_x; horizontal_overlap = 1; } } @@ -1427,16 +1440,16 @@ fit_to_screen (XlwMenuWidget mw, ws->y = previous_ws->y - ws->height; } - if (ws->y < 0) - ws->y = 0; - else if (ws->y + ws->height > screen_height) + if (ws->y < screen_y) + ws->y = screen_y; + else if (ws->y + ws->height > screen_y + screen_height) { if (horizontal_p) - ws->y = previous_ws->y - ws->height; + ws->y = screen_y + (previous_ws->y - ws->height); else - ws->y = screen_height - ws->height; - if (ws->y < 0) - ws->y = 0; + ws->y = screen_y + (screen_height - ws->height); + if (ws->y < screen_y) + ws->y = screen_y; } } @@ -2626,7 +2639,21 @@ pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event) int borderwidth = mw->menu.shadow_thickness; Screen* screen = XtScreen (mw); Display *display = XtDisplay (mw); + int screen_x; + int screen_y; + int screen_w; + int screen_h; +#ifdef emacs + xlw_monitor_dimensions_at_pos (display, screen, x, y, + &screen_x, &screen_y, + &screen_w, &screen_h); +#else + screen_x = 0; + screen_y = 0; + screen_w = WidthOfScreen (screen); + screen_h = HeightOfScreen (screen); +#endif next_release_must_exit = 0; mw->menu.inside_entry = NULL; @@ -2640,14 +2667,14 @@ pop_up_menu (XlwMenuWidget mw, XButtonPressedEvent *event) x -= borderwidth; y -= borderwidth; - if (x < borderwidth) - x = borderwidth; - if (x + w + 2 * borderwidth > WidthOfScreen (screen)) - x = WidthOfScreen (screen) - w - 2 * borderwidth; - if (y < borderwidth) - y = borderwidth; - if (y + h + 2 * borderwidth> HeightOfScreen (screen)) - y = HeightOfScreen (screen) - h - 2 * borderwidth; + if (x < screen_x + borderwidth) + x = screen_x + borderwidth; + if (x + w + 2 * borderwidth > screen_x + screen_w) + x = (screen_x + screen_w) - w - 2 * borderwidth; + if (y < screen_y + borderwidth) + y = screen_y + borderwidth; + if (y + h + 2 * borderwidth > screen_y + screen_h) + y = (screen_y + screen_h) - h - 2 * borderwidth; mw->menu.popped_up = True; if (XtIsShell (XtParent ((Widget)mw))) diff --git a/src/xfns.c b/src/xfns.c index ad77ebc8f43..8dc383ddfa8 100644 --- a/src/xfns.c +++ b/src/xfns.c @@ -5041,17 +5041,9 @@ x_get_monitor_attributes (struct x_display_info *dpyinfo) (void) dpy; /* Suppress unused variable warning. */ #ifdef HAVE_XRANDR - int xrr_event_base, xrr_error_base; - bool xrr_ok = false; - xrr_ok = XRRQueryExtension (dpy, &xrr_event_base, &xrr_error_base); - if (xrr_ok) - { - XRRQueryVersion (dpy, &dpyinfo->xrandr_major_version, - &dpyinfo->xrandr_minor_version); - xrr_ok = ((dpyinfo->xrandr_major_version == 1 - && dpyinfo->xrandr_minor_version >= 2) - || dpyinfo->xrandr_major_version > 1); - } + bool xrr_ok = ((dpyinfo->xrandr_major_version == 1 + && dpyinfo->xrandr_minor_version >= 2) + || dpyinfo->xrandr_major_version > 1); if (xrr_ok) attributes_list = x_get_monitor_attributes_xrandr (dpyinfo); @@ -5076,6 +5068,129 @@ x_get_monitor_attributes (struct x_display_info *dpyinfo) #endif /* !USE_GTK */ +#ifdef USE_LUCID +/* This is used by the Lucid menu widget, but it's defined here so we + can make use of a great deal of existing code. */ +static void +xlw_monitor_dimensions_at_pos_1 (struct x_display_info *dpyinfo, + Screen *screen, int src_x, int src_y, + int *x, int *y, int *width, int *height) +{ + Lisp_Object attrs, tem, val; +#ifdef HAVE_XRANDR +#if RANDR_MAJOR > 1 || (RANDR_MAJOR == 1 && RANDR_MINOR >= 5) + int num_rr_monitors; + XRRMonitorInfo *rr_monitors; + + /* If RandR 1.5 or later is available, use that instead, as some + video drivers don't report correct dimensions via other versions + of RandR. */ + if (dpyinfo->xrandr_major_version > 1 + || (dpyinfo->xrandr_major_version == 1 + && dpyinfo->xrandr_minor_version >= 5)) + { + rr_monitors = XRRGetMonitors (dpyinfo->display, + RootWindowOfScreen (screen), + True, &num_rr_monitors); + if (!rr_monitors) + goto fallback; + + for (int i = 0; i < num_rr_monitors; ++i) + { + if (rr_monitors[i].x <= src_x + && src_x < (rr_monitors[i].x + + rr_monitors[i].width) + && rr_monitors[i].y <= src_y + && src_y < (rr_monitors[i].y + + rr_monitors[i].height)) + { + *x = rr_monitors[i].x; + *y = rr_monitors[i].y; + *width = rr_monitors[i].width; + *height = rr_monitors[i].height; + + XRRFreeMonitors (rr_monitors); + return; + } + } + XRRFreeMonitors (rr_monitors); + } + + fallback: +#endif +#endif + + attrs = x_get_monitor_attributes (dpyinfo); + + for (tem = attrs; CONSP (tem); tem = XCDR (tem)) + { + int sx, sy, swidth, sheight; + val = assq_no_quit (Qgeometry, XCAR (tem)); + if (!NILP (val)) + { + sx = XFIXNUM (XCAR (XCDR (val))); + sy = XFIXNUM (XCAR (XCDR (XCDR (val)))); + swidth = XFIXNUM (XCAR (XCDR (XCDR (XCDR (val))))); + sheight = XFIXNUM (XCAR (XCDR (XCDR (XCDR (XCDR (val)))))); + + if (sx <= src_x && src_x < (sx + swidth) + && sy <= src_y && src_y < (sy + swidth)) + { + *x = sx; + *y = sy; + *width = swidth; + *height = sheight; + return; + } + } + } + + *x = 0; + *y = 0; + *width = WidthOfScreen (screen); + *height = HeightOfScreen (screen); +} + +void +xlw_monitor_dimensions_at_pos (Display *dpy, Screen *screen, int src_x, + int src_y, int *x, int *y, int *width, int *height) +{ + struct x_display_info *dpyinfo = x_display_info_for_display (dpy); + XRectangle rect, workarea, intersection; + int dim_x, dim_y, dim_w, dim_h; + + if (!dpyinfo) + emacs_abort (); + + block_input (); + xlw_monitor_dimensions_at_pos_1 (dpyinfo, screen, src_x, src_y, + &dim_x, &dim_y, &dim_w, &dim_h); + rect.x = dim_x; + rect.y = dim_y; + rect.width = dim_w; + rect.height = dim_h; + + if (!x_get_net_workarea (dpyinfo, &workarea)) + memset (&workarea, 0, sizeof workarea); + unblock_input (); + + if (!gui_intersect_rectangles (&rect, &workarea, &intersection)) + { + *x = 0; + *y = 0; + *width = 0; + *height = 0; + return; + } + + *x = intersection.x; + *y = intersection.y; + *width = intersection.width; + *height = intersection.height; +} +#endif + + DEFUN ("x-display-monitor-attributes-list", Fx_display_monitor_attributes_list, Sx_display_monitor_attributes_list, 0, 1, 0, diff --git a/src/xterm.c b/src/xterm.c index 698c1eba8b7..3fdf214c3da 100644 --- a/src/xterm.c +++ b/src/xterm.c @@ -46,6 +46,10 @@ along with GNU Emacs. If not, see . */ #include #endif +#ifdef HAVE_XRANDR +#include +#endif + /* Load sys/types.h if not already loaded. In some systems loading it twice is suicidal. */ #ifndef makedev @@ -14803,6 +14807,17 @@ x_term_init (Lisp_Object display_name, char *xrm_option, char *resource_name) dpyinfo->xi2_version = minor; #endif +#ifdef HAVE_XRANDR + int xrr_event_base, xrr_error_base; + bool xrr_ok = false; + xrr_ok = XRRQueryExtension (dpy, &xrr_event_base, &xrr_error_base); + if (xrr_ok) + { + XRRQueryVersion (dpy, &dpyinfo->xrandr_major_version, + &dpyinfo->xrandr_minor_version); + } +#endif + #ifdef HAVE_XKB dpyinfo->xkb_desc = XkbGetMap (dpyinfo->display, XkbAllComponentsMask, diff --git a/src/xterm.h b/src/xterm.h index d9ace002d58..5615a55d6b9 100644 --- a/src/xterm.h +++ b/src/xterm.h @@ -1240,6 +1240,10 @@ extern void x_change_tool_bar_height (struct frame *, int); extern void x_implicitly_set_name (struct frame *, Lisp_Object, Lisp_Object); extern void x_set_scroll_bar_default_width (struct frame *); extern void x_set_scroll_bar_default_height (struct frame *); +#ifdef USE_LUCID +extern void xlw_monitor_dimensions_at_pos (Display *, Screen *, int, int, + int *, int *, int *, int *); +#endif /* Defined in xselect.c. */ -- 2.39.2