/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ /* * Copyright (C) 2001 Havoc Pennington * Copyright (C) 2002, 2003, 2004 Red Hat, Inc. * Copyright (C) 2003, 2004 Rob Adams * Copyright (C) 2004-2006 Elijah Newren * * 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, see . */ #include "config.h" #include "x11/events.h" #include #include #include #include #include "bell.h" #include "display-private.h" #include "window-private.h" #include "workspace-private.h" #include "x11/window-x11.h" #include "x11/xprops.h" #ifdef HAVE_WAYLAND #include "wayland/meta-xwayland.h" #include "wayland/meta-wayland-private.h" #include "wayland/meta-xwayland-private.h" #endif static XIEvent * get_input_event (MetaDisplay *display, XEvent *event) { if (event->type == GenericEvent && event->xcookie.extension == display->xinput_opcode) { XIEvent *input_event; /* NB: GDK event filters already have generic events * allocated, so no need to do XGetEventData() on our own */ input_event = (XIEvent *) event->xcookie.data; switch (input_event->evtype) { case XI_Motion: case XI_ButtonPress: case XI_ButtonRelease: if (((XIDeviceEvent *) input_event)->deviceid == META_VIRTUAL_CORE_POINTER_ID) return input_event; break; case XI_KeyPress: case XI_KeyRelease: if (((XIDeviceEvent *) input_event)->deviceid == META_VIRTUAL_CORE_KEYBOARD_ID) return input_event; break; case XI_FocusIn: case XI_FocusOut: if (((XIEnterEvent *) input_event)->deviceid == META_VIRTUAL_CORE_KEYBOARD_ID) return input_event; break; case XI_Enter: case XI_Leave: if (((XIEnterEvent *) input_event)->deviceid == META_VIRTUAL_CORE_POINTER_ID) return input_event; break; #ifdef HAVE_XI23 case XI_BarrierHit: case XI_BarrierLeave: if (((XIBarrierEvent *) input_event)->deviceid == META_VIRTUAL_CORE_POINTER_ID) return input_event; break; #endif /* HAVE_XI23 */ default: break; } } return NULL; } static Window xievent_get_modified_window (MetaDisplay *display, XIEvent *input_event) { switch (input_event->evtype) { case XI_Motion: case XI_ButtonPress: case XI_ButtonRelease: case XI_KeyPress: case XI_KeyRelease: return ((XIDeviceEvent *) input_event)->event; case XI_FocusIn: case XI_FocusOut: case XI_Enter: case XI_Leave: return ((XIEnterEvent *) input_event)->event; #ifdef HAVE_XI23 case XI_BarrierHit: case XI_BarrierLeave: return ((XIBarrierEvent *) input_event)->event; #endif /* HAVE_XI23 */ } return None; } /* Return the window this has to do with, if any, rather * than the frame or root window that was selecting * for substructure */ static Window event_get_modified_window (MetaDisplay *display, XEvent *event) { XIEvent *input_event = get_input_event (display, event); if (input_event) return xievent_get_modified_window (display, input_event); switch (event->type) { case KeymapNotify: case Expose: case GraphicsExpose: case NoExpose: case VisibilityNotify: case ResizeRequest: case PropertyNotify: case SelectionClear: case SelectionRequest: case SelectionNotify: case ColormapNotify: case ClientMessage: return event->xany.window; case CreateNotify: return event->xcreatewindow.window; case DestroyNotify: return event->xdestroywindow.window; case UnmapNotify: return event->xunmap.window; case MapNotify: return event->xmap.window; case MapRequest: return event->xmaprequest.window; case ReparentNotify: return event->xreparent.window; case ConfigureNotify: return event->xconfigure.window; case ConfigureRequest: return event->xconfigurerequest.window; case GravityNotify: return event->xgravity.window; case CirculateNotify: return event->xcirculate.window; case CirculateRequest: return event->xcirculaterequest.window; case MappingNotify: return None; default: if (META_DISPLAY_HAS_SHAPE (display) && event->type == (display->shape_event_base + ShapeNotify)) { XShapeEvent *sev = (XShapeEvent*) event; return sev->window; } return None; } } static guint32 event_get_time (MetaDisplay *display, XEvent *event) { XIEvent *input_event = get_input_event (display, event); if (input_event) return input_event->time; switch (event->type) { case PropertyNotify: return event->xproperty.time; case SelectionClear: case SelectionRequest: case SelectionNotify: return event->xselection.time; case KeymapNotify: case Expose: case GraphicsExpose: case NoExpose: case MapNotify: case UnmapNotify: case VisibilityNotify: case ResizeRequest: case ColormapNotify: case ClientMessage: case CreateNotify: case DestroyNotify: case MapRequest: case ReparentNotify: case ConfigureNotify: case ConfigureRequest: case GravityNotify: case CirculateNotify: case CirculateRequest: case MappingNotify: default: return CurrentTime; } } const char* meta_event_detail_to_string (int d) { const char *detail = "???"; switch (d) { /* We are an ancestor in the A<->B focus change relationship */ case XINotifyAncestor: detail = "NotifyAncestor"; break; case XINotifyDetailNone: detail = "NotifyDetailNone"; break; /* We are a descendant in the A<->B focus change relationship */ case XINotifyInferior: detail = "NotifyInferior"; break; case XINotifyNonlinear: detail = "NotifyNonlinear"; break; case XINotifyNonlinearVirtual: detail = "NotifyNonlinearVirtual"; break; case XINotifyPointer: detail = "NotifyPointer"; break; case XINotifyPointerRoot: detail = "NotifyPointerRoot"; break; case XINotifyVirtual: detail = "NotifyVirtual"; break; } return detail; } const char* meta_event_mode_to_string (int m) { const char *mode = "???"; switch (m) { case XINotifyNormal: mode = "NotifyNormal"; break; case XINotifyGrab: mode = "NotifyGrab"; break; case XINotifyUngrab: mode = "NotifyUngrab"; break; case XINotifyWhileGrabbed: mode = "NotifyWhileGrabbed"; break; } return mode; } G_GNUC_UNUSED static const char* stack_mode_to_string (int mode) { switch (mode) { case Above: return "Above"; case Below: return "Below"; case TopIf: return "TopIf"; case BottomIf: return "BottomIf"; case Opposite: return "Opposite"; } return "Unknown"; } static gint64 sync_value_to_64 (const XSyncValue *value) { gint64 v; v = XSyncValueLow32 (*value); v |= (((gint64)XSyncValueHigh32 (*value)) << 32); return v; } static const char* alarm_state_to_string (XSyncAlarmState state) { switch (state) { case XSyncAlarmActive: return "Active"; case XSyncAlarmInactive: return "Inactive"; case XSyncAlarmDestroyed: return "Destroyed"; default: return "(unknown)"; } } static void meta_spew_xi2_event (MetaDisplay *display, XIEvent *input_event, const char **name_p, char **extra_p) { const char *name = NULL; char *extra = NULL; XIEnterEvent *enter_event = (XIEnterEvent *) input_event; switch (input_event->evtype) { case XI_FocusIn: name = "XI_FocusIn"; break; case XI_FocusOut: name = "XI_FocusOut"; break; case XI_Enter: name = "XI_Enter"; break; case XI_Leave: name = "XI_Leave"; break; #ifdef HAVE_XI23 case XI_BarrierHit: name = "XI_BarrierHit"; break; case XI_BarrierLeave: name = "XI_BarrierLeave"; break; #endif /* HAVE_XI23 */ } switch (input_event->evtype) { case XI_FocusIn: case XI_FocusOut: extra = g_strdup_printf ("detail: %s mode: %s\n", meta_event_detail_to_string (enter_event->detail), meta_event_mode_to_string (enter_event->mode)); break; case XI_Enter: case XI_Leave: extra = g_strdup_printf ("win: 0x%lx root: 0x%lx mode: %s detail: %s focus: %d x: %g y: %g", enter_event->event, enter_event->root, meta_event_mode_to_string (enter_event->mode), meta_event_detail_to_string (enter_event->detail), enter_event->focus, enter_event->root_x, enter_event->root_y); break; } *name_p = name; *extra_p = extra; } static void meta_spew_core_event (MetaDisplay *display, XEvent *event, const char **name_p, char **extra_p) { const char *name = NULL; char *extra = NULL; switch (event->type) { case KeymapNotify: name = "KeymapNotify"; break; case Expose: name = "Expose"; break; case GraphicsExpose: name = "GraphicsExpose"; break; case NoExpose: name = "NoExpose"; break; case VisibilityNotify: name = "VisibilityNotify"; break; case CreateNotify: name = "CreateNotify"; extra = g_strdup_printf ("parent: 0x%lx window: 0x%lx", event->xcreatewindow.parent, event->xcreatewindow.window); break; case DestroyNotify: name = "DestroyNotify"; extra = g_strdup_printf ("event: 0x%lx window: 0x%lx", event->xdestroywindow.event, event->xdestroywindow.window); break; case UnmapNotify: name = "UnmapNotify"; extra = g_strdup_printf ("event: 0x%lx window: 0x%lx from_configure: %d", event->xunmap.event, event->xunmap.window, event->xunmap.from_configure); break; case MapNotify: name = "MapNotify"; extra = g_strdup_printf ("event: 0x%lx window: 0x%lx override_redirect: %d", event->xmap.event, event->xmap.window, event->xmap.override_redirect); break; case MapRequest: name = "MapRequest"; extra = g_strdup_printf ("window: 0x%lx parent: 0x%lx\n", event->xmaprequest.window, event->xmaprequest.parent); break; case ReparentNotify: name = "ReparentNotify"; extra = g_strdup_printf ("window: 0x%lx parent: 0x%lx event: 0x%lx\n", event->xreparent.window, event->xreparent.parent, event->xreparent.event); break; case ConfigureNotify: name = "ConfigureNotify"; extra = g_strdup_printf ("x: %d y: %d w: %d h: %d above: 0x%lx override_redirect: %d", event->xconfigure.x, event->xconfigure.y, event->xconfigure.width, event->xconfigure.height, event->xconfigure.above, event->xconfigure.override_redirect); break; case ConfigureRequest: name = "ConfigureRequest"; extra = g_strdup_printf ("parent: 0x%lx window: 0x%lx x: %d %sy: %d %sw: %d %sh: %d %sborder: %d %sabove: %lx %sstackmode: %s %s", event->xconfigurerequest.parent, event->xconfigurerequest.window, event->xconfigurerequest.x, event->xconfigurerequest.value_mask & CWX ? "" : "(unset) ", event->xconfigurerequest.y, event->xconfigurerequest.value_mask & CWY ? "" : "(unset) ", event->xconfigurerequest.width, event->xconfigurerequest.value_mask & CWWidth ? "" : "(unset) ", event->xconfigurerequest.height, event->xconfigurerequest.value_mask & CWHeight ? "" : "(unset) ", event->xconfigurerequest.border_width, event->xconfigurerequest.value_mask & CWBorderWidth ? "" : "(unset)", event->xconfigurerequest.above, event->xconfigurerequest.value_mask & CWSibling ? "" : "(unset)", stack_mode_to_string (event->xconfigurerequest.detail), event->xconfigurerequest.value_mask & CWStackMode ? "" : "(unset)"); break; case GravityNotify: name = "GravityNotify"; break; case ResizeRequest: name = "ResizeRequest"; extra = g_strdup_printf ("width = %d height = %d", event->xresizerequest.width, event->xresizerequest.height); break; case CirculateNotify: name = "CirculateNotify"; break; case CirculateRequest: name = "CirculateRequest"; break; case PropertyNotify: { char *str; const char *state; name = "PropertyNotify"; meta_error_trap_push (display); str = XGetAtomName (display->xdisplay, event->xproperty.atom); meta_error_trap_pop (display); if (event->xproperty.state == PropertyNewValue) state = "PropertyNewValue"; else if (event->xproperty.state == PropertyDelete) state = "PropertyDelete"; else state = "???"; extra = g_strdup_printf ("atom: %s state: %s", str ? str : "(unknown atom)", state); meta_XFree (str); } break; case SelectionClear: name = "SelectionClear"; break; case SelectionRequest: name = "SelectionRequest"; break; case SelectionNotify: name = "SelectionNotify"; break; case ColormapNotify: name = "ColormapNotify"; break; case ClientMessage: { char *str; name = "ClientMessage"; meta_error_trap_push (display); str = XGetAtomName (display->xdisplay, event->xclient.message_type); meta_error_trap_pop (display); extra = g_strdup_printf ("type: %s format: %d\n", str ? str : "(unknown atom)", event->xclient.format); meta_XFree (str); } break; case MappingNotify: name = "MappingNotify"; break; default: if (META_DISPLAY_HAS_XSYNC (display) && event->type == (display->xsync_event_base + XSyncAlarmNotify)) { XSyncAlarmNotifyEvent *aevent = (XSyncAlarmNotifyEvent*) event; name = "XSyncAlarmNotify"; extra = g_strdup_printf ("alarm: 0x%lx" " counter_value: %" G_GINT64_FORMAT " alarm_value: %" G_GINT64_FORMAT " time: %u alarm state: %s", aevent->alarm, (gint64) sync_value_to_64 (&aevent->counter_value), (gint64) sync_value_to_64 (&aevent->alarm_value), (unsigned int)aevent->time, alarm_state_to_string (aevent->state)); } else if (META_DISPLAY_HAS_SHAPE (display) && event->type == (display->shape_event_base + ShapeNotify)) { XShapeEvent *sev = (XShapeEvent*) event; name = "ShapeNotify"; extra = g_strdup_printf ("kind: %s " "x: %d y: %d w: %u h: %u " "shaped: %d", sev->kind == ShapeBounding ? "ShapeBounding" : (sev->kind == ShapeClip ? "ShapeClip" : "(unknown)"), sev->x, sev->y, sev->width, sev->height, sev->shaped); } else { name = "(Unknown event)"; extra = g_strdup_printf ("type: %d", event->xany.type); } break; } *name_p = name; *extra_p = extra; } static char * meta_spew_event (MetaDisplay *display, XEvent *event) { MetaScreen *screen = display->screen; const char *name = NULL; char *extra = NULL; char *winname; char *ret; XIEvent *input_event; input_event = get_input_event (display, event); if (input_event) meta_spew_xi2_event (display, input_event, &name, &extra); else meta_spew_core_event (display, event, &name, &extra); if (event->xany.window == screen->xroot) winname = g_strdup_printf ("root %d", screen->number); else winname = g_strdup_printf ("0x%lx", event->xany.window); ret = g_strdup_printf ("%s on %s%s %s %sserial %lu", name, winname, extra ? ":" : "", extra ? extra : "", event->xany.send_event ? "SEND " : "", event->xany.serial); g_free (winname); g_free (extra); return ret; } G_GNUC_UNUSED static void meta_spew_event_print (MetaDisplay *display, XEvent *event) { char *event_str; /* filter overnumerous events */ if (event->type == Expose || event->type == MotionNotify || event->type == NoExpose) return; if (event->type == (display->damage_event_base + XDamageNotify)) return; if (event->type == (display->xsync_event_base + XSyncAlarmNotify)) return; if (event->type == PropertyNotify && event->xproperty.atom == display->atom__NET_WM_USER_TIME) return; event_str = meta_spew_event (display, event); g_print ("%s\n", event_str); g_free (event_str); } static gboolean handle_window_focus_event (MetaDisplay *display, MetaWindow *window, XIEnterEvent *event, unsigned long serial) { MetaWindow *focus_window; #ifdef WITH_VERBOSE_MODE const char *window_type; /* Note the event can be on either the window or the frame, * we focus the frame for shaded windows */ if (window) { if (event->event == window->xwindow) window_type = "client window"; else if (window->frame && event->event == window->frame->xwindow) window_type = "frame window"; else window_type = "unknown client window"; } else if (meta_display_xwindow_is_a_no_focus_window (display, event->event)) window_type = "no_focus_window"; else if (event->event == display->screen->xroot) window_type = "root window"; else window_type = "unknown window"; meta_topic (META_DEBUG_FOCUS, "Focus %s event received on %s 0x%lx (%s) " "mode %s detail %s serial %lu\n", event->evtype == XI_FocusIn ? "in" : event->evtype == XI_FocusOut ? "out" : "???", window ? window->desc : "", event->event, window_type, meta_event_mode_to_string (event->mode), meta_event_detail_to_string (event->mode), serial); #endif /* FIXME our pointer tracking is broken; see how * gtk+/gdk/x11/gdkevents-x11.c or XFree86/xc/programs/xterm/misc.c * for how to handle it the correct way. In brief you need to track * pointer focus and regular focus, and handle EnterNotify in * PointerRoot mode with no window manager. However as noted above, * accurate focus tracking will break things because we want to keep * windows "focused" when using keybindings on them, and also we * sometimes "focus" a window by focusing its frame or * no_focus_window; so this all needs rethinking massively. * * My suggestion is to change it so that we clearly separate * actual keyboard focus tracking using the xterm algorithm, * and mutter's "pretend" focus window, and go through all * the code and decide which one should be used in each place; * a hard bit is deciding on a policy for that. * * http://bugzilla.gnome.org/show_bug.cgi?id=90382 */ /* We ignore grabs, though this is questionable. It may be better to * increase the intelligence of the focus window tracking. * * The problem is that keybindings for windows are done with * XGrabKey, which means focus_window disappears and the front of * the MRU list gets confused from what the user expects once a * keybinding is used. */ if (event->mode == XINotifyGrab || event->mode == XINotifyUngrab || /* From WindowMaker, ignore all funky pointer root events */ event->detail > XINotifyNonlinearVirtual) { meta_topic (META_DEBUG_FOCUS, "Ignoring focus event generated by a grab or other weirdness\n"); return FALSE; } if (event->evtype == XI_FocusIn) { display->server_focus_window = event->event; display->server_focus_serial = serial; focus_window = window; } else if (event->evtype == XI_FocusOut) { if (event->detail == XINotifyInferior) { /* This event means the client moved focus to a subwindow */ meta_topic (META_DEBUG_FOCUS, "Ignoring focus out with NotifyInferior\n"); return FALSE; } display->server_focus_window = None; display->server_focus_serial = serial; focus_window = NULL; } else g_assert_not_reached (); /* If display->focused_by_us, then the focus_serial will be used only * for a focus change we made and have already accounted for. * (See request_xserver_input_focus_change().) Otherwise, we can get * multiple focus events with the same serial. */ if (display->server_focus_serial > display->focus_serial || (!display->focused_by_us && display->server_focus_serial == display->focus_serial)) { meta_display_update_focus_window (display, focus_window, focus_window ? focus_window->xwindow : None, display->server_focus_serial, FALSE); return TRUE; } else { return FALSE; } } static gboolean crossing_serial_is_ignored (MetaDisplay *display, unsigned long serial) { int i; i = 0; while (i < N_IGNORED_CROSSING_SERIALS) { if (display->ignored_crossing_serials[i] == serial) return TRUE; ++i; } return FALSE; } static gboolean handle_input_xevent (MetaDisplay *display, XIEvent *input_event, unsigned long serial) { XIEnterEvent *enter_event = (XIEnterEvent *) input_event; Window modified; MetaWindow *window; MetaScreen *screen = display->screen; if (input_event == NULL) return FALSE; switch (input_event->evtype) { case XI_Enter: case XI_Leave: case XI_FocusIn: case XI_FocusOut: break; default: return FALSE; } modified = xievent_get_modified_window (display, input_event); window = modified != None ? meta_display_lookup_x_window (display, modified) : NULL; /* If this is an event for a GTK+ widget, let GTK+ handle it. */ if (meta_ui_window_is_widget (display->screen->ui, modified)) return FALSE; switch (input_event->evtype) { case XI_Enter: if (display->event_route != META_EVENT_ROUTE_NORMAL) break; /* Check if we've entered a window; do this even if window->has_focus to * avoid races. */ if (window && !crossing_serial_is_ignored (display, serial) && enter_event->mode != XINotifyGrab && enter_event->mode != XINotifyUngrab && enter_event->detail != XINotifyInferior && meta_display_focus_sentinel_clear (display)) { meta_window_handle_enter (window, enter_event->time, enter_event->root_x, enter_event->root_y); } break; case XI_Leave: if (display->event_route != META_EVENT_ROUTE_NORMAL) break; if (window != NULL && enter_event->mode != XINotifyGrab && enter_event->mode != XINotifyUngrab) { meta_window_handle_leave (window); } break; case XI_FocusIn: case XI_FocusOut: if (handle_window_focus_event (display, window, enter_event, serial) && enter_event->event == enter_event->root) { if (enter_event->evtype == XI_FocusIn && enter_event->detail == XINotifyDetailNone) { meta_topic (META_DEBUG_FOCUS, "Focus got set to None, probably due to " "brain-damage in the X protocol (see bug " "125492). Setting the default focus window.\n"); meta_workspace_focus_default_window (screen->active_workspace, NULL, meta_display_get_current_time_roundtrip (display)); } else if (enter_event->evtype == XI_FocusIn && enter_event->mode == XINotifyNormal && enter_event->detail == XINotifyInferior) { meta_topic (META_DEBUG_FOCUS, "Focus got set to root window, probably due to " "gnome-session logout dialog usage (see bug " "153220). Setting the default focus window.\n"); meta_workspace_focus_default_window (screen->active_workspace, NULL, meta_display_get_current_time_roundtrip (display)); } } break; } /* Don't eat events for GTK frames (we need to update the :hover state on buttons) */ if (window && window->frame && modified == window->frame->xwindow) return FALSE; /* Don't pass these events through to Clutter / GTK+ */ return TRUE; } static void process_request_frame_extents (MetaDisplay *display, XEvent *event) { /* The X window whose frame extents will be set. */ Window xwindow = event->xclient.window; unsigned long data[4] = { 0, 0, 0, 0 }; MotifWmHints *hints = NULL; gboolean hints_set = FALSE; meta_verbose ("Setting frame extents for 0x%lx\n", xwindow); /* See if the window is decorated. */ hints_set = meta_prop_get_motif_hints (display, xwindow, display->atom__MOTIF_WM_HINTS, &hints); if ((hints_set && hints->decorations) || !hints_set) { MetaFrameBorders borders; /* Return estimated frame extents for a normal window. */ meta_ui_theme_get_frame_borders (display->screen->ui, META_FRAME_TYPE_NORMAL, 0, &borders); data[0] = borders.visible.left; data[1] = borders.visible.right; data[2] = borders.visible.top; data[3] = borders.visible.bottom; } meta_topic (META_DEBUG_GEOMETRY, "Setting _NET_FRAME_EXTENTS on unmanaged window 0x%lx " "to top = %lu, left = %lu, bottom = %lu, right = %lu\n", xwindow, data[0], data[1], data[2], data[3]); meta_error_trap_push (display); XChangeProperty (display->xdisplay, xwindow, display->atom__NET_FRAME_EXTENTS, XA_CARDINAL, 32, PropModeReplace, (guchar*) data, 4); meta_error_trap_pop (display); meta_XFree (hints); } /* from fvwm2, Copyright Matthias Clasen, Dominik Vogt */ static gboolean convert_property (MetaDisplay *display, MetaScreen *screen, Window w, Atom target, Atom property) { #define N_TARGETS 4 Atom conversion_targets[N_TARGETS]; long icccm_version[] = { 2, 0 }; conversion_targets[0] = display->atom_TARGETS; conversion_targets[1] = display->atom_MULTIPLE; conversion_targets[2] = display->atom_TIMESTAMP; conversion_targets[3] = display->atom_VERSION; meta_error_trap_push (display); if (target == display->atom_TARGETS) XChangeProperty (display->xdisplay, w, property, XA_ATOM, 32, PropModeReplace, (unsigned char *)conversion_targets, N_TARGETS); else if (target == display->atom_TIMESTAMP) XChangeProperty (display->xdisplay, w, property, XA_INTEGER, 32, PropModeReplace, (unsigned char *)&screen->wm_sn_timestamp, 1); else if (target == display->atom_VERSION) XChangeProperty (display->xdisplay, w, property, XA_INTEGER, 32, PropModeReplace, (unsigned char *)icccm_version, 2); else { meta_error_trap_pop_with_return (display); return FALSE; } if (meta_error_trap_pop_with_return (display) != Success) return FALSE; /* Be sure the PropertyNotify has arrived so we * can send SelectionNotify */ /* FIXME the error trap pop synced anyway, right? */ meta_topic (META_DEBUG_SYNC, "Syncing on %s\n", G_STRFUNC); XSync (display->xdisplay, False); return TRUE; } /* from fvwm2, Copyright Matthias Clasen, Dominik Vogt */ static void process_selection_request (MetaDisplay *display, XEvent *event) { MetaScreen *screen = display->screen; XSelectionEvent reply; if (screen->wm_sn_selection_window != event->xselectionrequest.owner || screen->wm_sn_atom != event->xselectionrequest.selection) { char *str; meta_error_trap_push (display); str = XGetAtomName (display->xdisplay, event->xselectionrequest.selection); meta_error_trap_pop (display); meta_verbose ("Selection request with selection %s window 0x%lx not a WM_Sn selection we recognize\n", str ? str : "(bad atom)", event->xselectionrequest.owner); meta_XFree (str); return; } reply.type = SelectionNotify; reply.display = display->xdisplay; reply.requestor = event->xselectionrequest.requestor; reply.selection = event->xselectionrequest.selection; reply.target = event->xselectionrequest.target; reply.property = None; reply.time = event->xselectionrequest.time; if (event->xselectionrequest.target == display->atom_MULTIPLE) { if (event->xselectionrequest.property != None) { Atom type, *adata; int i, format; unsigned long num, rest; unsigned char *data; meta_error_trap_push (display); if (XGetWindowProperty (display->xdisplay, event->xselectionrequest.requestor, event->xselectionrequest.property, 0, 256, False, display->atom_ATOM_PAIR, &type, &format, &num, &rest, &data) != Success) { meta_error_trap_pop_with_return (display); return; } if (meta_error_trap_pop_with_return (display) == Success) { /* FIXME: to be 100% correct, should deal with rest > 0, * but since we have 4 possible targets, we will hardly ever * meet multiple requests with a length > 8 */ adata = (Atom*)data; i = 0; while (i < (int) num) { if (!convert_property (display, screen, event->xselectionrequest.requestor, adata[i], adata[i+1])) adata[i+1] = None; i += 2; } meta_error_trap_push (display); XChangeProperty (display->xdisplay, event->xselectionrequest.requestor, event->xselectionrequest.property, display->atom_ATOM_PAIR, 32, PropModeReplace, data, num); meta_error_trap_pop (display); meta_XFree (data); } } } else { if (event->xselectionrequest.property == None) event->xselectionrequest.property = event->xselectionrequest.target; if (convert_property (display, screen, event->xselectionrequest.requestor, event->xselectionrequest.target, event->xselectionrequest.property)) reply.property = event->xselectionrequest.property; } XSendEvent (display->xdisplay, event->xselectionrequest.requestor, False, 0L, (XEvent*)&reply); meta_verbose ("Handled selection request\n"); } static gboolean process_selection_clear (MetaDisplay *display, XEvent *event) { MetaScreen *screen = display->screen; if (screen->wm_sn_selection_window != event->xselectionclear.window || screen->wm_sn_atom != event->xselectionclear.selection) { char *str; meta_error_trap_push (display); str = XGetAtomName (display->xdisplay, event->xselectionclear.selection); meta_error_trap_pop (display); meta_verbose ("Selection clear with selection %s window 0x%lx not a WM_Sn selection we recognize\n", str ? str : "(bad atom)", event->xselectionclear.window); meta_XFree (str); return FALSE; } meta_verbose ("Got selection clear for screen %d on display %s\n", screen->number, display->name); meta_display_unmanage_screen (display, display->screen, event->xselectionclear.time); return TRUE; } static gboolean handle_other_xevent (MetaDisplay *display, XEvent *event) { Window modified; MetaWindow *window; MetaWindow *property_for_window; gboolean frame_was_receiver; gboolean bypass_gtk = FALSE; modified = event_get_modified_window (display, event); window = modified != None ? meta_display_lookup_x_window (display, modified) : NULL; frame_was_receiver = (window && window->frame && modified == window->frame->xwindow); /* We only want to respond to _NET_WM_USER_TIME property notify * events on _NET_WM_USER_TIME_WINDOW windows; in particular, * responding to UnmapNotify events is kind of bad. */ property_for_window = NULL; if (window && modified == window->user_time_window) { property_for_window = window; window = NULL; } if (META_DISPLAY_HAS_XSYNC (display) && event->type == (display->xsync_event_base + XSyncAlarmNotify)) { MetaWindow *alarm_window = meta_display_lookup_sync_alarm (display, ((XSyncAlarmNotifyEvent*)event)->alarm); if (alarm_window != NULL) { XSyncValue value = ((XSyncAlarmNotifyEvent*)event)->counter_value; gint64 new_counter_value; new_counter_value = XSyncValueLow32 (value) + ((gint64)XSyncValueHigh32 (value) << 32); meta_window_x11_update_sync_request_counter (alarm_window, new_counter_value); bypass_gtk = TRUE; /* GTK doesn't want to see this really */ } else { if (display->alarm_filter && display->alarm_filter (display, (XSyncAlarmNotifyEvent*)event, display->alarm_filter_data)) bypass_gtk = TRUE; } goto out; } if (META_DISPLAY_HAS_SHAPE (display) && event->type == (display->shape_event_base + ShapeNotify)) { bypass_gtk = TRUE; /* GTK doesn't want to see this really */ if (window && !frame_was_receiver) { XShapeEvent *sev = (XShapeEvent*) event; if (sev->kind == ShapeBounding) meta_window_x11_update_shape_region (window); else if (sev->kind == ShapeInput) meta_window_x11_update_input_region (window); } else { meta_topic (META_DEBUG_SHAPES, "ShapeNotify not on a client window (window %s frame_was_receiver = %d)\n", window ? window->desc : "(none)", frame_was_receiver); } goto out; } switch (event->type) { case KeymapNotify: break; case Expose: break; case GraphicsExpose: break; case NoExpose: break; case VisibilityNotify: break; case CreateNotify: { if (event->xcreatewindow.parent == display->screen->xroot) meta_stack_tracker_create_event (display->screen->stack_tracker, &event->xcreatewindow); } break; case DestroyNotify: { if (event->xdestroywindow.event == display->screen->xroot) meta_stack_tracker_destroy_event (display->screen->stack_tracker, &event->xdestroywindow); } if (window) { /* FIXME: It sucks that DestroyNotify events don't come with * a timestamp; could we do something better here? Maybe X * will change one day? */ guint32 timestamp; timestamp = meta_display_get_current_time_roundtrip (display); if (display->grab_op != META_GRAB_OP_NONE && display->grab_window == window) meta_display_end_grab_op (display, timestamp); if (frame_was_receiver) { meta_warning ("Unexpected destruction of frame 0x%lx, not sure if this should silently fail or be considered a bug\n", window->frame->xwindow); meta_error_trap_push (display); meta_window_destroy_frame (window->frame->window); meta_error_trap_pop (display); } else { /* Unmanage destroyed window */ meta_window_unmanage (window, timestamp); window = NULL; } } break; case UnmapNotify: if (window) { /* FIXME: It sucks that UnmapNotify events don't come with * a timestamp; could we do something better here? Maybe X * will change one day? */ guint32 timestamp; timestamp = meta_display_get_current_time_roundtrip (display); if (display->grab_op != META_GRAB_OP_NONE && display->grab_window == window && window->frame == NULL) meta_display_end_grab_op (display, timestamp); if (!frame_was_receiver) { if (window->unmaps_pending == 0) { meta_topic (META_DEBUG_WINDOW_STATE, "Window %s withdrawn\n", window->desc); /* Unmanage withdrawn window */ window->withdrawn = TRUE; meta_window_unmanage (window, timestamp); window = NULL; } else { window->unmaps_pending -= 1; meta_topic (META_DEBUG_WINDOW_STATE, "Received pending unmap, %d now pending\n", window->unmaps_pending); } } } break; case MapNotify: /* NB: override redirect windows wont cause a map request so we * watch out for map notifies against any root windows too if a * compositor is enabled: */ if (window == NULL && event->xmap.event == display->screen->xroot) { window = meta_window_x11_new (display, event->xmap.window, FALSE, META_COMP_EFFECT_CREATE); } break; case MapRequest: if (window == NULL) { window = meta_window_x11_new (display, event->xmaprequest.window, FALSE, META_COMP_EFFECT_CREATE); } /* if frame was receiver it's some malicious send event or something */ else if (!frame_was_receiver && window) { meta_verbose ("MapRequest on %s mapped = %d minimized = %d\n", window->desc, window->mapped, window->minimized); if (window->minimized) { meta_window_unminimize (window); if (window->workspace != window->screen->active_workspace) { meta_verbose ("Changing workspace due to MapRequest mapped = %d minimized = %d\n", window->mapped, window->minimized); meta_window_change_workspace (window, window->screen->active_workspace); } } } break; case ReparentNotify: { if (event->xreparent.event == display->screen->xroot) meta_stack_tracker_reparent_event (display->screen->stack_tracker, &event->xreparent); } break; case ConfigureNotify: if (event->xconfigure.event != event->xconfigure.window) { if (event->xconfigure.event == display->screen->xroot && event->xconfigure.window != display->screen->composite_overlay_window) meta_stack_tracker_configure_event (display->screen->stack_tracker, &event->xconfigure); } if (window && window->override_redirect) meta_window_x11_configure_notify (window, &event->xconfigure); break; case ConfigureRequest: /* This comment and code is found in both twm and fvwm */ /* * According to the July 27, 1988 ICCCM draft, we should ignore size and * position fields in the WM_NORMAL_HINTS property when we map a window. * Instead, we'll read the current geometry. Therefore, we should respond * to configuration requests for windows which have never been mapped. */ if (window == NULL) { unsigned int xwcm; XWindowChanges xwc; xwcm = event->xconfigurerequest.value_mask & (CWX | CWY | CWWidth | CWHeight | CWBorderWidth); xwc.x = event->xconfigurerequest.x; xwc.y = event->xconfigurerequest.y; xwc.width = event->xconfigurerequest.width; xwc.height = event->xconfigurerequest.height; xwc.border_width = event->xconfigurerequest.border_width; meta_verbose ("Configuring withdrawn window to %d,%d %dx%d border %d (some values may not be in mask)\n", xwc.x, xwc.y, xwc.width, xwc.height, xwc.border_width); meta_error_trap_push (display); XConfigureWindow (display->xdisplay, event->xconfigurerequest.window, xwcm, &xwc); meta_error_trap_pop (display); } else { if (!frame_was_receiver) meta_window_x11_configure_request (window, event); } break; case GravityNotify: break; case ResizeRequest: break; case CirculateNotify: break; case CirculateRequest: break; case PropertyNotify: { MetaGroup *group; if (window && !frame_was_receiver) meta_window_x11_property_notify (window, event); else if (property_for_window && !frame_was_receiver) meta_window_x11_property_notify (property_for_window, event); group = meta_display_lookup_group (display, event->xproperty.window); if (group != NULL) meta_group_property_notify (group, event); if (event->xproperty.window == display->screen->xroot) { if (event->xproperty.atom == display->atom__NET_DESKTOP_LAYOUT) meta_screen_update_workspace_layout (display->screen); else if (event->xproperty.atom == display->atom__NET_DESKTOP_NAMES) meta_screen_update_workspace_names (display->screen); /* we just use this property as a sentinel to avoid * certain race conditions. See the comment for the * sentinel_counter variable declaration in display.h */ if (event->xproperty.atom == display->atom__MUTTER_SENTINEL) { meta_display_decrement_focus_sentinel (display); } } } break; case SelectionRequest: process_selection_request (display, event); break; case SelectionNotify: break; case ColormapNotify: break; case ClientMessage: if (window) { #ifdef HAVE_WAYLAND if (event->xclient.message_type == display->atom_WL_SURFACE_ID) { guint32 surface_id = event->xclient.data.l[0]; meta_xwayland_handle_wl_surface_id (window, surface_id); } else #endif if (!frame_was_receiver) meta_window_x11_client_message (window, event); } else { if (event->xclient.window == display->screen->xroot) { if (event->xclient.message_type == display->atom__NET_CURRENT_DESKTOP) { int space; MetaWorkspace *workspace; guint32 time; space = event->xclient.data.l[0]; time = event->xclient.data.l[1]; meta_verbose ("Request to change current workspace to %d with " "specified timestamp of %u\n", space, time); workspace = meta_screen_get_workspace_by_index (display->screen, space); /* Handle clients using the older version of the spec... */ if (time == 0 && workspace) { meta_warning ("Received a NET_CURRENT_DESKTOP message " "from a broken (outdated) client who sent " "a 0 timestamp\n"); time = meta_display_get_current_time_roundtrip (display); } if (workspace) meta_workspace_activate (workspace, time); else meta_verbose ("Don't know about workspace %d\n", space); } else if (event->xclient.message_type == display->atom__NET_NUMBER_OF_DESKTOPS) { int num_spaces; num_spaces = event->xclient.data.l[0]; meta_verbose ("Request to set number of workspaces to %d\n", num_spaces); meta_prefs_set_num_workspaces (num_spaces); } else if (event->xclient.message_type == display->atom__NET_SHOWING_DESKTOP) { gboolean showing_desktop; guint32 timestamp; showing_desktop = event->xclient.data.l[0] != 0; /* FIXME: Braindead protocol doesn't have a timestamp */ timestamp = meta_display_get_current_time_roundtrip (display); meta_verbose ("Request to %s desktop\n", showing_desktop ? "show" : "hide"); if (showing_desktop) meta_screen_show_desktop (display->screen, timestamp); else { meta_screen_unshow_desktop (display->screen); meta_workspace_focus_default_window (display->screen->active_workspace, NULL, timestamp); } } else if (event->xclient.message_type == display->atom_WM_PROTOCOLS) { meta_verbose ("Received WM_PROTOCOLS message\n"); if ((Atom)event->xclient.data.l[0] == display->atom__NET_WM_PING) { guint32 timestamp = event->xclient.data.l[1]; meta_display_pong_for_serial (display, timestamp); /* We don't want ping reply events going into * the GTK+ event loop because gtk+ will treat * them as ping requests and send more replies. */ bypass_gtk = TRUE; } } } if (event->xclient.message_type == display->atom__NET_REQUEST_FRAME_EXTENTS) { meta_verbose ("Received _NET_REQUEST_FRAME_EXTENTS message\n"); process_request_frame_extents (display, event); } } break; case MappingNotify: { gboolean ignore_current; ignore_current = FALSE; /* Check whether the next event is an identical MappingNotify * event. If it is, ignore the current event, we'll update * when we get the next one. */ if (XPending (display->xdisplay)) { XEvent next_event; XPeekEvent (display->xdisplay, &next_event); if (next_event.type == MappingNotify && next_event.xmapping.request == event->xmapping.request) ignore_current = TRUE; } if (!ignore_current) { /* Let XLib know that there is a new keyboard mapping. */ XRefreshKeyboardMapping (&event->xmapping); } } break; default: if (event->type == display->xkb_base_event_type) { XkbAnyEvent *xkb_ev = (XkbAnyEvent *) event; switch (xkb_ev->xkb_type) { case XkbBellNotify: if (XSERVER_TIME_IS_BEFORE(display->last_bell_time, xkb_ev->time - 100)) { display->last_bell_time = xkb_ev->time; meta_bell_notify (display, xkb_ev); } break; default: break; } } break; } out: return bypass_gtk; } static gboolean window_has_xwindow (MetaWindow *window, Window xwindow) { if (window->xwindow == xwindow) return TRUE; if (window->frame && window->frame->xwindow == xwindow) return TRUE; return FALSE; } /** * meta_display_handle_xevent: * @display: The MetaDisplay that events are coming from * @event: The event that just happened * * This is the most important function in the whole program. It is the heart, * it is the nexus, it is the Grand Central Station of Mutter's world. * When we create a #MetaDisplay, we ask GDK to pass *all* events for *all* * windows to this function. So every time anything happens that we might * want to know about, this function gets called. You see why it gets a bit * busy around here. Most of this function is a ginormous switch statement * dealing with all the kinds of events that might turn up. */ static gboolean meta_display_handle_xevent (MetaDisplay *display, XEvent *event) { Window modified; gboolean bypass_compositor = FALSE, bypass_gtk = FALSE; XIEvent *input_event; #if 0 meta_spew_event_print (display, event); #endif #ifdef HAVE_STARTUP_NOTIFICATION if (sn_display_process_event (display->sn_display, event)) { bypass_gtk = bypass_compositor = TRUE; goto out; } #endif #ifdef HAVE_WAYLAND if (meta_is_wayland_compositor () && meta_xwayland_selection_handle_event (event)) { bypass_gtk = bypass_compositor = TRUE; goto out; } #endif display->current_time = event_get_time (display, event); display->monitor_cache_invalidated = TRUE; if (display->focused_by_us && event->xany.serial > display->focus_serial && display->focus_window && !window_has_xwindow (display->focus_window, display->server_focus_window)) { meta_topic (META_DEBUG_FOCUS, "Earlier attempt to focus %s failed\n", display->focus_window->desc); meta_display_update_focus_window (display, meta_display_lookup_x_window (display, display->server_focus_window), display->server_focus_window, display->server_focus_serial, FALSE); } if (event->xany.window == display->screen->xroot) { if (meta_screen_handle_xevent (display->screen, event)) { bypass_gtk = bypass_compositor = TRUE; goto out; } } modified = event_get_modified_window (display, event); input_event = get_input_event (display, event); if (event->type == UnmapNotify) { if (meta_ui_window_should_not_cause_focus (display->xdisplay, modified)) { meta_display_add_ignored_crossing_serial (display, event->xany.serial); meta_topic (META_DEBUG_FOCUS, "Adding EnterNotify serial %lu to ignored focus serials\n", event->xany.serial); } } #ifdef HAVE_XI23 if (meta_display_process_barrier_xevent (display, input_event)) { bypass_gtk = bypass_compositor = TRUE; goto out; } #endif /* HAVE_XI23 */ if (handle_input_xevent (display, input_event, event->xany.serial)) { bypass_gtk = bypass_compositor = TRUE; goto out; } if (handle_other_xevent (display, event)) { bypass_gtk = TRUE; goto out; } if (event->type == SelectionClear) { if (process_selection_clear (display, event)) { /* This means we called meta_display_unmanage_screen, which * means the MetaDisplay is effectively dead. We don't want * to poke into display->current_time below, since that would * crash, so just directly return. */ return TRUE; } } out: if (!bypass_compositor) { MetaWindow *window = modified != None ? meta_display_lookup_x_window (display, modified) : NULL; if (meta_compositor_process_event (display->compositor, event, window)) bypass_gtk = TRUE; } display->current_time = CurrentTime; return bypass_gtk; } static GdkFilterReturn xevent_filter (GdkXEvent *xevent, GdkEvent *event, gpointer data) { MetaDisplay *display = data; if (meta_display_handle_xevent (display, xevent)) return GDK_FILTER_REMOVE; else return GDK_FILTER_CONTINUE; } void meta_display_init_events_x11 (MetaDisplay *display) { gdk_window_add_filter (NULL, xevent_filter, display); } void meta_display_free_events_x11 (MetaDisplay *display) { gdk_window_remove_filter (NULL, xevent_filter, display); }