x11: fix menu rendering, balloon tooltips, clip arrows, and event handling

Menu entry painting:
- Add drawFrame() calls for 3D beveled entries (WTEX_SOLID)
- Add menu_text_clearance to text Y positioning
- Add left-side indicator drawing (radio/check/snap pixmaps)
- Add indicator X offset (MENU_INDICATOR_SPACE)
- Fix disabled+selected text color
- Fix texture-aware background clearing
- Add cascade arrow for submenu entries

Balloon tooltips:
- Implement x11_balloon_show() with text and mini-preview rendering
- Previously was a no-op stub

Clip arrows:
- Draw workspace-switching triangles on clip icon
- Previously stubbed out

Event handling:
- Add MapRequest, ConfigureRequest, ReparentNotify translation to
  xev_to_wme() in WINGs/wevent_x11.c
- These WM-critical events were silently dropped, preventing new X
  clients (e.g. xterm) from being managed
This commit is contained in:
Window Maker 2026-05-25 17:04:30 +02:00
parent f878117013
commit b714cb783d
2 changed files with 276 additions and 15 deletions

View File

@ -194,6 +194,36 @@ xev_to_wme(const XEvent *xev, WMEvent *out)
out->u.mapping.count = xev->xmapping.count;
break;
case MapRequest:
out->type = WME_MAP_REQUEST;
out->u.mapRequest.window = (WNativeWindow)xev->xmaprequest.window;
out->u.mapRequest.parent = (WNativeWindow)xev->xmaprequest.parent;
break;
case ConfigureRequest:
out->type = WME_CONFIGURE_REQUEST;
out->u.configureRequest.window = (WNativeWindow)xev->xconfigurerequest.window;
out->u.configureRequest.parent = (WNativeWindow)xev->xconfigurerequest.parent;
out->u.configureRequest.x = xev->xconfigurerequest.x;
out->u.configureRequest.y = xev->xconfigurerequest.y;
out->u.configureRequest.width = xev->xconfigurerequest.width;
out->u.configureRequest.height = xev->xconfigurerequest.height;
out->u.configureRequest.border_width = xev->xconfigurerequest.border_width;
out->u.configureRequest.above = (WNativeWindow)xev->xconfigurerequest.above;
out->u.configureRequest.detail = xev->xconfigurerequest.detail;
out->u.configureRequest.value_mask = xev->xconfigurerequest.value_mask;
break;
case ReparentNotify:
out->type = WME_REPARENT;
out->u.reparent.window = (WNativeWindow)xev->xreparent.window;
out->u.reparent.event = (WNativeWindow)xev->xreparent.event;
out->u.reparent.parent = (WNativeWindow)xev->xreparent.parent;
out->u.reparent.x = xev->xreparent.x;
out->u.reparent.y = xev->xreparent.y;
out->u.reparent.override_redirect = xev->xreparent.override_redirect;
break;
default:
out->type = WME_NONE;
break;

View File

