/* event.c- event loop and handling * * Window Maker window manager * * Copyright (c) 1997-2003 Alfredo K. Kojima * Copyright (c) 2014-2023 Window Maker Team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "wconfig.h" #ifdef HAVE_INOTIFY #include #include #endif #include #include #include #include #include #include #include #include "WindowMaker.h" #include "window.h" #include "actions.h" #include "client.h" #include "main.h" #include "cycling.h" #include "keybind.h" #include "application.h" #include "stacking.h" #include "defaults.h" #include "workspace.h" #include "dock.h" #include "framewin.h" #include "properties.h" #include "balloon.h" #include "xinerama.h" #include "rootmenu.h" #include "colormap.h" #include "screen.h" #include "shutdown.h" #include "misc.h" #include "event.h" #include "winmenu.h" #include "switchmenu.h" #include "wsmap.h" #define MOD_MASK wPreferences.modifier_mask /************ Local stuff ***********/ static void saveTimestamp(WMEvent *event); static void handleColormapNotify(WMEvent *event); static void handleMapNotify(WMEvent *event); static void handleUnmapNotify(WMEvent *event); static void handleButtonPress(WMEvent *event); static void handleExpose(WMEvent *event); static void handleDestroyNotify(WMEvent *event); static void handleConfigureRequest(WMEvent *event); static void handleMapRequest(WMEvent *event); static void handlePropertyNotify(WMEvent *event); static void handleEnterNotify(WMEvent *event); static void handleLeaveNotify(WMEvent *event); static void handleExtensions(WMEvent *event); static void handleClientMessage(WMEvent *event); static void handleKeyPress(WMEvent *event); static void handleFocusIn(WMEvent *event); static void handleMotionNotify(WMEvent *event); static void handleVisibilityNotify(WMEvent *event); /* inotify handling moved to x11_backend.c (x11_event_loop_run) */ static void wdelete_death_handler(WMagicNumber id); /* real dead process handler */ static void handleDeadProcess(void); typedef struct DeadProcesses { pid_t pid; unsigned char exit_status; } DeadProcesses; /* stack of dead processes */ static DeadProcesses deadProcesses[MAX_DEAD_PROCESSES]; static int deadProcessPtr = 0; typedef struct DeathHandler { WDeathHandler *callback; pid_t pid; void *client_data; } DeathHandler; static WMArray *deathHandlers = NULL; WMagicNumber wAddDeathHandler(pid_t pid, WDeathHandler * callback, void *cdata) { DeathHandler *handler; handler = malloc(sizeof(DeathHandler)); if (!handler) return 0; handler->pid = pid; handler->callback = callback; handler->client_data = cdata; if (!deathHandlers) deathHandlers = WMCreateArrayWithDestructor(8, free); WMAddToArray(deathHandlers, handler); return handler; } static void wdelete_death_handler(WMagicNumber id) { DeathHandler *handler = (DeathHandler *) id; if (!handler || !deathHandlers) return; /* array destructor will call free(handler) */ WMRemoveFromArray(deathHandlers, handler); } void DispatchEvent(WMEvent * event) { if (deathHandlers) handleDeadProcess(); if (WCHECK_STATE(WSTATE_NEED_EXIT)) { WCHANGE_STATE(WSTATE_EXITING); /* received SIGTERM */ /* * WMHandleEvent() can't be called from anything * executed inside here, or we can get in a infinite * recursive loop. * * In Wayland mode, calling wl_display_destroy() from a signal * handler while wl_event_loop_dispatch() is active causes a * double-free (glibc detects it as SIGABRT). Instead, write a * byte to the self-pipe; the event loop reads it and calls * wl_display_terminate() from within loop context. EventLoop() * then calls Exit(0) after wl_display_run() returns. */ wm_backend->event_handle_signal_exit(); } else if (WCHECK_STATE(WSTATE_NEED_RESTART)) { WCHANGE_STATE(WSTATE_RESTARTING); Shutdown(WSRestartPreparationMode); /* received SIGHUP */ Restart(NULL, True); } else if (WCHECK_STATE(WSTATE_NEED_REREAD)) { WCHANGE_STATE(WSTATE_NORMAL); wDefaultsCheckDomains(NULL); } /* for the case that all that is wanted to be dispatched is * the stuff above */ if (!event) return; saveTimestamp(event); switch (event->type) { case WME_MAP_REQUEST: handleMapRequest(event); break; case WME_KEY_PRESS: handleKeyPress(event); break; case WME_MOTION: handleMotionNotify(event); break; case WME_CONFIGURE_REQUEST: handleConfigureRequest(event); break; case WME_DESTROY: handleDestroyNotify(event); break; case WME_MAP: handleMapNotify(event); break; case WME_UNMAP: handleUnmapNotify(event); break; case WME_BUTTON_PRESS: handleButtonPress(event); break; case WME_EXPOSE: handleExpose(event); break; case WME_PROPERTY: handlePropertyNotify(event); break; case WME_ENTER: handleEnterNotify(event); break; case WME_LEAVE: handleLeaveNotify(event); break; case WME_CLIENT_MESSAGE: handleClientMessage(event); break; case WME_COLORMAP: handleColormapNotify(event); break; case WME_MAPPING: if (event->u.mapping.request == WM_MAPPING_KEYBOARD || event->u.mapping.request == WM_MAPPING_MODIFIER) wm_backend->keyboard_mapping_refresh(event); break; case WME_FOCUS_IN: handleFocusIn(event); break; case WME_VISIBILITY: handleVisibilityNotify(event); break; case WME_CONFIGURE: wm_backend->event_randr_update(event); break; case WME_SELECTION_REQUEST: wm_backend->handle_selection_request(event); break; case WME_SELECTION_CLEAR: wm_backend->handle_selection_clear(event); break; default: handleExtensions(event); break; } } /* *---------------------------------------------------------------------- * EventLoop- * Processes X and internal events indefinitely. * * Returns: * Never returns * * Side effects: * The LastTimestamp global variable is updated. * Calls inotifyGetEvents if defaults database changes. *---------------------------------------------------------------------- */ noreturn void EventLoop(void) { wm_backend->event_loop_run(); Exit(0); } /* *---------------------------------------------------------------------- * ProcessPendingEvents -- * Processes the events that are currently pending (at the time * this function is called) in the display's queue. * * Returns: * After the pending events that were present at the function call * are processed. * * Side effects: * Many -- whatever handling events may involve. * *---------------------------------------------------------------------- */ void ProcessPendingEvents(void) { WMEvent event; int count; wm_backend->event_sync(False); /* Take a snapshot of the event count in the queue */ count = wm_backend->event_pending(); while (count > 0 && wm_backend->event_pending()) { WMNextEvent(wScreenWithNumber(0)->wmscreen, &event); WMHandleEvent(&event); count--; } } Bool IsDoubleClick(WScreen * scr, WBackendEvent * event) { if ((scr->last_click_time > 0) && (event->u.button.time - scr->last_click_time <= wPreferences.dblclick_time) && (event->u.button.button == (unsigned int)scr->last_click_button) && (event->u.button.window == scr->last_click_window)) { scr->flags.next_click_is_not_double = 1; scr->last_click_time = 0; scr->last_click_window = event->u.button.window; return True; } return False; } void NotifyDeadProcess(pid_t pid, unsigned char status) { if (deadProcessPtr >= MAX_DEAD_PROCESSES - 1) { wwarning("stack overflow: too many dead processes"); return; } /* stack the process to be handled later, * as this is called from the signal handler */ deadProcesses[deadProcessPtr].pid = pid; deadProcesses[deadProcessPtr].exit_status = status; deadProcessPtr++; } static void handleDeadProcess(void) { DeathHandler *tmp; int i; for (i = 0; i < deadProcessPtr; i++) { wWindowDeleteSavedStatesForPID(deadProcesses[i].pid); } if (!deathHandlers) { deadProcessPtr = 0; return; } /* get the pids on the queue and call handlers */ while (deadProcessPtr > 0) { deadProcessPtr--; for (i = WMGetArrayItemCount(deathHandlers) - 1; i >= 0; i--) { tmp = WMGetFromArray(deathHandlers, i); if (!tmp) continue; if (tmp->pid == deadProcesses[deadProcessPtr].pid) { (*tmp->callback) (tmp->pid, deadProcesses[deadProcessPtr].exit_status, tmp->client_data); wdelete_death_handler(tmp); } } } } static void saveTimestamp(WMEvent * event) { /* * Never save WM_CURRENT_TIME as LastTimestamp because WM_CURRENT_TIME * it's not a real timestamp (it's the 0L constant) */ switch (event->type) { case WME_BUTTON_RELEASE: case WME_BUTTON_PRESS: w_global.timestamp.last_event = event->u.button.time; break; case WME_KEY_PRESS: case WME_KEY_RELEASE: w_global.timestamp.last_event = event->u.key.time; break; case WME_MOTION: w_global.timestamp.last_event = event->u.motion.time; break; case WME_PROPERTY: w_global.timestamp.last_event = event->u.property.time; break; case WME_ENTER: case WME_LEAVE: w_global.timestamp.last_event = event->u.crossing.time; break; case WME_SELECTION_CLEAR: case WME_SELECTION_REQUEST: case WME_SELECTION: w_global.timestamp.last_event = event->u.selection.time; if (event->type == WME_SELECTION) wm_backend->xdnd_process_selection(event); break; } } static int matchWindow(const void *item, const void *cdata) { return (((WFakeGroupLeader *) item)->origLeader == (WNativeWindow)(unsigned long) cdata); } static void handleExtensions(WMEvent * event) { wm_backend->event_handle_extensions(event); } static void handleMapRequest(WMEvent * ev) { WWindow *wwin; WScreen *scr = NULL; WNativeWindow window = ev->u.mapRequest.window; wwin = wWindowFor(window); if (wwin != NULL) { if (wwin->flags.shaded) { wUnshadeWindow(wwin); } /* deiconify window */ if (wwin->flags.miniaturized) { wDeiconifyWindow(wwin); } else if (wwin->flags.hidden) { WApplication *wapp = wApplicationOf(wwin->main_window); /* go to the last workspace that the user worked on the app */ if (wapp) { wWorkspaceChange(wwin->screen_ptr, wapp->last_workspace); } wUnhideApplication(wapp, False, False); } return; } scr = wScreenForRootWindow(ev->u.mapRequest.parent); wwin = wManageWindow(scr, window); /* * This is to let the Dock know that the application it launched * has already been mapped (eg: it has finished launching). * It is not necessary for normally docked apps, but is needed for * apps that were forcedly docked (like with dockit). */ if (scr->last_dock) { if (wwin && wwin->main_window != WM_NATIVE_NONE && wwin->main_window != window) wDockTrackWindowLaunch(scr->last_dock, wwin->main_window); else wDockTrackWindowLaunch(scr->last_dock, window); } if (wwin) { wClientSetState(wwin, WM_STATE_NORMAL, WM_NATIVE_NONE); if (wwin->flags.maximized) { wMaximizeWindow(wwin, wwin->flags.maximized, wGetHeadForWindow(wwin)); } if (wwin->flags.shaded) { wwin->flags.shaded = 0; wwin->flags.skip_next_animation = 1; wShadeWindow(wwin); } if (wwin->flags.miniaturized) { wwin->flags.miniaturized = 0; wwin->flags.skip_next_animation = 1; wIconifyWindow(wwin); } if (wwin->flags.fullscreen) { wwin->flags.fullscreen = 0; wFullscreenWindow(wwin); } if (wwin->flags.hidden) { WApplication *wapp = wApplicationOf(wwin->main_window); wwin->flags.hidden = 0; wwin->flags.skip_next_animation = 1; if (wapp) { wHideApplication(wapp); } } } } static void handleDestroyNotify(WMEvent * event) { WWindow *wwin; WApplication *app; WNativeWindow window = event->u.lifecycle.window; WScreen *scr = wScreenForRootWindow(event->u.lifecycle.event); int widx; wwin = wWindowFor(window); if (wwin) { wUnmanageWindow(wwin, False, True); } if (scr != NULL) { while ((widx = WMFindInArray(scr->fakeGroupLeaders, matchWindow, (void *)window)) != WANotFound) { WFakeGroupLeader *fPtr; fPtr = WMGetFromArray(scr->fakeGroupLeaders, widx); if (fPtr->retainCount > 0) { fPtr->retainCount--; if (fPtr->retainCount == 0 && fPtr->leader != WM_NATIVE_NONE) { wm_backend->frame_destroy(NULL, (WNativeWindow)fPtr->leader); fPtr->leader = WM_NATIVE_NONE; wm_backend->event_flush(); } } fPtr->origLeader = WM_NATIVE_NONE; } } app = wApplicationOf(window); if (app) { if (window == app->main_window) { app->refcount = 0; wwin = app->main_window_desc->screen_ptr->focused_window; while (wwin) { if (wwin->main_window == window) { wwin->main_window = WM_NATIVE_NONE; } wwin = wwin->prev; } } wApplicationDestroy(app); } } static void handleExpose(WMEvent * event) { WObjDescriptor *desc; wm_backend->event_flush_window((WNativeWindow)event->u.expose.window, WME_EXPOSE); desc = wm_backend->context_find(event->u.expose.window, WM_CTX_CLIENT_WIN); if (!desc) { return; } if (desc->handle_expose) { (*desc->handle_expose) (desc, event); } } static void executeWheelAction(WScreen *scr, WMEvent *event, int action) { WWindow *wwin; Bool next_direction; if (event->u.button.button == Button5 || event->u.button.button == Button6) next_direction = False; else next_direction = True; switch (action) { case WA_SWITCH_WORKSPACES: if (next_direction) wWorkspaceRelativeChange(scr, 1); else wWorkspaceRelativeChange(scr, -1); break; case WA_SWITCH_WINDOWS: wwin = scr->focused_window; if (next_direction) wWindowFocusNext(wwin, True); else wWindowFocusPrev(wwin, True); break; } } static void executeButtonAction(WScreen *scr, WMEvent *event, int action) { WWindow *wwin; switch (action) { case WA_SELECT_WINDOWS: wUnselectWindows(scr); wSelectWindows(scr, event->u.button.x_root, event->u.button.y_root); if (wPreferences.close_rootmenu_left_right_click){ WMenu *menu = NULL; WMPropList *definition; menu = scr->root_menu; if (scr->root_menu){ wMenuDestroy(menu,True); scr->root_menu = NULL; definition = w_global.domain.root_menu->dictionary; menu = configureMenu(scr, definition); scr->root_menu = menu; } } break; case WA_OPEN_APPMENU: OpenRootMenu(scr, event->u.button.x_root, event->u.button.y_root, False); /* ugly hack */ if (scr->root_menu) { if (scr->root_menu->brother->flags.mapped) event->u.button.window = scr->root_menu->brother->frame->core->window; else event->u.button.window = scr->root_menu->frame->core->window; } break; case WA_OPEN_WINLISTMENU: OpenSwitchMenu(scr, event->u.button.x_root, event->u.button.y_root, False); if (scr->switch_menu) { if (scr->switch_menu->brother->flags.mapped) event->u.button.window = scr->switch_menu->brother->frame->core->window; else event->u.button.window = scr->switch_menu->frame->core->window; } break; case WA_MOVE_PREVWORKSPACE: wWorkspaceRelativeChange(scr, -1); break; case WA_MOVE_NEXTWORKSPACE: wWorkspaceRelativeChange(scr, 1); break; case WA_MOVE_PREVWINDOW: wwin = scr->focused_window; wWindowFocusPrev(wwin, True); break; case WA_MOVE_NEXTWINDOW: wwin = scr->focused_window; wWindowFocusNext(wwin, True); break; } } /* bindable */ static void handleButtonPress(WMEvent * event) { WObjDescriptor *desc; WScreen *scr; scr = wScreenForRootWindow(event->u.button.window); if (!scr) scr = wScreenWithNumber(0); #ifdef BALLOON_TEXT wBalloonHide(scr); #endif if (!wPreferences.disable_root_mouse && event->u.button.window == scr->root_win) { if (event->u.button.button == Button1 && wPreferences.mouse_button1 != WA_NONE) { executeButtonAction(scr, event, wPreferences.mouse_button1); } else if (event->u.button.button == Button2 && wPreferences.mouse_button2 != WA_NONE) { executeButtonAction(scr, event, wPreferences.mouse_button2); } else if (event->u.button.button == Button3 && wPreferences.mouse_button3 != WA_NONE) { executeButtonAction(scr, event, wPreferences.mouse_button3); } else if (event->u.button.button == Button8 && wPreferences.mouse_button8 != WA_NONE) { executeButtonAction(scr, event, wPreferences.mouse_button8); }else if (event->u.button.button == Button9 && wPreferences.mouse_button9 != WA_NONE) { executeButtonAction(scr, event, wPreferences.mouse_button9); } else if (event->u.button.button == Button4 && wPreferences.mouse_wheel_scroll != WA_NONE) { executeWheelAction(scr, event, wPreferences.mouse_wheel_scroll); } else if (event->u.button.button == Button5 && wPreferences.mouse_wheel_scroll != WA_NONE) { executeWheelAction(scr, event, wPreferences.mouse_wheel_scroll); } else if (event->u.button.button == Button6 && wPreferences.mouse_wheel_tilt != WA_NONE) { executeWheelAction(scr, event, wPreferences.mouse_wheel_tilt); } else if (event->u.button.button == Button7 && wPreferences.mouse_wheel_tilt != WA_NONE) { executeWheelAction(scr, event, wPreferences.mouse_wheel_tilt); } } desc = wm_backend->context_find(event->u.button.window, WM_CTX_CLIENT_WIN); if (!desc) desc = wm_backend->context_find(event->u.button.window, WM_CTX_CLIENT_WIN); if (!desc) return; if (desc->parent_type == WCLASS_WINDOW) { wm_backend->event_sync(0); if (event->u.button.state & ( MOD_MASK | WM_MOD_CONTROL )) { wm_backend->event_allow_async(); } else { /* if (wPreferences.focus_mode == WKF_CLICK) { */ if (wPreferences.ignore_focus_click) { wm_backend->event_allow_async(); } wm_backend->event_replay_pointer(); /* } */ } wm_backend->event_sync(0); } else if (desc->parent_type == WCLASS_APPICON || desc->parent_type == WCLASS_MINIWINDOW || desc->parent_type == WCLASS_DOCK_ICON) { if (event->u.button.state & MOD_MASK) { wm_backend->event_sync(0); wm_backend->event_allow_async(); wm_backend->event_sync(0); } } if (desc->handle_mousedown != NULL) { (*desc->handle_mousedown) (desc, event); } /* save double-click information */ if (scr->flags.next_click_is_not_double) { scr->flags.next_click_is_not_double = 0; } else { scr->last_click_time = event->u.button.time; scr->last_click_button = event->u.button.button; scr->last_click_window = event->u.button.window; } } static void handleMapNotify(WMEvent * event) { WWindow *wwin; wwin = wWindowFor(event->u.lifecycle.event); if (wwin && wwin->client_win == event->u.lifecycle.event) { if (wwin->flags.miniaturized) { wDeiconifyWindow(wwin); } else { wm_backend->server_grab(); wWindowMap(wwin); wClientSetState(wwin, WM_STATE_NORMAL, WM_NATIVE_NONE); wm_backend->server_ungrab(); } } } static void handleUnmapNotify(WMEvent * event) { WWindow *wwin; Bool withdraw = False; /* only process windows with StructureNotify selected * (ignore SubstructureNotify) */ wwin = wWindowFor(event->u.lifecycle.window); if (!wwin) return; /* whether the event is a Withdrawal request */ if (event->u.lifecycle.event == wwin->screen_ptr->root_win) { withdraw = True; } if (wwin->client_win != event->u.lifecycle.window && !withdraw) return; if (!wwin->flags.mapped && !withdraw && WIN_WORKSPACE(wwin) == wwin->screen_ptr->current_workspace && !wwin->flags.miniaturized && !wwin->flags.hidden) return; wm_backend->server_grab(); wm_backend->window_unmap((WNativeWindow)wwin->frame->core->window); wwin->flags.mapped = 0; wm_backend->event_sync(0); /* If a destroy event for the client window is already pending, skip * the unmanage here — handleDestroyNotify will run it on the next * event-loop iteration. (Pre-Phase-30 code dispatched the destroy * synchronously here via x11_translate_event(); that path used an * uninitialized XEvent and never actually carried the destroy data, * so it became dead code. Letting the destroy flow through the * normal dispatch is the correct fix.) */ if (!wm_backend->event_check_window((WNativeWindow)wwin->client_win, WME_DESTROY)) { Bool reparented = False; if (wm_backend->event_check_window((WNativeWindow)wwin->client_win, WME_REPARENT)) reparented = True; /* withdraw window */ wwin->flags.mapped = 0; if (!reparented) wClientSetState(wwin, WM_STATE_WITHDRAWN, WM_NATIVE_NONE); /* if the window was reparented, do not reparent it back to the * root window */ wUnmanageWindow(wwin, !reparented, False); } wm_backend->server_ungrab(); } static void handleConfigureRequest(WMEvent * event) { WWindow *wwin; wwin = wWindowFor(event->u.configureRequest.window); if (wwin == NULL) { /* * Configure request for unmapped window */ wClientConfigure(NULL, event); } else { wClientConfigure(wwin, event); } } static void handlePropertyNotify(WMEvent * event) { WWindow *wwin; WApplication *wapp; WNativeWindow jr; int ji; unsigned int ju; wwin = wWindowFor(event->u.property.window); if (wwin) { if (!wm_backend->window_exists((WNativeWindow)wwin->client_win)) { return; } wClientCheckProperty(wwin, event); } wapp = wApplicationOf(event->u.property.window); if (wapp) { wClientCheckProperty(wapp->main_window_desc, event); } } static void handleClientMessage(WMEvent * event) { WWindow *wwin; WObjDescriptor *desc; /* handle transition from Normal to Iconic state */ if (event->u.clientMessage.message_type == w_global.atom.wm.change_state && event->u.clientMessage.format == 32 && event->u.clientMessage.data.l[0] == WM_STATE_ICONIC) { wwin = wWindowFor(event->u.clientMessage.window); if (!wwin) return; if (!wwin->flags.miniaturized) wIconifyWindow(wwin); } else if (event->u.clientMessage.message_type == w_global.atom.wm.colormap_notify && event->u.clientMessage.format == 32) { WScreen *scr = wScreenForRootWindow(event->u.clientMessage.window); if (!scr) return; if (event->u.clientMessage.data.l[1] == 1) { /* starting */ wColormapAllowClientInstallation(scr, True); } else { /* stopping */ wColormapAllowClientInstallation(scr, False); } } else if (event->u.clientMessage.message_type == w_global.atom.wmaker.command) { char *command; size_t len; len = sizeof(event->u.clientMessage.data.b); command = wmalloc(len + 1); strncpy(command, event->u.clientMessage.data.b, len); if (strncmp(command, "Reconfigure", sizeof("Reconfigure")) == 0) { wwarning(_("Got Reconfigure command")); wDefaultsCheckDomains(NULL); } else { wwarning(_("Got unknown command %s"), command); } wfree(command); } else if (event->u.clientMessage.message_type == w_global.atom.wmaker.wm_function) { WApplication *wapp; int done = 0; wapp = wApplicationOf(event->u.clientMessage.window); if (wapp) { switch (event->u.clientMessage.data.l[0]) { case WMFHideOtherApplications: wHideOtherApplications(wapp->main_window_desc); done = 1; break; case WMFHideApplication: wHideApplication(wapp); done = 1; break; } } if (!done) { wwin = wWindowFor(event->u.clientMessage.window); if (wwin) { switch (event->u.clientMessage.data.l[0]) { case WMFHideOtherApplications: wHideOtherApplications(wwin); break; case WMFHideApplication: wHideApplication(wApplicationOf(wwin->main_window)); break; } } } } else if (event->u.clientMessage.message_type == w_global.atom.gnustep.wm_attr) { wwin = wWindowFor(event->u.clientMessage.window); if (!wwin) return; switch (event->u.clientMessage.data.l[0]) { case GSWindowLevelAttr: { int level = (int)event->u.clientMessage.data.l[1]; if (WINDOW_LEVEL(wwin) != level) { ChangeStackingLevel(wwin->frame->core, level); } } break; } } else if (event->u.clientMessage.message_type == w_global.atom.gnustep.titlebar_state) { wwin = wWindowFor(event->u.clientMessage.window); if (!wwin) return; switch (event->u.clientMessage.data.l[0]) { case WMTitleBarNormal: wFrameWindowChangeState(wwin->frame, WS_UNFOCUSED); break; case WMTitleBarMain: wFrameWindowChangeState(wwin->frame, WS_PFOCUSED); break; case WMTitleBarKey: wFrameWindowChangeState(wwin->frame, WS_FOCUSED); break; } } else if (event->u.clientMessage.message_type == w_global.atom.wm.ignore_focus_events) { WScreen *scr = wScreenForRootWindow(event->u.clientMessage.window); if (!scr) return; scr->flags.ignore_focus_events = event->u.clientMessage.data.l[0] ? 1 : 0; } else if (wm_backend->netwm_process_client_message(event)) { /* do nothing */ } else if (wm_backend->xdnd_process_client_message(event)) { /* do nothing */ } else { /* * Non-standard thing, but needed by OffiX DND. * For when the icon frame gets a ClientMessage * that should have gone to the icon_window. */ if ((desc = wm_backend->context_find(event->u.clientMessage.window, WM_CTX_CLIENT_WIN)) != NULL) { struct WIcon *icon = NULL; if (desc->parent_type == WCLASS_MINIWINDOW) { icon = (WIcon *) desc->parent; } else if (desc->parent_type == WCLASS_DOCK_ICON || desc->parent_type == WCLASS_APPICON) { icon = ((WAppIcon *) desc->parent)->icon; } if (icon && (wwin = icon->owner)) { if (wwin->client_win != event->u.clientMessage.window) wm_backend->client_message_forward(event, wwin->client_win); } } } } static void raiseWindow(WScreen * scr) { WWindow *wwin; scr->autoRaiseTimer = NULL; wwin = wWindowFor(scr->autoRaiseWindow); if (!wwin) return; if (!wwin->flags.destroyed && wwin->flags.focused) { wRaiseFrame(wwin->frame->core); /* this is needed or a race condition will occur */ wm_backend->event_sync(False); } } static void handleEnterNotify(WMEvent * event) { WWindow *wwin; WObjDescriptor *desc = NULL; WScreen *scr = wScreenForRootWindow(event->u.crossing.window); /* * If a Leave event for this window is already pending, the pointer * has already left — skip the enter handler. Pre-Phase-30 code * peeked the leave event details and only skipped when mode/detail * matched; that path went through an x11_translate_event() with an * uninitialized XEvent and was therefore dead code (always reading * stack garbage). Treating any pending Leave as cause to skip is * the conservative correct behaviour. */ if (wm_backend->event_check_window((WNativeWindow)event->u.crossing.window, WME_LEAVE)) { return; } desc = wm_backend->context_find(event->u.crossing.window, WM_CTX_CLIENT_WIN); if (desc) { if (desc->handle_enternotify) { (*desc->handle_enternotify) (desc, event); } } /* enter to window */ wwin = wWindowFor(event->u.crossing.window); if (!wwin) { if (wPreferences.colormap_mode == WCM_POINTER) { wm_backend->colormap_install(scr, NULL); } if (scr->autoRaiseTimer && event->u.crossing.window == event->u.crossing.window) { WMDeleteTimerHandler(scr->autoRaiseTimer); scr->autoRaiseTimer = NULL; } } else { /* set auto raise timer even if in focus-follows-mouse mode * and the event is for the frame window, even if the window * has focus already. useful if you move the pointer from a focused * window to the root window and back pretty fast * * set focus if in focus-follows-mouse mode and the event * is for the frame window and window doesn't have focus yet */ if (wPreferences.focus_mode == WKF_SLOPPY && wwin->frame->core->window == event->u.crossing.window && !scr->flags.doing_alt_tab) { if (!wwin->flags.focused && !WFLAGP(wwin, no_focusable)) wSetFocusTo(scr, wwin); if (scr->autoRaiseTimer) WMDeleteTimerHandler(scr->autoRaiseTimer); scr->autoRaiseTimer = NULL; if (wPreferences.raise_delay && !WFLAGP(wwin, no_focusable)) { scr->autoRaiseWindow = wwin->frame->core->window; scr->autoRaiseTimer = WMAddTimerHandler(wPreferences.raise_delay, (WMCallback *) raiseWindow, scr); } } /* Install colormap for window, if the colormap installation mode * is colormap_follows_mouse */ if (wPreferences.colormap_mode == WCM_POINTER) { if (wwin->client_win == event->u.crossing.window) wm_backend->colormap_install(scr, wwin); else wm_backend->colormap_install(scr, NULL); } } if (event->u.crossing.window == event->u.crossing.window && event->u.crossing.detail == WM_NOTIFY_NORMAL && event->u.crossing.detail != WM_NOTIFY_INFERIOR && wPreferences.focus_mode != WKF_CLICK) { wSetFocusTo(scr, scr->focused_window); } #ifdef BALLOON_TEXT wBalloonEnteredObject(scr, desc); #endif } static void handleLeaveNotify(WMEvent * event) { WObjDescriptor *desc = NULL; desc = wm_backend->context_find(event->u.crossing.window, WM_CTX_CLIENT_WIN); if (desc) { if (desc->handle_leavenotify) { (*desc->handle_leavenotify) (desc, event); } } } static void handleColormapNotify(WMEvent * event) { WWindow *wwin; WScreen *scr; Bool reinstall = False; wwin = wWindowFor(event->u.colormap.window); if (!wwin) return; scr = wwin->screen_ptr; do { if (wwin) { if (event->u.colormap.c_new) { WMNativeColormap cmap = 0; wm_backend->window_get_colormap((WNativeWindow)wwin->client_win, &cmap); if (wwin == scr->cmap_window && wwin->cmap_window_no == 0) scr->current_colormap = cmap; reinstall = True; } else if (event->u.colormap.state == ColormapUninstalled && scr->current_colormap == event->u.colormap.colormap) { /* some bastard app (like XV) removed our colormap */ reinstall = False; } else if (event->u.colormap.state == ColormapInstalled && scr->current_colormap == event->u.colormap.colormap) { /* someone has put our colormap back */ reinstall = False; } } } while (wm_backend->event_check_type(WME_COLORMAP, event) && ((wwin = wWindowFor(event->u.colormap.window)) || 1)); if (reinstall && scr->current_colormap != WM_NATIVE_NONE) { if (!scr->flags.colormap_stuff_blocked) wm_backend->colormap_install(scr, NULL); } } static void handleFocusIn(WMEvent * event) { WWindow *wwin; /* * For applications that like stealing the focus. */ wm_backend->event_flush_type(WME_FOCUS_IN); saveTimestamp(event); if (event->u.focus.mode == WM_NOTIFY_UNGRAB || event->u.focus.mode == WM_NOTIFY_GRAB || event->u.focus.detail > WM_NOTIFY_NONLINEAR_VIRTUAL) { return; } wwin = wWindowFor(event->u.focus.window); if (wwin && !wwin->flags.focused) { if (wwin->flags.mapped) wSetFocusTo(wwin->screen_ptr, wwin); else wSetFocusTo(wwin->screen_ptr, NULL); } else if (!wwin) { WScreen *scr = wScreenForWindow(event->u.focus.window); if (scr) wSetFocusTo(scr, NULL); } } static WWindow *windowUnderPointer(WScreen * scr) { unsigned int mask; int foo; WNativeWindow bar, win; win = (WNativeWindow)wm_backend->pointer_query_window((WNativeWindow)scr->root_win); if (win) return wWindowFor(win); return NULL; } static int CheckFullScreenWindowFocused(WScreen * scr) { if (scr->focused_window && scr->focused_window->flags.fullscreen) return 1; else return 0; } /* * Phase 78: Wayland keyboard event dispatch. * Called from wl_backend's keyboard key listener with the XKB keycode and * modifier mask. Scans wKeyBindings and dispatches the matching WM action. * Returns 1 if consumed (WM shortcut), 0 if the event should be forwarded. */ int wWaylandKeyPress(unsigned int keycode, unsigned int modifiers) { WScreen *scr = wScreenWithNumber(0); if (!scr) return 0; WWindow *wwin = scr->focused_window; int modifiers_filtered = (int)(modifiers & (unsigned int)w_global.shortcut.modifiers_mask); int command = -1; short i; for (i = 0; i < WKBD_LAST; i++) { if (wKeyBindings[i].keycode == 0) continue; if ((unsigned int)wKeyBindings[i].keycode == keycode && wKeyBindings[i].modifier == modifiers_filtered) { command = i; break; } } if (command < 0) return 0; /* not a WM shortcut */ /* Reuse the ISMAPPED/ISFOCUSED macros defined inside handleKeyPress below. */ #ifndef ISMAPPED #define ISMAPPED(w) ((w) && !(w)->flags.miniaturized && ((w)->flags.mapped || (w)->flags.shaded)) #define ISFOCUSED(w) ((w) && (w)->flags.focused) #endif /* Dispatch WM shortcuts — identical to handleKeyPress switch but without * XEvent arguments. Only workspace/window commands that don't need an * XEvent are included; pointer-position shortcuts use the virtual pointer. */ switch (command) { case WKBD_MINIATURIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { CloseWindowMenu(scr); wIconifyWindow(wwin); } break; case WKBD_HIDE: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { WApplication *wapp = wApplicationOf(wwin->main_window); CloseWindowMenu(scr); if (wapp && !WFLAGP(wapp->main_window_desc, no_appicon)) wHideApplication(wapp); } break; case WKBD_MAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { CloseWindowMenu(scr); wMaximizeWindow(wwin, MAX_VERTICAL | MAX_HORIZONTAL, wGetHeadForWindow(wwin)); } break; case WKBD_VMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { CloseWindowMenu(scr); wMaximizeWindow(wwin, MAX_VERTICAL, wGetHeadForWindow(wwin)); } break; case WKBD_HMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { CloseWindowMenu(scr); wMaximizeWindow(wwin, MAX_HORIZONTAL, wGetHeadForWindow(wwin)); } break; case WKBD_RAISE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && wwin->frame) { CloseWindowMenu(scr); wRaiseFrame(wwin->frame->core); } break; case WKBD_LOWER: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && wwin->frame) { CloseWindowMenu(scr); wLowerFrame(wwin->frame->core); } break; case WKBD_CLOSE: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { CloseWindowMenu(scr); if (wwin->protocols.DELETE_WINDOW && !WFLAGP(wwin, kill_close)) wClientSendProtocol(wwin, w_global.atom.wm.delete_window, w_global.timestamp.last_event); } break; case WKBD_FOCUSNEXT: wWindowFocusNext(wwin, True); break; case WKBD_FOCUSPREV: wWindowFocusPrev(wwin, True); break; case WKBD_WORKSPACE1: case WKBD_WORKSPACE2: case WKBD_WORKSPACE3: case WKBD_WORKSPACE4: case WKBD_WORKSPACE5: case WKBD_WORKSPACE6: case WKBD_WORKSPACE7: case WKBD_WORKSPACE8: case WKBD_WORKSPACE9: case WKBD_WORKSPACE10: { int widx2 = command - WKBD_WORKSPACE1; int ws = (scr->current_workspace / 10) * 10 + widx2; if (wPreferences.ws_advance || ws < scr->workspace_count) wWorkspaceChange(scr, ws); } break; case WKBD_NEXTWORKSPACE: wWorkspaceRelativeChange(scr, 1); break; case WKBD_PREVWORKSPACE: wWorkspaceRelativeChange(scr, -1); break; default: /* Complex shortcuts that need XEvent (pointer coords, menus) are * not dispatched here; return 0 so the caller forwards the key. */ return 0; } return 1; /* consumed */ } static void handleKeyPress(WMEvent * event) { WScreen *scr = wScreenForRootWindow(event->window); WWindow *wwin = scr->focused_window; short i, widx; int modifiers; int command = -1; /* ignore CapsLock */ modifiers = event->u.key.state & w_global.shortcut.modifiers_mask; for (i = 0; i < WKBD_LAST; i++) { if (wKeyBindings[i].keycode == 0) continue; if (wKeyBindings[i].keycode == event->u.key.keycode && ( /*wKeyBindings[i].modifier==0 || */ wKeyBindings[i].modifier == modifiers)) { command = i; break; } } if (command < 0) { if (!wRootMenuPerformShortcut(event)) { static int dontLoop = 0; if (dontLoop > 10) { wwarning("problem with key event processing code"); return; } dontLoop++; /* if the focused window is an internal window, try redispatching * the event to the managed window, as it can be a WINGs window */ if (wwin && wwin->flags.internal_window && wwin->client_leader != WM_NATIVE_NONE) { /* client_leader contains the WINGs toplevel */ event->window = wwin->client_leader; WMHandleEvent(event); } dontLoop--; } return; } #define ISMAPPED(w) ((w) && !(w)->flags.miniaturized && ((w)->flags.mapped || (w)->flags.shaded)) #define ISFOCUSED(w) ((w) && (w)->flags.focused) switch (command) { case WKBD_ROOTMENU: /*OpenRootMenu(scr, event->u.key.x_root, event->u.key.y_root, True); */ if (!CheckFullScreenWindowFocused(scr)) { WMRect rect = wGetRectForHead(scr, wGetHeadForPointerLocation(scr)); OpenRootMenu(scr, rect.pos.x + rect.size.width / 2, rect.pos.y + rect.size.height / 2, True); } break; case WKBD_WINDOWLIST: if (!CheckFullScreenWindowFocused(scr)) { WMRect rect = wGetRectForHead(scr, wGetHeadForPointerLocation(scr)); OpenSwitchMenu(scr, rect.pos.x + rect.size.width / 2, rect.pos.y + rect.size.height / 2, True); } break; case WKBD_WINDOWMENU: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) if (wwin->frame) OpenWindowMenu(wwin, wwin->frame_x, wwin->frame_y + wwin->frame->top_width, True); break; case WKBD_MINIMIZEALL: CloseWindowMenu(scr); wHideAll(scr); break; case WKBD_MINIATURIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && !WFLAGP(wwin, no_miniaturizable)) { CloseWindowMenu(scr); if (wwin->protocols.MINIATURIZE_WINDOW) wClientSendProtocol(wwin, w_global.atom.gnustep.wm_miniaturize_window, event->u.key.time); else { wIconifyWindow(wwin); } } break; case WKBD_HIDE: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { WApplication *wapp = wApplicationOf(wwin->main_window); CloseWindowMenu(scr); if (wapp && !WFLAGP(wapp->main_window_desc, no_appicon)) { wHideApplication(wapp); } } break; case WKBD_HIDE_OTHERS: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { CloseWindowMenu(scr); wHideOtherApplications(wwin); } break; case WKBD_MAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_VERTICAL | MAX_HORIZONTAL | MAX_KEYBOARD); } break; case WKBD_VMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_VERTICAL | MAX_KEYBOARD); } break; case WKBD_HMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_HORIZONTAL | MAX_KEYBOARD); movePointerToWindowCenter(wwin); } break; case WKBD_CENTRAL: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_CENTRAL | MAX_KEYBOARD); movePointerToWindowCenter(wwin); } break; case WKBD_LHMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_VERTICAL | MAX_LEFTHALF | MAX_KEYBOARD); movePointerToWindowCenter(wwin); } break; case WKBD_RHMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_VERTICAL | MAX_RIGHTHALF | MAX_KEYBOARD); movePointerToWindowCenter(wwin); } break; case WKBD_THMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_HORIZONTAL | MAX_TOPHALF | MAX_KEYBOARD); movePointerToWindowCenter(wwin); } break; case WKBD_BHMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_HORIZONTAL | MAX_BOTTOMHALF | MAX_KEYBOARD); movePointerToWindowCenter(wwin); } break; case WKBD_LTCMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_LEFTHALF | MAX_TOPHALF | MAX_KEYBOARD); movePointerToWindowCenter(wwin); } break; case WKBD_RTCMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_RIGHTHALF | MAX_TOPHALF | MAX_KEYBOARD); movePointerToWindowCenter(wwin); } break; case WKBD_LBCMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_LEFTHALF | MAX_BOTTOMHALF | MAX_KEYBOARD); movePointerToWindowCenter(wwin); } break; case WKBD_RBCMAXIMIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_RIGHTHALF | MAX_BOTTOMHALF | MAX_KEYBOARD); movePointerToWindowCenter(wwin); } break; case WKBD_MAXIMUS: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && IS_RESIZABLE(wwin)) { CloseWindowMenu(scr); handleMaximize(wwin, MAX_MAXIMUS | MAX_KEYBOARD); } break; case WKBD_KEEP_ON_TOP: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && wwin->frame) { CloseWindowMenu(scr); if (wwin->frame->core->stacking->window_level != WMFloatingLevel) ChangeStackingLevel(wwin->frame->core, WMFloatingLevel); else ChangeStackingLevel(wwin->frame->core, WMNormalLevel); } break; case WKBD_KEEP_AT_BOTTOM: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && wwin->frame) { CloseWindowMenu(scr); if (wwin->frame->core->stacking->window_level != WMSunkenLevel) ChangeStackingLevel(wwin->frame->core, WMSunkenLevel); else ChangeStackingLevel(wwin->frame->core, WMNormalLevel); } break; case WKBD_OMNIPRESENT: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { CloseWindowMenu(scr); wWindowSetOmnipresent(wwin, !wwin->flags.omnipresent); } break; case WKBD_RAISE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && wwin->frame) { CloseWindowMenu(scr); wRaiseFrame(wwin->frame->core); } break; case WKBD_LOWER: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && wwin->frame) { CloseWindowMenu(scr); wLowerFrame(wwin->frame->core); } break; case WKBD_RAISELOWER: /* raise or lower the window under the pointer, not the * focused one */ wwin = windowUnderPointer(scr); if (wwin && wwin->frame) wRaiseLowerFrame(wwin->frame->core); break; case WKBD_SHADE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && !WFLAGP(wwin, no_shadeable)) { if (wwin->flags.shaded) wUnshadeWindow(wwin); else wShadeWindow(wwin); } break; case WKBD_MOVERESIZE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && (IS_RESIZABLE(wwin) || IS_MOVABLE(wwin))) { CloseWindowMenu(scr); wKeyboardMoveResizeWindow(wwin); } break; case WKBD_CLOSE: if (ISMAPPED(wwin) && ISFOCUSED(wwin) && !WFLAGP(wwin, no_closable)) { CloseWindowMenu(scr); if (wwin->protocols.DELETE_WINDOW) wClientSendProtocol(wwin, w_global.atom.wm.delete_window, event->u.key.time); } break; case WKBD_SELECT: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) { wSelectWindow(wwin, !wwin->flags.selected); } break; case WKBD_WORKSPACEMAP: if (wPreferences.enable_workspace_pager) StartWorkspaceMap(scr); break; case WKBD_FOCUSNEXT: StartWindozeCycle(wwin, event, True, False); break; case WKBD_FOCUSPREV: StartWindozeCycle(wwin, event, False, False); break; case WKBD_GROUPNEXT: StartWindozeCycle(wwin, event, True, True); break; case WKBD_GROUPPREV: StartWindozeCycle(wwin, event, False, True); break; case WKBD_WORKSPACE1 ... WKBD_WORKSPACE10: widx = command - WKBD_WORKSPACE1; i = (scr->current_workspace / 10) * 10 + widx; if (wPreferences.ws_advance || i < scr->workspace_count) wWorkspaceChange(scr, i); break; case WKBD_NEXTWORKSPACE: wWorkspaceRelativeChange(scr, 1); break; case WKBD_PREVWORKSPACE: wWorkspaceRelativeChange(scr, -1); break; case WKBD_LASTWORKSPACE: wWorkspaceChange(scr, scr->last_workspace); break; case WKBD_MOVE_WORKSPACE1 ... WKBD_MOVE_WORKSPACE10: widx = command - WKBD_MOVE_WORKSPACE1; i = (scr->current_workspace / 10) * 10 + widx; if (wwin && (wPreferences.ws_advance || i < scr->workspace_count)) wWindowChangeWorkspace(wwin, i); break; case WKBD_MOVE_NEXTWORKSPACE: if (wwin) wWindowChangeWorkspaceRelative(wwin, 1); break; case WKBD_MOVE_PREVWORKSPACE: if (wwin) wWindowChangeWorkspaceRelative(wwin, -1); break; case WKBD_MOVE_LASTWORKSPACE: if (wwin) wWindowChangeWorkspace(wwin, scr->last_workspace); break; case WKBD_MOVE_NEXTWSLAYER: case WKBD_MOVE_PREVWSLAYER: { if (wwin) { int row, column; row = scr->current_workspace / 10; column = scr->current_workspace % 10; if (command == WKBD_MOVE_NEXTWSLAYER) { if ((row + 1) * 10 < scr->workspace_count) wWindowChangeWorkspace(wwin, column + (row + 1) * 10); } else { if (row > 0) wWindowChangeWorkspace(wwin, column + (row - 1) * 10); } } } break; case WKBD_WINDOW1: case WKBD_WINDOW2: case WKBD_WINDOW3: case WKBD_WINDOW4: case WKBD_WINDOW5: case WKBD_WINDOW6: case WKBD_WINDOW7: case WKBD_WINDOW8: case WKBD_WINDOW9: case WKBD_WINDOW10: widx = command - WKBD_WINDOW1; if (scr->shortcutWindows[widx]) { WMArray *list = scr->shortcutWindows[widx]; int cw; int count = WMGetArrayItemCount(list); WWindow *twin; WMArrayIterator iter; WWindow *wwin; wUnselectWindows(scr); cw = scr->current_workspace; WM_ETARETI_ARRAY(list, wwin, iter) { if (count > 1) wWindowChangeWorkspace(wwin, cw); wMakeWindowVisible(wwin); if (count > 1) wSelectWindow(wwin, True); } /* rotate the order of windows, to create a cycling effect */ twin = WMGetFromArray(list, 0); WMDeleteFromArray(list, 0); WMAddToArray(list, twin); } else if (wwin && ISMAPPED(wwin) && ISFOCUSED(wwin)) { if (scr->shortcutWindows[widx]) { WMFreeArray(scr->shortcutWindows[widx]); scr->shortcutWindows[widx] = NULL; } if (wwin->flags.selected && scr->selected_windows) { scr->shortcutWindows[widx] = WMDuplicateArray(scr->selected_windows); /*WMRemoveFromArray(scr->shortcutWindows[index], wwin); WMInsertInArray(scr->shortcutWindows[index], 0, wwin); */ } else { scr->shortcutWindows[widx] = WMCreateArray(4); WMAddToArray(scr->shortcutWindows[widx], wwin); } wSelectWindow(wwin, !wwin->flags.selected); wm_backend->event_flush(); wusleep(3000); wSelectWindow(wwin, !wwin->flags.selected); wm_backend->event_flush(); } else if (scr->selected_windows && WMGetArrayItemCount(scr->selected_windows)) { if (wwin->flags.selected && scr->selected_windows) { if (scr->shortcutWindows[widx]) { WMFreeArray(scr->shortcutWindows[widx]); } scr->shortcutWindows[widx] = WMDuplicateArray(scr->selected_windows); } } break; case WKBD_MOVE_12_TO_6_HEAD: case WKBD_MOVE_6_TO_12_HEAD: if (wwin) moveBetweenHeads(wwin, command - WKBD_MOVE_12_TO_6_HEAD); break; case WKBD_RELAUNCH: if (ISMAPPED(wwin) && ISFOCUSED(wwin)) (void) RelaunchWindow(wwin); break; case WKBD_SWITCH_SCREEN: if (w_global.screen_count > 1) { WScreen *scr2; int i; /* find index of this screen */ for (i = 0; i < w_global.screen_count; i++) { if (wScreenWithNumber(i) == scr) break; } i++; if (i >= w_global.screen_count) { i = 0; } scr2 = wScreenWithNumber(i); if (scr2) { wm_backend->pointer_warp_to( scr2->scr_width / 2, scr2->scr_height / 2); } } break; case WKBD_RUN: { ExecuteInputCommand(scr, _("exec %A(Run, Type command:)")); break; } case WKBD_EXIT: { /* quick mode is not allowed to prevent inadvertently call */ ExecuteExitCommand(scr, 0); break; } case WKBD_PRINTS: { ScreenCapture(scr, PRINT_SCREEN); break; } case WKBD_PRINTW: { ScreenCapture(scr, PRINT_WINDOW); break; } case WKBD_PRINTP: { ScreenCapture(scr, PRINT_PARTIAL); break; } case WKBD_NEXTWSLAYER: case WKBD_PREVWSLAYER: { int row, column; row = scr->current_workspace / 10; column = scr->current_workspace % 10; if (command == WKBD_NEXTWSLAYER) { if ((row + 1) * 10 < scr->workspace_count) wWorkspaceChange(scr, column + (row + 1) * 10); } else { if (row > 0) wWorkspaceChange(scr, column + (row - 1) * 10); } } break; case WKBD_CLIPRAISELOWER: if (!wPreferences.flags.noclip) wDockRaiseLower(scr->workspaces[scr->current_workspace]->clip); break; case WKBD_DOCKRAISELOWER: if (!wPreferences.flags.nodock) wDockRaiseLower(scr->dock); break; #ifdef KEEP_XKB_LOCK_STATUS case WKBD_TOGGLE: if (wPreferences.modelock) { /*toggle */ wwin = scr->focused_window; if (wwin && wwin->flags.mapped && wwin->frame && WIN_WORKSPACE(wwin) == wwin->screen_ptr->current_workspace && !wwin->flags.miniaturized && !wwin->flags.hidden) { int cur_group = wm_backend->xkb_get_group(); wwin->frame->languagemode = wwin->frame->last_languagemode; wwin->frame->last_languagemode = cur_group; wm_backend->xkb_lock_group(wwin->frame->languagemode); } } break; #endif /* KEEP_XKB_LOCK_STATUS */ } } #define CORNER_NONE 0 #define CORNER_TOPLEFT 1 #define CORNER_TOPRIGHT 2 #define CORNER_BOTTOMLEFT 3 #define CORNER_BOTTOMRIGHT 4 static int get_corner(WMRect rect, WMPoint p) { if (p.x <= (rect.pos.x + wPreferences.hot_corner_edge) && p.y <= (rect.pos.y + wPreferences.hot_corner_edge)) return CORNER_TOPLEFT; if (p.x >= (rect.pos.x + rect.size.width - wPreferences.hot_corner_edge) && p.y <= (rect.pos.y + wPreferences.hot_corner_edge)) return CORNER_TOPRIGHT; if (p.x <= (rect.pos.x + wPreferences.hot_corner_edge) && p.y >= (rect.pos.y + rect.size.height - wPreferences.hot_corner_edge)) return CORNER_BOTTOMLEFT; if (p.x >= (rect.pos.x + rect.size.width - wPreferences.hot_corner_edge) && p.y >= (rect.pos.y + rect.size.height - wPreferences.hot_corner_edge)) return CORNER_BOTTOMRIGHT; return CORNER_NONE; } static void hotCornerDelay(void *data) { WScreen *scr = (WScreen *) data; if (scr->flags.in_hot_corner && wPreferences.hot_corner_actions[scr->flags.in_hot_corner - 1]) ExecuteShellCommand(scr, wPreferences.hot_corner_actions[scr->flags.in_hot_corner - 1]); WMDeleteTimerHandler(scr->hot_corner_timer); scr->hot_corner_timer = NULL; } static void handleMotionNotify(WMEvent *event) { if (wPreferences.scrollable_menus || wPreferences.hot_corners) { WScreen *scr = wScreenForRootWindow(event->window); WMPoint p = wmkpoint(event->u.motion.x_root, event->u.motion.y_root); WMRect rect = wGetRectForHead(scr, wGetHeadForPoint(scr, p)); if (wPreferences.hot_corners) { if (!scr->flags.in_hot_corner) { scr->flags.in_hot_corner = get_corner(rect, p); if (scr->flags.in_hot_corner && !scr->hot_corner_timer) scr->hot_corner_timer = WMAddTimerHandler(wPreferences.hot_corner_delay, hotCornerDelay, scr); } else { int out_hot_corner = 0; switch (scr->flags.in_hot_corner) { case CORNER_TOPLEFT: if ((p.x > (rect.pos.x + wPreferences.hot_corner_edge)) || (p.y > (rect.pos.y + wPreferences.hot_corner_edge))) out_hot_corner = 1; break; case CORNER_TOPRIGHT: if ((p.x < (rect.pos.x + rect.size.width - wPreferences.hot_corner_edge)) || (p.y > (rect.pos.y + wPreferences.hot_corner_edge))) out_hot_corner = 1; break; case CORNER_BOTTOMLEFT: if ((p.x > (rect.pos.x + wPreferences.hot_corner_edge)) || (p.y < (rect.pos.y + rect.size.height - wPreferences.hot_corner_edge))) out_hot_corner = 1; break; case CORNER_BOTTOMRIGHT: if ((p.x < (rect.pos.x + rect.size.width - wPreferences.hot_corner_edge)) || (p.y < (rect.pos.y + rect.size.height - wPreferences.hot_corner_edge))) out_hot_corner = 1; break; } if (out_hot_corner) { scr->flags.in_hot_corner = CORNER_NONE; if (scr->hot_corner_timer) { WMDeleteTimerHandler(scr->hot_corner_timer); scr->hot_corner_timer = NULL; } } } } if (wPreferences.scrollable_menus) { if (scr->flags.jump_back_pending || p.x <= (rect.pos.x + 1) || p.x >= (rect.pos.x + rect.size.width - 2) || p.y <= (rect.pos.y + 1) || p.y >= (rect.pos.y + rect.size.height - 2)) { WMenu *menu; menu = wMenuUnderPointer(scr); if (menu != NULL) wMenuScroll(menu); } } } } #undef CORNER_NONE #undef CORNER_TOPLEFT #undef CORNER_TOPRIGHT #undef CORNER_BOTTOMLEFT #undef CORNER_BOTTOMRIGHT static void handleVisibilityNotify(WMEvent * event) { WWindow *wwin; wwin = wWindowFor(event->u.visibility.window); if (!wwin) return; wwin->flags.obscured = (event->u.visibility.state == WM_VISIBILITY_FULLY_OBSCURED); } /* Selection request/clear handling moved to backend * (X11 backend: x11_handle_selection_request/clear; Wayland: no-op) */