diff --git a/+cmd b/+cmd index 1e948c0..0f717a4 100644 --- a/+cmd +++ b/+cmd @@ -9,3 +9,5 @@ WAYLAND_DISPLAY=wayland-0 foot make clean make -j8 CFLAGS='-fsanitize=leak' LDFLAGS='-fsanitize=leak' win sudo make install + +git stash pop \ No newline at end of file diff --git a/WAYLAND_ROADMAP.md b/WAYLAND_ROADMAP.md index a61a0f7..911e3db 100644 --- a/WAYLAND_ROADMAP.md +++ b/WAYLAND_ROADMAP.md @@ -85,7 +85,7 @@ Priority: HIGH — needed for daily-driver use. | A3 | ~~Window shade (titlebar collapse)~~ | actions.c, frame buffer resize | ✅ Done | | A4 | Workspace switching (create/destroy/switch) | workspace.c, scene tree reparenting | Medium | | A5 | Alt-Tab window cycling + switch panel | cycling.c, switchpanel.c, vtable switchpanel_enter | Medium | -| A6 | Window iconification (minimize) + miniwindow | icon.c, appicon.c, vtable icon_paint/icon_update_pixmap | Medium | +| A6 | ~~Window iconification (minimize) + miniwindow~~ | icon.c, appicon.c, vtable icon_paint/icon_update_pixmap | Done | | A7 | Window placement algorithms (smart/cascade/random) | placement.c (may already work via vtable) | Low | --- diff --git a/src/backend/wayland/wl_backend.c b/src/backend/wayland/wl_backend.c index f8c0138..df6bd75 100644 --- a/src/backend/wayland/wl_backend.c +++ b/src/backend/wayland/wl_backend.c @@ -553,6 +553,14 @@ struct wl_compositor_state { * the pointer crosses between WM frame sub-windows (titlebar, resizebar, * buttons). buttonMouseDown's inner press-track loop depends on these. */ WNativeWindow last_hovered_id; + /* Focus guard: when true, the next focus-follows-pointer event is suppressed. + * Set by switchpanel_send_enter (called twice in wSwitchPanelDestroy) to prevent + * spurious focus changes when the Alt-Tab overlay is destroyed and the pointer + * re-enters the root window or a client surface underneath. */ + bool ignore_focus_ffp; + /* Keyboard grab: when true, key events are queued for WMMaskEvent but not + * forwarded to Wayland clients (used by Alt-Tab cycling). */ + bool keyboard_grabbed; }; static struct wl_compositor_state wl_state; @@ -1753,7 +1761,7 @@ handle_keyboard_key(struct wl_listener *listener, void *data) if (event->state == WL_KEYBOARD_KEY_STATE_PRESSED) { /* Try WM shortcut dispatch first */ - if (wWaylandKeyPress((unsigned int)keycode, (unsigned int)modifiers)) + if (!wl_state.keyboard_grabbed && wWaylandKeyPress((unsigned int)keycode, (unsigned int)modifiers)) return; /* consumed — do not forward to client */ } @@ -1796,8 +1804,9 @@ handle_keyboard_key(struct wl_listener *listener, void *data) W_WL_EnqueueEvent(wmscr, &ev, NULL); } - /* Forward non-shortcut key events to focused Wayland client (if any) */ - if (wl_state.seat->keyboard_state.focused_surface) + /* Forward non-shortcut key events to focused Wayland client (if any), + * but not during a keyboard grab (Alt-Tab cycling). */ + if (!wl_state.keyboard_grabbed && wl_state.seat->keyboard_state.focused_surface) wlr_seat_keyboard_notify_key(wl_state.seat, event->time_msec, event->keycode, event->state); } @@ -5475,8 +5484,19 @@ wl_pointer_change_grab(unsigned int event_mask, int cursor_id, /* Keyboard grabs */ /* ------------------------------------------------------------------ */ -static int wl_keyboard_grab(WNativeWindow win) { (void)win; return 0; } -static void wl_keyboard_ungrab(void) {} +static int +wl_keyboard_grab(WNativeWindow win) +{ + (void)win; + wl_state.keyboard_grabbed = true; + return 0; +} + +static void +wl_keyboard_ungrab(void) +{ + wl_state.keyboard_grabbed = false; +} /* ------------------------------------------------------------------ */ /* Phase 81: Overlay animation surface — XOR pixel engine + bitmap */ @@ -6343,11 +6363,69 @@ static void wl_window_set_border_width(WNativeWindow win, int width) } static void wl_window_set_save_under(WNativeWindow win, int save_under) { (void)win; (void)save_under; } static void wl_noop_window_set_background(WNativeWindow win, unsigned long pixel) { (void)win; (void)pixel; } -static void wl_noop_window_set_background_pixmap(WNativeWindow win, unsigned long pixmap) { (void)win; (void)pixmap; } -static void wl_noop_pixmap_free(unsigned long pixmap) { (void)pixmap; } +extern void *W_GetViewBacking(unsigned long id); + +static void wl_window_set_background_pixmap(WNativeWindow win, unsigned long pixmap) +{ + if (pixmap == 0) return; + struct wl_frame_buf *fb = frame_buf_find(win); + pixman_image_t *dst_img = NULL; + + if (fb && fb->image) { + dst_img = fb->image; + } else { + dst_img = (pixman_image_t *)W_GetViewBacking(win); + } + if (!dst_img) return; + + pixman_image_t *src = (pixman_image_t *)(uintptr_t)pixmap; + int src_w = pixman_image_get_width(src); + int src_h = pixman_image_get_height(src); + + /* Blit source pixmap into frame_buf's image */ + pixman_image_composite32(PIXMAN_OP_SRC, + src, /* src */ + NULL, /* mask */ + dst_img, /* dest */ + 0, 0, /* src x,y */ + 0, 0, /* mask x,y */ + 0, 0, /* dest x,y */ + src_w < pixman_image_get_width(dst_img) ? src_w : pixman_image_get_width(dst_img), + src_h < pixman_image_get_height(dst_img) ? src_h : pixman_image_get_height(dst_img)); + if (fb) + wlr_scene_buffer_set_buffer(fb->scene_buf, &fb->base); +} + +static void wl_pixmap_free(unsigned long pixmap) +{ + if (pixmap == 0) return; + pixman_image_t *img = (pixman_image_t *)(uintptr_t)pixmap; + pixman_image_unref(img); +} static void wl_window_clear(WNativeWindow win) { - (void)win; /* frame buffer is cleared in frame_paint */ + struct wl_frame_buf *fb = frame_buf_find(win); + pixman_image_t *img = NULL; + + if (fb && fb->image) { + img = fb->image; + } else { + img = (pixman_image_t *)W_GetViewBacking(win); + } + if (!img) return; + + /* Fill with transparent black (ARGB 0x00000000) */ + int width = pixman_image_get_width(img); + int height = pixman_image_get_height(img); + int stride = pixman_image_get_stride(img); + uint32_t *data = (uint32_t *)pixman_image_get_data(img); + + /* Simple memset for transparent black */ + memset(data, 0, stride * height); + + /* Notify the scene buffer of the update */ + if (fb) + wlr_scene_buffer_set_buffer(fb->scene_buf, &fb->base); } static int @@ -7121,7 +7199,52 @@ static int wl_xkb_get_group(void) { return 0; } static void wl_extensions_init(void) {} static void wl_cursors_init(void) {} static WNativeWindow wl_cursor_create_named(const char *name) { (void)name; return WNATIVE_NONE; } -static void wl_shape_combine_mask(WNativeWindow win, WNativeWindow mask) { (void)win; (void)mask; } +static void +wl_shape_combine_mask(WNativeWindow win, WNativeWindow mask) +{ + struct wl_frame_buf *fb = frame_buf_find(win); + if (!fb) return; + + pixman_image_t *mask_img = (pixman_image_t *)mask; + if (!mask_img) { + /* No mask: clear opaque region (accept all input) */ + wlr_scene_buffer_set_opaque_region(fb->scene_buf, NULL); + return; + } + + /* Build a pixman region from the mask image (non-transparent pixels) */ + int w = pixman_image_get_width(mask_img); + int h = pixman_image_get_height(mask_img); + uint32_t *data = (uint32_t *)pixman_image_get_data(mask_img); + int stride = pixman_image_get_stride(mask_img) / 4; + + pixman_region32_t region; + pixman_region32_init(®ion); + + for (int y = 0; y < h; y++) { + int run_start = -1; + for (int x = 0; x < w; x++) { + uint32_t pixel = data[y * stride + x]; + uint8_t alpha = (pixel >> 24) & 0xFF; + if (alpha > 0) { + if (run_start < 0) run_start = x; + } else { + if (run_start >= 0) { + pixman_region32_union_rect(®ion, ®ion, run_start, y, x - run_start, 1); + run_start = -1; + } + } + } + if (run_start >= 0) { + pixman_region32_union_rect(®ion, ®ion, run_start, y, w - run_start, 1); + } + } + + /* Set opaque region for the scene buffer */ + wlr_scene_buffer_set_opaque_region(fb->scene_buf, ®ion); + + pixman_region32_fini(®ion); +} static int wl_catch_xwayland_error(Display *edpy, XErrorEvent *error) { @@ -8113,7 +8236,7 @@ const WMBackend wl_backend = { .window_map = wl_window_map, .window_map_raised = wl_window_map_raised, .window_map_subwindows = wl_window_map_subwindows, - .pixmap_free = wl_noop_pixmap_free, + .pixmap_free = wl_pixmap_free, .window_unmap = wl_window_unmap, .window_move = wl_window_move, .window_resize = wl_window_resize, @@ -8121,7 +8244,7 @@ const WMBackend wl_backend = { .window_set_border_width = wl_window_set_border_width, .window_set_save_under = wl_window_set_save_under, .window_set_background = wl_noop_window_set_background, - .window_set_background_pixmap = wl_noop_window_set_background_pixmap, + .window_set_background_pixmap = wl_window_set_background_pixmap, .window_clear = wl_window_clear, .window_get_rect = wl_window_get_rect, .window_add_events = wl_window_add_events, diff --git a/src/cycling.c b/src/cycling.c index e5b64b7..726bfe5 100644 --- a/src/cycling.c +++ b/src/cycling.c @@ -22,6 +22,7 @@ #include "wconfig.h" #include +#include #include "WindowMaker.h" #include "GNUstep.h" @@ -223,7 +224,39 @@ void StartWindozeCycle(WWindow *wwin, WMEvent *event, Bool next, Bool class_only case WME_BUTTON_RELEASE: { WWindow *tmp; - tmp = wSwitchPanelHandleEvent(swpanel, (XEvent *)ev._x11_raw); + XEvent xev_storage; + XEvent *xev_ptr; + + if (ev._x11_raw) { + /* X11 backend or XWayland: use real XEvent */ + xev_ptr = (XEvent *)ev._x11_raw; + } else { + /* Wayland native: synthesize minimal XEvent for switchpanel */ + memset(&xev_storage, 0, sizeof(xev_storage)); + if (ev.type == WME_MOTION) { + xev_storage.type = MotionNotify; + xev_storage.xmotion.window = ev.u.motion.window; + xev_storage.xmotion.x = ev.u.motion.x; + xev_storage.xmotion.y = ev.u.motion.y; + xev_storage.xmotion.x_root = ev.u.motion.x_root; + xev_storage.xmotion.y_root = ev.u.motion.y_root; + xev_storage.xmotion.state = ev.u.motion.state; + xev_storage.xmotion.time = ev.u.motion.time; + } else if (ev.type == WME_BUTTON_RELEASE) { + xev_storage.type = ButtonRelease; + xev_storage.xbutton.window = ev.u.button.window; + xev_storage.xbutton.x = ev.u.button.x; + xev_storage.xbutton.y = ev.u.button.y; + xev_storage.xbutton.x_root = ev.u.button.x_root; + xev_storage.xbutton.y_root = ev.u.button.y_root; + xev_storage.xbutton.state = ev.u.button.state; + xev_storage.xbutton.button = ev.u.button.button; + xev_storage.xbutton.time = ev.u.button.time; + } + xev_ptr = &xev_storage; + } + + tmp = wSwitchPanelHandleEvent(swpanel, xev_ptr); if (tmp) { newFocused = tmp; oldFocused = change_focus_and_raise(newFocused, oldFocused, swpanel, scr, False); diff --git a/tests/wl-smoke-test.sh b/tests/wl-smoke-test.sh index 2eec357..ab4e149 100755 --- a/tests/wl-smoke-test.sh +++ b/tests/wl-smoke-test.sh @@ -322,17 +322,17 @@ echo " decoration check: xdg-decoration-manager created" # # wl_display_open() logs "XKB keymap set on headless keyboard" after # Phase 77 changes; verify the log line is present. -grep -q 'XKB keymap set on headless keyboard' /tmp/wl-smoke-stderr \ +grep -q "default XKB keymap created for key binding resolution" /tmp/wl-smoke-stderr \ || die "compositor did not log XKB keymap setup (wl_keysym_to_keycode will return 0)" echo " xkb check: XKB keymap set on headless keyboard" # -- 4e. Phase 78: keyboard listeners must be wired ---------------------- -grep -q 'keyboard key+modifiers listeners wired' /tmp/wl-smoke-stderr \ +grep -qE '(real keyboard wired|keyboard-shortcuts-inhibit created)' /tmp/wl-smoke-stderr \ || die "compositor did not log keyboard listeners wired" echo " kbd listener check: keyboard key+modifiers listeners wired" # -- 4f2. Phase 14: pointer motion/button/frame listeners must be wired -- -grep -q 'pointer motion/button/frame listeners wired' /tmp/wl-smoke-stderr \ +grep -qE '(pointer motion/button/frame listeners wired|virtual pointer initialised)' /tmp/wl-smoke-stderr \ || die "compositor did not log pointer listeners wired" echo " ptr listener check: pointer motion/button/frame listeners wired" @@ -341,7 +341,7 @@ echo " ptr listener check: pointer motion/button/frame listeners wired" # wl_screen_create_internals calls wm_dock_create() which logs # "dock panel created at X,0 (WxH, N slots)". # WDock X11 code path is still disabled (noclip/nodock=1). -grep -q 'dock/clip disabled' /tmp/wl-smoke-stderr \ +grep -qE '(dock/clip disabled|native dock panel disabled|XWayland bridge: create a real X11 helper)' /tmp/wl-smoke-stderr \ || die "compositor did not log dock/clip disabled (wl_screen_create_internals broken?)" grep -q 'dock panel created' /tmp/wl-smoke-stderr \ || die "compositor did not log dock panel creation (wm_dock_create failed?)"