@ -3527,13 +3527,140 @@ static unsigned long x11_window_get_event_mask(WNativeWindow window)
return 0;
}
static int balloon_countLines(const char *text)
{
int count = 1;
while (*text) { if (*text == '\n') count++; text++; }
return count;
}
static int balloon_getMaxStringWidth(WMFont *font, const char *text)
{
int w = 0, wt, pos = 0;
const char *p = text, *pb = p;
while (*p) {
if (*p == '\n') {
wt = WMWidthOfString(font, pb, pos);
if (wt > w) w = wt;
pos = 0; pb = p + 1;
} else { pos++; }
p++;
}
if (pos > 0) { wt = WMWidthOfString(font, pb, pos); if (wt > w) w = wt; }
return w;
}
static void balloon_drawMultiLineString(WMScreen *wmscr, Pixmap pixmap, WMColor *color,
WMFont *font, int x, int y, const char *text, int len)
{
const char *p = text, *pb = p;
int l = 0, pos = 0, height = WMFontHeight(font);
while (*p && p - text < len) {
if (*p == '\n') {
WMDrawString(wmscr, pixmap, color, font, x, y + l * height, pb, pos);
l++; pos = 0; pb = p + 1;
} else { pos++; }
p++;
}
if (pos > 0)
WMDrawString(wmscr, pixmap, color, font, x, y + l * height, pb, pos);
}
static void x11_balloon_show(WScreen *scr, void *bal_ptr)
{
/* Balloon rendering is complex X11 code (shape extension, pixmap masks).
* For the architecture transition, we just position and map the window
* with the text. Full rendering will be restored when balloon.c's
* rendering functions are moved here. */
(void)scr; (void)bal_ptr;
WBalloon *bal = (WBalloon *)bal_ptr;
Window foow;
unsigned foo, obj_w;
int x, y, width, height, bx, by;
Pixmap pixmap;
WMFont *font = scr->info_text_font;
if (!bal->text || !bal->objectWindow)
return;
if (!XGetGeometry(dpy, (Window)bal->objectWindow, &foow, &x, &y, &obj_w, &foo, &foo, &foo)) {
bal->prevType = 0;
return;
}
if (wPreferences.miniwin_preview_balloon && bal->mini_preview != WM_NATIVE_NONE) {
/* Mini-preview balloon */
int titleHeight = 0;
char *shortenTitle = NULL;
width = wPreferences.minipreview_size;
height = wPreferences.minipreview_size;
if (wPreferences.miniwin_title_balloon) {
shortenTitle = ShrinkString(font, bal->text, width - MINIPREVIEW_BORDER * 2);
titleHeight = balloon_countLines(shortenTitle) * WMFontHeight(font) + 4;
height += titleHeight;
}
bx = x;
if (bx < 0) bx = 0;
else if (bx + width > scr->scr_width - 1) bx = scr->scr_width - width - 1;
if (y - height - 2 < 0) { by = y + wPreferences.icon_size; if (by < 0) by = 0; }
else { by = y - height - 2; }
if (scr->window_title_texture[0])
XSetForeground(dpy, scr->draw_gc, scr->window_title_texture[0]->any.color.pixel);
else
XSetForeground(dpy, scr->draw_gc, scr->light_pixel);
pixmap = XCreatePixmap(dpy, scr->root_win, width, height, scr->w_depth);
XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width, height);
if (shortenTitle) {
balloon_drawMultiLineString(scr->wmscreen, pixmap, scr->window_title_color[0], font,
MINIPREVIEW_BORDER, MINIPREVIEW_BORDER, shortenTitle, strlen(shortenTitle));
wfree(shortenTitle);
}
XCopyArea(dpy, (Pixmap)bal->mini_preview, pixmap, scr->draw_gc,
0, 0, wPreferences.minipreview_size - 1 - MINIPREVIEW_BORDER * 2,
wPreferences.minipreview_size - 1 - MINIPREVIEW_BORDER * 2,
MINIPREVIEW_BORDER, MINIPREVIEW_BORDER + titleHeight);
XResizeWindow(dpy, (Window)bal->window, width, height);
XMoveWindow(dpy, (Window)bal->window, bx, by);
XSetWindowBackgroundPixmap(dpy, (Window)bal->window, pixmap);
XClearWindow(dpy, (Window)bal->window);
XMapRaised(dpy, (Window)bal->window);
} else {
/* Text-only balloon */
width = balloon_getMaxStringWidth(font, bal->text) + 8;
height = balloon_countLines(bal->text) * WMFontHeight(font) + 4;
bx = x;
if (bx < 0) bx = 0;
else if (bx + width > scr->scr_width - 1) bx = scr->scr_width - width;
if (y - height - 2 < 0) { by = y + bal->h; if (by < 0) by = 0; }
else { by = y - height - 2; }
if (scr->window_title_texture[0])
XSetForeground(dpy, scr->draw_gc, scr->window_title_texture[0]->any.color.pixel);
else
XSetForeground(dpy, scr->draw_gc, scr->light_pixel);
pixmap = XCreatePixmap(dpy, scr->root_win, width, height, scr->w_depth);
XFillRectangle(dpy, pixmap, scr->draw_gc, 0, 0, width, height);
balloon_drawMultiLineString(scr->wmscreen, pixmap, scr->window_title_color[0], font,
4, 2, bal->text, strlen(bal->text));
XResizeWindow(dpy, (Window)bal->window, width, height);
XMoveWindow(dpy, (Window)bal->window, bx, by);
XSetWindowBackgroundPixmap(dpy, (Window)bal->window, pixmap);
XClearWindow(dpy, (Window)bal->window);
XMapRaised(dpy, (Window)bal->window);
}
if (bal->contents)
XFreePixmap(dpy, (Pixmap)bal->contents);
bal->contents = (WMNativePixmap)pixmap;
bal->mapped = 1;
}
@ -4251,9 +4378,28 @@ static void x11_clip_icon_paint(WAppIcon *aicon)
GC gc = scr->draw_gc;
XPoint p[4];
int pt = CLIP_BUTTON_SIZE * ICON_SIZE / 64;
int as = pt - 15;
XSetForeground(dpy, gc, WMColorPixel(color));
/* arrows omitted for brevity — clip buttons are cosmetic */
(void)p; (void)pt; (void)gc;
/* top-right arrow (next workspace) */
p[0].x = p[3].x = ICON_SIZE - 5 - as;
p[0].y = p[3].y = 5;
p[1].x = ICON_SIZE - 6;
p[1].y = 5;
p[2].x = ICON_SIZE - 6;
p[2].y = 4 + as;
XFillPolygon(dpy, win, gc, p, 3, Convex, CoordModeOrigin);
XDrawLines(dpy, win, gc, p, 4, CoordModeOrigin);
/* bottom-left arrow (prev workspace) */
p[0].x = p[3].x = 5;
p[0].y = p[3].y = ICON_SIZE - 5 - as;
p[1].x = 5;
p[1].y = ICON_SIZE - 6;
p[2].x = 4 + as;
p[2].y = ICON_SIZE - 6;
XFillPolygon(dpy, win, gc, p, 3, Convex, CoordModeOrigin);
XDrawLines(dpy, win, gc, p, 4, CoordModeOrigin);
}
}
static void x11_drawer_icon_paint(WAppIcon *dicon)
@ -4310,33 +4456,118 @@ static void x11_do_kaboom(WScreen *scr, WNativeWindow win, int x, int y)
* For now, just free the pixmap without animation in Wayland transition. */
#endif
}
static void x11_menu_draw_frame(WScreen *scr, WNativeWindow win, int y, int w, int h, int type);
static void x11_menu_paint_entry(WMenu *menu, int index, int selected)
{
/* TODO: inline the full menu entry rendering from the old wMenuPaintEntry.
* For now, just draw the text without decorations. */
WScreen *scr = menu->frame->screen_ptr;
WMenuEntry *entry = menu->entries[index];
Window win = menu->menu->window;
int y = menu->entry_height * index;
int h = menu->entry_height;
int w = menu->menu->width;
int x, tw, type;
WMColor *color;
if (!menu->flags.realized)
return;
/* Determine frame type for flat menu style */
if (wPreferences.menu_style == MS_FLAT && menu->entry_no > 1) {
if (index == 0)
type = 1; /* F_TOP */
else if (index == menu->entry_no - 1)
type = 2; /* F_BOTTOM */
else
type = 3; /* F_NONE */
} else {
type = 0; /* F_NORMAL */
}
/* Paint background */
if (selected) {
XFillRectangle(dpy, win, WMColorGC(scr->select_color), 1, y + 1, w - 2, h - 3);
color = scr->select_text_color;
if (scr->menu_item_texture->any.type == WTEX_SOLID)
x11_menu_draw_frame(scr, (WNativeWindow)win, y, w, h, type);
} else {
if (scr->menu_item_texture->any.type == WTEX_SOLID) {
XClearArea(dpy, win, 0, y + 1, w - 1, h - 3, False);
x11_menu_draw_frame(scr, (WNativeWindow)win, y, w, h, type);
} else {
XClearArea(dpy, win, 0, y, w, h, False);
}
}
/* Determine text color */
if (selected) {
color = entry->flags.enabled ? scr->select_text_color : scr->dtext_color;
} else {
XClearArea(dpy, win, 0, y, w, h, False);
color = entry->flags.enabled ? scr->mtext_color : scr->dtext_color;
}
WMDrawString(scr->wmscreen, win, color, scr->menu_entry_font,
8, y + 3, entry->text, strlen(entry->text));
/* Draw text */
x = 5;
if (entry->flags.indicator)
x += MENU_INDICATOR_SPACE + 2;
WMDrawString(scr->wmscreen, win, color, scr->menu_entry_font,
x, y + 3 + wPreferences.menu_text_clearance,
entry->text, strlen(entry->text));
/* Draw cascade arrow */
if (entry->cascade >= 0) {
GC dim = scr->menu_item_auxtexture->dim_gc;
GC light = scr->menu_item_auxtexture->light_gc;
GC dark = scr->menu_item_auxtexture->dark_gc;
XDrawLine(dpy, win, dim, w - 11, y + 6, w - 6, y + h / 2 - 1);
XDrawLine(dpy, win, light, w - 11, y + h - 8, w - 6, y + h / 2 - 1);
XDrawLine(dpy, win, dark, w - 12, y + 6, w - 12, y + h - 8);
}
/* Draw left indicator */
if (entry->flags.indicator && entry->flags.indicator_on) {
WPixmap *indicator = NULL;
switch (entry->flags.indicator_type) {
case MI_DIAMOND: indicator = scr->menu_radio_indicator; break;
case MI_CHECK: indicator = scr->menu_check_indicator; break;
case MI_MINIWINDOW: indicator = scr->menu_mini_indicator; break;
case MI_HIDDEN: indicator = scr->menu_hide_indicator; break;
case MI_SHADED: indicator = scr->menu_shade_indicator; break;
case MI_SNAP_V: indicator = scr->menu_snap_vertical_indicator; break;
case MI_SNAP_H: indicator = scr->menu_snap_horizontal_indicator; break;
case MI_CENTRAL: indicator = scr->menu_central_indicator; break;
case MI_SNAP_RH: indicator = scr->menu_snap_rh_indicator; break;
case MI_SNAP_LH: indicator = scr->menu_snap_lh_indicator; break;
case MI_SNAP_TH: indicator = scr->menu_snap_th_indicator; break;
case MI_SNAP_BH: indicator = scr->menu_snap_bh_indicator; break;
case MI_SNAP_TL: indicator = scr->menu_snap_tl_indicator; break;
case MI_SNAP_TR: indicator = scr->menu_snap_tr_indicator; break;
case MI_SNAP_BL: indicator = scr->menu_snap_bl_indicator; break;
case MI_SNAP_BR: indicator = scr->menu_snap_br_indicator; break;
case MI_SNAP_TILED: indicator = scr->menu_snap_tiled_indicator; break;
default: break;
}
if (indicator) {
int iw = indicator->width;
int ih = indicator->height;
XSetClipMask(dpy, scr->copy_gc, indicator->mask);
XSetClipOrigin(dpy, scr->copy_gc, 5, y + (h - ih) / 2);
if (selected)
XSetForeground(dpy, scr->copy_gc,
WMColorPixel(entry->flags.enabled ? scr->select_text_color : scr->dtext_color));
else
XSetForeground(dpy, scr->copy_gc,
WMColorPixel(entry->flags.enabled ? scr->mtext_color : scr->dtext_color));
XFillRectangle(dpy, win, scr->copy_gc, 5, y + (h - ih) / 2, iw, ih);
XSetClipMask(dpy, scr->copy_gc, None);
}
}
/* Draw right text */
if (entry->rtext && entry->cascade < 0) {
int tw = WMWidthOfString(scr->menu_entry_font, entry->rtext, strlen(entry->rtext));
tw = WMWidthOfString(scr->menu_entry_font, entry->rtext, strlen(entry->rtext));
WMDrawString(scr->wmscreen, win, color, scr->menu_entry_font,
w - 8 - tw, y + 3, entry->rtext, strlen(entry->rtext));
w - 6 - tw, y + 3 + wPreferences.menu_text_clearance,
entry->rtext, strlen(entry->rtext));
}
}
static void x11_menu_draw_frame(WScreen *scr, WNativeWindow win, int y, int w, int h, int type)