+
+static gboolean more_client_message_event(Window window, Atom msgtype)
+{
+ ObtXQueueWindowMessage wm;
+ wm.window = window;
+ wm.message = msgtype;
+ return xqueue_exists_local(xqueue_match_window_message, &wm);
+}
+
+struct ObSkipPropertyChange {
+ Window window;
+ Atom prop;
+};
+
+static gboolean skip_property_change(XEvent *e, gpointer data)
+{
+ const struct ObSkipPropertyChange s = *(struct ObSkipPropertyChange*)data;
+
+ if (e->type == PropertyNotify && e->xproperty.window == s.window) {
+ const Atom a = e->xproperty.atom;
+ const Atom b = s.prop;
+
+ /* these are all updated together */
+ if ((a == OBT_PROP_ATOM(NET_WM_NAME) ||
+ a == OBT_PROP_ATOM(WM_NAME) ||
+ a == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
+ a == OBT_PROP_ATOM(WM_ICON_NAME))
+ &&
+ (b == OBT_PROP_ATOM(NET_WM_NAME) ||
+ b == OBT_PROP_ATOM(WM_NAME) ||
+ b == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
+ b == OBT_PROP_ATOM(WM_ICON_NAME)))
+ {
+ return TRUE;
+ }
+ else if (a == b && a == OBT_PROP_ATOM(NET_WM_ICON))
+ return TRUE;
+ }
+ return FALSE;
+}
+
+static void event_handle_client(ObClient *client, XEvent *e)
+{
+ Atom msgtype;
+ ObFrameContext con;
+ gboolean *but;
+ static gint px = -1, py = -1;
+ static guint pb = 0;
+ static ObFrameContext pcon = OB_FRAME_CONTEXT_NONE;
+
+ switch (e->type) {
+ case ButtonPress:
+ /* save where the press occured for the first button pressed */
+ if (!pb) {
+ pb = e->xbutton.button;
+ px = e->xbutton.x;
+ py = e->xbutton.y;
+
+ pcon = frame_context(client, e->xbutton.window, px, py);
+ pcon = mouse_button_frame_context(pcon, e->xbutton.button,
+ e->xbutton.state);
+ }
+ case ButtonRelease:
+ /* Wheel buttons don't draw because they are an instant click, so it
+ is a waste of resources to go drawing it.
+ if the user is doing an interactive thing, or has a menu open then
+ the mouse is grabbed (possibly) and if we get these events we don't
+ want to deal with them
+ */
+ if (!(e->xbutton.button == 4 || e->xbutton.button == 5) &&
+ !grab_on_keyboard())
+ {
+ /* use where the press occured */
+ con = frame_context(client, e->xbutton.window, px, py);
+ con = mouse_button_frame_context(con, e->xbutton.button,
+ e->xbutton.state);
+
+ /* button presses on CLIENT_CONTEXTs are not accompanied by a
+ release because they are Replayed to the client */
+ if ((e->type == ButtonRelease || CLIENT_CONTEXT(con, client)) &&
+ e->xbutton.button == pb)
+ pb = 0, px = py = -1, pcon = OB_FRAME_CONTEXT_NONE;
+
+ but = context_to_button(client->frame, con, TRUE);
+ if (but) {
+ *but = (e->type == ButtonPress);
+ frame_adjust_state(client->frame);
+ }
+ }
+ break;
+ case MotionNotify:
+ /* when there is a grab on the pointer, we won't get enter/leave
+ notifies, but we still get motion events */
+ if (grab_on_pointer()) break;
+
+ con = frame_context(client, e->xmotion.window,
+ e->xmotion.x, e->xmotion.y);
+ switch (con) {
+ case OB_FRAME_CONTEXT_TITLEBAR:
+ case OB_FRAME_CONTEXT_TLCORNER:
+ case OB_FRAME_CONTEXT_TRCORNER:
+ /* we've left the button area inside the titlebar */
+ if (client->frame->max_hover || client->frame->desk_hover ||
+ client->frame->shade_hover || client->frame->iconify_hover ||
+ client->frame->close_hover)
+ {
+ client->frame->max_hover =
+ client->frame->desk_hover =
+ client->frame->shade_hover =
+ client->frame->iconify_hover =
+ client->frame->close_hover = FALSE;
+ frame_adjust_state(client->frame);
+ }
+ break;
+ default:
+ but = context_to_button(client->frame, con, FALSE);
+ if (but && !*but && !pb) {
+ *but = TRUE;
+ frame_adjust_state(client->frame);
+ }
+ break;
+ }
+ break;
+ case LeaveNotify:
+ con = frame_context(client, e->xcrossing.window,
+ e->xcrossing.x, e->xcrossing.y);
+ switch (con) {
+ case OB_FRAME_CONTEXT_TITLEBAR:
+ case OB_FRAME_CONTEXT_TLCORNER:
+ case OB_FRAME_CONTEXT_TRCORNER:
+ /* we've left the button area inside the titlebar */
+ client->frame->max_hover =
+ client->frame->desk_hover =
+ client->frame->shade_hover =
+ client->frame->iconify_hover =
+ client->frame->close_hover = FALSE;
+ if (e->xcrossing.mode == NotifyGrab) {
+ client->frame->max_press =
+ client->frame->desk_press =
+ client->frame->shade_press =
+ client->frame->iconify_press =
+ client->frame->close_press = FALSE;
+ }
+ break;
+ case OB_FRAME_CONTEXT_FRAME:
+ /* When the mouse leaves an animating window, don't use the
+ corresponding enter events. Pretend like the animating window
+ doesn't even exist..! */
+ if (frame_iconify_animating(client->frame))
+ event_end_ignore_all_enters(event_start_ignore_all_enters());
+
+ ob_debug_type(OB_DEBUG_FOCUS,
+ "%sNotify mode %d detail %d on %lx",
+ (e->type == EnterNotify ? "Enter" : "Leave"),
+ e->xcrossing.mode,
+ e->xcrossing.detail, (client?client->window:0));
+ if (grab_on_keyboard())
+ break;
+ if (config_focus_follow &&
+ /* leave inferior events can happen when the mouse goes onto
+ the window's border and then into the window before the
+ delay is up */
+ e->xcrossing.detail != NotifyInferior)
+ {
+ if (config_focus_delay && focus_delay_timeout_id)
+ g_source_remove(focus_delay_timeout_id);
+ if (config_unfocus_leave)
+ event_leave_client(client);
+ }
+ break;
+ default:
+ but = context_to_button(client->frame, con, FALSE);
+ if (but) {
+ *but = FALSE;
+ if (e->xcrossing.mode == NotifyGrab) {
+ but = context_to_button(client->frame, con, TRUE);
+ *but = FALSE;
+ }
+ frame_adjust_state(client->frame);
+ }
+ break;
+ }
+ break;
+ case EnterNotify:
+ {
+ con = frame_context(client, e->xcrossing.window,
+ e->xcrossing.x, e->xcrossing.y);
+ switch (con) {
+ case OB_FRAME_CONTEXT_FRAME:
+ if (grab_on_keyboard())
+ break;
+ if (e->xcrossing.mode == NotifyGrab ||
+ (e->xcrossing.mode == NotifyUngrab &&
+ /* ungrab enters are used when _under_ mouse is being used */
+ !(config_focus_follow && config_focus_under_mouse)) ||
+ /*ignore enters when we're already in the window */
+ e->xcrossing.detail == NotifyInferior)
+ {
+ ob_debug_type(OB_DEBUG_FOCUS,
+ "%sNotify mode %d detail %d serial %lu on %lx "
+ "IGNORED",
+ (e->type == EnterNotify ? "Enter" : "Leave"),
+ e->xcrossing.mode,
+ e->xcrossing.detail,
+ e->xcrossing.serial,
+ client?client->window:0);
+ }
+ else {
+ ob_debug_type(OB_DEBUG_FOCUS,
+ "%sNotify mode %d detail %d serial %lu on %lx, "
+ "focusing window",
+ (e->type == EnterNotify ? "Enter" : "Leave"),
+ e->xcrossing.mode,
+ e->xcrossing.detail,
+ e->xcrossing.serial,
+ (client?client->window:0));
+ if (config_focus_follow) {
+ if (config_focus_delay && unfocus_delay_timeout_id)
+ g_source_remove(unfocus_delay_timeout_id);
+ event_enter_client(client);
+ }
+ }
+ break;
+ default:
+ but = context_to_button(client->frame, con, FALSE);
+ if (but) {
+ *but = TRUE;
+ if (e->xcrossing.mode == NotifyUngrab) {
+ but = context_to_button(client->frame, con, TRUE);
+ *but = (con == pcon);
+ }
+ frame_adjust_state(client->frame);
+ }
+ break;
+ }
+ break;
+ }
+ case ConfigureRequest:
+ {
+ /* dont compress these unless you're going to watch for property
+ notifies in between (these can change what the configure would
+ do to the window).
+ also you can't compress stacking events
+ */
+
+ gint x, y, w, h;
+ gboolean move = FALSE;
+ gboolean resize = FALSE;
+
+ /* get the current area */
+ RECT_TO_DIMS(client->area, x, y, w, h);
+
+ ob_debug("ConfigureRequest for \"%s\" desktop %d wmstate %d "
+ "visible %d",
+ client->title,
+ screen_desktop, client->wmstate, client->frame->visible);
+ ob_debug(" x %d y %d w %d h %d b %d",
+ x, y, w, h, client->border_width);
+
+ if (e->xconfigurerequest.value_mask & CWBorderWidth)
+ if (client->border_width != e->xconfigurerequest.border_width) {
+ client->border_width = e->xconfigurerequest.border_width;
+
+ /* if the border width is changing then that is the same
+ as requesting a resize, but we don't actually change
+ the client's border, so it will change their root
+ coordinates (since they include the border width) and
+ we need to a notify then */
+ move = TRUE;
+ }
+
+ if (e->xconfigurerequest.value_mask & CWStackMode) {
+ ObClient *sibling = NULL;
+ gulong ignore_start;
+ gboolean ok = TRUE;
+
+ /* get the sibling */
+ if (e->xconfigurerequest.value_mask & CWSibling) {
+ ObWindow *win;
+ win = window_find(e->xconfigurerequest.above);
+ if (win && WINDOW_IS_CLIENT(win) &&
+ WINDOW_AS_CLIENT(win) != client)
+ {
+ sibling = WINDOW_AS_CLIENT(win);
+ }
+ else
+ /* an invalid sibling was specified so don't restack at
+ all, it won't make sense no matter what we do */
+ ok = FALSE;
+ }
+
+ if (ok) {
+ if (!config_focus_under_mouse)
+ ignore_start = event_start_ignore_all_enters();
+ stacking_restack_request(client, sibling,
+ e->xconfigurerequest.detail);
+ if (!config_focus_under_mouse)
+ event_end_ignore_all_enters(ignore_start);
+ }
+
+ /* a stacking change moves the window without resizing */
+ move = TRUE;
+ }
+
+ if ((e->xconfigurerequest.value_mask & CWX) ||
+ (e->xconfigurerequest.value_mask & CWY) ||
+ (e->xconfigurerequest.value_mask & CWWidth) ||
+ (e->xconfigurerequest.value_mask & CWHeight))
+ {
+ /* don't allow clients to move shaded windows (fvwm does this)
+ */
+ if (e->xconfigurerequest.value_mask & CWX) {
+ if (!client->shaded)
+ x = e->xconfigurerequest.x;
+ move = TRUE;
+ }
+ if (e->xconfigurerequest.value_mask & CWY) {
+ if (!client->shaded)
+ y = e->xconfigurerequest.y;
+ move = TRUE;
+ }
+
+ if (e->xconfigurerequest.value_mask & CWWidth) {
+ w = e->xconfigurerequest.width;
+ resize = TRUE;
+ }
+ if (e->xconfigurerequest.value_mask & CWHeight) {
+ h = e->xconfigurerequest.height;
+ resize = TRUE;
+ }
+ }
+
+ ob_debug("ConfigureRequest x(%d) %d y(%d) %d w(%d) %d h(%d) %d "
+ "move %d resize %d",
+ e->xconfigurerequest.value_mask & CWX, x,
+ e->xconfigurerequest.value_mask & CWY, y,
+ e->xconfigurerequest.value_mask & CWWidth, w,
+ e->xconfigurerequest.value_mask & CWHeight, h,
+ move, resize);
+
+ /* check for broken apps moving to their root position
+
+ XXX remove this some day...that would be nice. right now all
+ kde apps do this when they try activate themselves on another
+ desktop. eg. open amarok window on desktop 1, switch to desktop
+ 2, click amarok tray icon. it will move by its decoration size.
+ */
+ if (x != client->area.x &&
+ x == (client->frame->area.x + client->frame->size.left -
+ (gint)client->border_width) &&
+ y != client->area.y &&
+ y == (client->frame->area.y + client->frame->size.top -
+ (gint)client->border_width) &&
+ w == client->area.width &&
+ h == client->area.height)
+ {
+ ob_debug_type(OB_DEBUG_APP_BUGS,
+ "Application %s is trying to move via "
+ "ConfigureRequest to it's root window position "
+ "but it is not using StaticGravity",
+ client->title);
+ /* don't move it */
+ x = client->area.x;
+ y = client->area.y;
+
+ /* they still requested a move, so don't change whether a
+ notify is sent or not */
+ }
+
+ /* check for broken apps (java swing) moving to 0,0 when there is a
+ strut there.
+
+ XXX remove this some day...that would be nice. but really unexpected
+ from Sun Microsystems.
+ */
+ if (x == 0 && y == 0 && client->gravity == NorthWestGravity &&
+ client_normal(client))
+ {
+ const Rect to = { x, y, w, h };
+
+ /* oldschool fullscreen windows are allowed */
+ if (!client_is_oldfullscreen(client, &to)) {
+ Rect *r;
+
+ r = screen_area(client->desktop, SCREEN_AREA_ALL_MONITORS,
+ NULL);
+ if (r->x || r->y) {
+ /* move the window only to the corner outside struts */
+ x = r->x;
+ y = r->y;
+
+ ob_debug_type(OB_DEBUG_APP_BUGS,
+ "Application %s is trying to move via "
+ "ConfigureRequest to 0,0 using "
+ "NorthWestGravity, while there is a "
+ "strut there. "
+ "Moving buggy app from (0,0) to (%d,%d)",
+ client->title, r->x, r->y);
+ }
+
+ g_slice_free(Rect, r);
+
+ /* they still requested a move, so don't change whether a
+ notify is sent or not */
+ }
+ }
+
+ {
+ gint lw, lh;
+
+ client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
+
+ /* if x was not given, then use gravity to figure out the new
+ x. the reference point should not be moved */
+ if ((e->xconfigurerequest.value_mask & CWWidth &&
+ !(e->xconfigurerequest.value_mask & CWX)))
+ client_gravity_resize_w(client, &x, client->area.width, w);
+ /* same for y */
+ if ((e->xconfigurerequest.value_mask & CWHeight &&
+ !(e->xconfigurerequest.value_mask & CWY)))
+ client_gravity_resize_h(client, &y, client->area.height,h);
+
+ client_find_onscreen(client, &x, &y, w, h, FALSE);
+
+ ob_debug("Granting ConfigureRequest x %d y %d w %d h %d",
+ x, y, w, h);
+ client_configure(client, x, y, w, h, FALSE, TRUE, TRUE);
+ }
+ break;
+ }
+ case UnmapNotify:
+ ob_debug("UnmapNotify for window 0x%x eventwin 0x%x sendevent %d "
+ "ignores left %d",
+ client->window, e->xunmap.event, e->xunmap.from_configure,
+ client->ignore_unmaps);
+ if (client->ignore_unmaps) {
+ client->ignore_unmaps--;
+ break;
+ }
+ client_unmanage(client);
+ break;
+ case DestroyNotify:
+ ob_debug("DestroyNotify for window 0x%x", client->window);
+ client_unmanage(client);
+ break;
+ case ReparentNotify:
+ /* this is when the client is first taken captive in the frame */
+ if (e->xreparent.parent == client->frame->window) break;
+
+ /*
+ This event is quite rare and is usually handled in unmapHandler.
+ However, if the window is unmapped when the reparent event occurs,
+ the window manager never sees it because an unmap event is not sent
+ to an already unmapped window.
+ */
+
+ ob_debug("ReparentNotify for window 0x%x", client->window);
+ client_unmanage(client);
+ break;
+ case MapRequest:
+ ob_debug("MapRequest for 0x%lx", client->window);
+ if (!client->iconic) break; /* this normally doesn't happen, but if it
+ does, we don't want it!
+ it can happen now when the window is on
+ another desktop, but we still don't
+ want it! */
+ client_activate(client, FALSE, FALSE, TRUE, TRUE, TRUE);
+ break;
+ case ClientMessage:
+ /* validate cuz we query stuff off the client here */
+ if (!client_validate(client)) break;
+
+ if (e->xclient.format != 32) return;
+
+ msgtype = e->xclient.message_type;
+ if (msgtype == OBT_PROP_ATOM(WM_CHANGE_STATE)) {
+ if (!more_client_message_event(client->window, msgtype))
+ client_set_wm_state(client, e->xclient.data.l[0]);
+ } else if (msgtype == OBT_PROP_ATOM(NET_WM_DESKTOP)) {
+ if (!more_client_message_event(client->window, msgtype) &&
+ ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
+ (unsigned)e->xclient.data.l[0] == DESKTOP_ALL))
+ {
+ client_set_desktop(client, (unsigned)e->xclient.data.l[0],
+ FALSE, FALSE);
+ }
+ } else if (msgtype == OBT_PROP_ATOM(NET_WM_STATE)) {
+ gulong ignore_start;
+
+ /* can't compress these */
+ ob_debug("net_wm_state %s %ld %ld for 0x%lx",
+ (e->xclient.data.l[0] == 0 ? "Remove" :
+ e->xclient.data.l[0] == 1 ? "Add" :
+ e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
+ e->xclient.data.l[1], e->xclient.data.l[2],
+ client->window);
+
+ /* ignore enter events caused by these like ob actions do */
+ if (!config_focus_under_mouse)
+ ignore_start = event_start_ignore_all_enters();
+ client_set_state(client, e->xclient.data.l[0],
+ e->xclient.data.l[1], e->xclient.data.l[2]);
+ if (!config_focus_under_mouse)
+ event_end_ignore_all_enters(ignore_start);
+ } else if (msgtype == OBT_PROP_ATOM(NET_CLOSE_WINDOW)) {
+ ob_debug("net_close_window for 0x%lx", client->window);
+ client_close(client);
+ } else if (msgtype == OBT_PROP_ATOM(NET_ACTIVE_WINDOW)) {
+ ob_debug("net_active_window for 0x%lx source=%s",
+ client->window,
+ (e->xclient.data.l[0] == 0 ? "unknown" :
+ (e->xclient.data.l[0] == 1 ? "application" :
+ (e->xclient.data.l[0] == 2 ? "user" : "INVALID"))));
+ /* XXX make use of data.l[2] !? */
+ if (e->xclient.data.l[0] == 1 || e->xclient.data.l[0] == 2) {
+ /* we can not trust the timestamp from applications.
+ e.g. chromium passes a very old timestamp. openbox thinks
+ the window will get focus and calls XSetInputFocus with the
+ (old) timestamp, which doesn't end up moving focus at all.
+ but the window is raised, not hilited, etc, as if it was
+ really going to get focus.
+
+ so do not use this timestamp in event_curtime, as this would
+ be used in XSetInputFocus.
+ */
+ event_sourcetime = e->xclient.data.l[1];
+ if (e->xclient.data.l[1] == 0)
+ ob_debug_type(OB_DEBUG_APP_BUGS,
+ "_NET_ACTIVE_WINDOW message for window %s is"
+ " missing a timestamp", client->title);
+ } else
+ ob_debug_type(OB_DEBUG_APP_BUGS,
+ "_NET_ACTIVE_WINDOW message for window %s is "
+ "missing source indication", client->title);
+ /* TODO(danakj) This should use
+ (e->xclient.data.l[0] == 0 ||
+ e->xclient.data.l[0] == 2)
+ to determine if a user requested the activation, however GTK+
+ applications seem unable to make this distinction ever
+ (including panels such as xfce4-panel and gnome-panel).
+ So we are left just assuming all activations are from the user.
+ */
+ client_activate(client, FALSE, FALSE, TRUE, TRUE, TRUE);
+ } else if (msgtype == OBT_PROP_ATOM(NET_WM_MOVERESIZE)) {
+ ob_debug("net_wm_moveresize for 0x%lx direction %d",
+ client->window, e->xclient.data.l[2]);
+ if ((Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPLEFT) ||
+ (Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOP) ||
+ (Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_TOPRIGHT) ||
+ (Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) ||
+ (Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_RIGHT) ||
+ (Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT) ||
+ (Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOM) ||
+ (Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT) ||
+ (Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_LEFT) ||
+ (Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE) ||
+ (Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_SIZE_KEYBOARD) ||
+ (Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_MOVE_KEYBOARD))
+ {
+ moveresize_start(client, e->xclient.data.l[0],
+ e->xclient.data.l[1], e->xclient.data.l[3],
+ e->xclient.data.l[2]);
+ }
+ else if ((Atom)e->xclient.data.l[2] ==
+ OBT_PROP_ATOM(NET_WM_MOVERESIZE_CANCEL))
+ if (moveresize_client)
+ moveresize_end(TRUE);
+ } else if (msgtype == OBT_PROP_ATOM(NET_MOVERESIZE_WINDOW)) {
+ gint ograv, x, y, w, h;
+
+ ograv = client->gravity;
+
+ if (e->xclient.data.l[0] & 0xff)
+ client->gravity = e->xclient.data.l[0] & 0xff;
+
+ if (e->xclient.data.l[0] & 1 << 8)
+ x = e->xclient.data.l[1];
+ else
+ x = client->area.x;
+ if (e->xclient.data.l[0] & 1 << 9)
+ y = e->xclient.data.l[2];
+ else
+ y = client->area.y;
+
+ if (e->xclient.data.l[0] & 1 << 10) {
+ w = e->xclient.data.l[3];
+
+ /* if x was not given, then use gravity to figure out the new
+ x. the reference point should not be moved */
+ if (!(e->xclient.data.l[0] & 1 << 8))
+ client_gravity_resize_w(client, &x, client->area.width, w);
+ }
+ else
+ w = client->area.width;
+
+ if (e->xclient.data.l[0] & 1 << 11) {
+ h = e->xclient.data.l[4];
+
+ /* same for y */
+ if (!(e->xclient.data.l[0] & 1 << 9))
+ client_gravity_resize_h(client, &y, client->area.height,h);
+ }
+ else
+ h = client->area.height;
+
+ ob_debug("MOVERESIZE x %d %d y %d %d (gravity %d)",
+ e->xclient.data.l[0] & 1 << 8, x,
+ e->xclient.data.l[0] & 1 << 9, y,
+ client->gravity);
+
+ client_find_onscreen(client, &x, &y, w, h, FALSE);
+
+ client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
+
+ client->gravity = ograv;
+ } else if (msgtype == OBT_PROP_ATOM(NET_RESTACK_WINDOW)) {
+ if (e->xclient.data.l[0] != 2) {
+ ob_debug_type(OB_DEBUG_APP_BUGS,
+ "_NET_RESTACK_WINDOW sent for window %s with "
+ "invalid source indication %ld",
+ client->title, e->xclient.data.l[0]);
+ } else {
+ ObClient *sibling = NULL;
+ if (e->xclient.data.l[1]) {
+ ObWindow *win = window_find(e->xclient.data.l[1]);
+ if (WINDOW_IS_CLIENT(win) &&
+ WINDOW_AS_CLIENT(win) != client)
+ {
+ sibling = WINDOW_AS_CLIENT(win);
+ }
+ if (sibling == NULL)
+ ob_debug_type(OB_DEBUG_APP_BUGS,
+ "_NET_RESTACK_WINDOW sent for window %s "
+ "with invalid sibling 0x%x",
+ client->title, e->xclient.data.l[1]);
+ }
+ if (e->xclient.data.l[2] == Below ||
+ e->xclient.data.l[2] == BottomIf ||
+ e->xclient.data.l[2] == Above ||
+ e->xclient.data.l[2] == TopIf ||
+ e->xclient.data.l[2] == Opposite)
+ {
+ gulong ignore_start;
+
+ if (!config_focus_under_mouse)
+ ignore_start = event_start_ignore_all_enters();
+ /* just raise, don't activate */
+ stacking_restack_request(client, sibling,
+ e->xclient.data.l[2]);
+ if (!config_focus_under_mouse)
+ event_end_ignore_all_enters(ignore_start);
+
+ /* send a synthetic ConfigureNotify, cuz this is supposed
+ to be like a ConfigureRequest. */
+ client_reconfigure(client, TRUE);
+ } else
+ ob_debug_type(OB_DEBUG_APP_BUGS,
+ "_NET_RESTACK_WINDOW sent for window %s "
+ "with invalid detail %d",
+ client->title, e->xclient.data.l[2]);
+ }
+ }
+ break;
+ case PropertyNotify:
+ /* validate cuz we query stuff off the client here */
+ if (!client_validate(client)) break;
+
+ msgtype = e->xproperty.atom;
+
+ /* ignore changes to some properties if there is another change
+ coming in the queue */
+ {
+ struct ObSkipPropertyChange s;
+ s.window = client->window;
+ s.prop = msgtype;
+ if (xqueue_exists_local(skip_property_change, &s))
+ break;
+ }
+
+ msgtype = e->xproperty.atom;
+ if (msgtype == XA_WM_NORMAL_HINTS) {
+ int x, y, w, h, lw, lh;
+
+ ob_debug("Update NORMAL hints");
+ client_update_normal_hints(client);
+ /* normal hints can make a window non-resizable */
+ client_setup_decor_and_functions(client, FALSE);
+
+ x = client->area.x;
+ y = client->area.y;
+ w = client->area.width;
+ h = client->area.height;
+
+ /* apply the new normal hints */
+ client_try_configure(client, &x, &y, &w, &h, &lw, &lh, FALSE);
+ /* make sure the window is visible, and if the window is resized
+ off-screen due to the normal hints changing then this will push
+ it back onto the screen. */
+ client_find_onscreen(client, &x, &y, w, h, FALSE);
+
+ /* make sure the client's sizes are within its bounds, but don't
+ make it reply with a configurenotify unless something changed.
+ emacs will update its normal hints every time it receives a
+ configurenotify */
+ client_configure(client, x, y, w, h, FALSE, TRUE, FALSE);
+ } else if (msgtype == OBT_PROP_ATOM(MOTIF_WM_HINTS)) {
+ client_get_mwm_hints(client);
+ /* This can override some mwm hints */
+ client_get_type_and_transientness(client);
+
+ /* Apply the changes to the window */
+ client_setup_decor_and_functions(client, TRUE);
+ } else if (msgtype == XA_WM_HINTS) {
+ client_update_wmhints(client);
+ } else if (msgtype == XA_WM_TRANSIENT_FOR) {
+ /* get the transient-ness first, as this affects if the client
+ decides to be transient for the group or not in
+ client_update_transient_for() */
+ client_get_type_and_transientness(client);
+ client_update_transient_for(client);
+ /* type may have changed, so update the layer */
+ client_calc_layer(client);
+ client_setup_decor_and_functions(client, TRUE);
+ } else if (msgtype == OBT_PROP_ATOM(NET_WM_NAME) ||
+ msgtype == OBT_PROP_ATOM(WM_NAME) ||
+ msgtype == OBT_PROP_ATOM(NET_WM_ICON_NAME) ||
+ msgtype == OBT_PROP_ATOM(WM_ICON_NAME)) {
+ client_update_title(client);
+ } else if (msgtype == OBT_PROP_ATOM(WM_PROTOCOLS)) {
+ client_update_protocols(client);
+ }
+ else if (msgtype == OBT_PROP_ATOM(NET_WM_STRUT) ||
+ msgtype == OBT_PROP_ATOM(NET_WM_STRUT_PARTIAL)) {
+ client_update_strut(client);
+ }
+ else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON)) {
+ client_update_icons(client);
+ }
+ else if (msgtype == OBT_PROP_ATOM(NET_WM_ICON_GEOMETRY)) {
+ client_update_icon_geometry(client);
+ }
+ else if (msgtype == OBT_PROP_ATOM(NET_WM_USER_TIME)) {
+ guint32 t;
+ if (client == focus_client &&
+ OBT_PROP_GET32(client->window, NET_WM_USER_TIME, CARDINAL, &t)
+ && t && !event_time_after(t, e->xproperty.time) &&
+ (!event_last_user_time ||
+ event_time_after(t, event_last_user_time)))
+ {
+ event_last_user_time = t;
+ }
+ }
+ else if (msgtype == OBT_PROP_ATOM(NET_WM_WINDOW_OPACITY)) {
+ client_update_opacity(client);
+ }
+#ifdef SYNC
+ else if (msgtype == OBT_PROP_ATOM(NET_WM_SYNC_REQUEST_COUNTER)) {
+ /* if they are resizing right now this would cause weird behaviour.
+ if one day a user reports clients stop resizing, then handle
+ this better by resetting a new XSync alarm and stuff on the
+ new counter, but I expect it will never happen */
+ if (moveresize_client == client)
+ moveresize_end(FALSE);
+ client_update_sync_request_counter(client);
+ }
+#endif
+ break;
+ case ColormapNotify:
+ client_update_colormap(client, e->xcolormap.colormap);
+ break;
+ default:
+ ;
+#ifdef SHAPE
+ {
+ int kind;
+ if (obt_display_extension_shape &&
+ e->type == obt_display_extension_shape_basep)
+ {
+ switch (((XShapeEvent*)e)->kind) {
+ case ShapeBounding:
+ case ShapeClip:
+ client->shaped = ((XShapeEvent*)e)->shaped;
+ kind = ShapeBounding;
+ break;
+#ifdef ShapeInput
+ case ShapeInput:
+ client->shaped_input = ((XShapeEvent*)e)->shaped;
+ kind = ShapeInput;
+ break;
+#endif
+ default:
+ g_assert_not_reached();
+ }
+ frame_adjust_shape_kind(client->frame, kind);
+ }
+ }
+#endif
+ }
+}
+
+static void event_handle_dock(ObDock *s, XEvent *e)
+{
+ switch (e->type) {
+ case EnterNotify:
+ dock_hide(FALSE);
+ break;
+ case LeaveNotify:
+ /* don't hide when moving into a dock app */
+ if (e->xcrossing.detail != NotifyInferior)
+ dock_hide(TRUE);
+ break;
+ }
+}
+
+static void event_handle_dockapp(ObDockApp *app, XEvent *e)
+{
+ switch (e->type) {
+ case MotionNotify:
+ dock_app_drag(app, &e->xmotion);
+ break;
+ case UnmapNotify:
+ if (app->ignore_unmaps) {
+ app->ignore_unmaps--;
+ break;
+ }
+ dock_unmanage(app, TRUE);
+ break;
+ case DestroyNotify:
+ case ReparentNotify:
+ dock_unmanage(app, FALSE);
+ break;
+ case ConfigureNotify:
+ dock_app_configure(app, e->xconfigure.width, e->xconfigure.height);
+ break;
+ }
+}
+
+static ObMenuFrame* find_active_menu(void)
+{
+ GList *it;
+ ObMenuFrame *ret = NULL;
+
+ for (it = menu_frame_visible; it; it = g_list_next(it)) {
+ ret = it->data;
+ if (ret->selected)
+ break;
+ ret = NULL;
+ }
+ return ret;
+}
+
+static ObMenuFrame* find_active_or_last_menu(void)
+{
+ ObMenuFrame *ret = NULL;
+
+ ret = find_active_menu();
+ if (!ret && menu_frame_visible)
+ ret = menu_frame_visible->data;
+ return ret;
+}
+
+static gboolean event_handle_prompt(ObPrompt *p, XEvent *e)
+{
+ switch (e->type) {
+ case ButtonPress:
+ case ButtonRelease:
+ case MotionNotify:
+ return prompt_mouse_event(p, e);
+ break;
+ case KeyPress:
+ return prompt_key_event(p, e);
+ break;
+ }
+ return FALSE;
+}
+
+static gboolean event_handle_menu_input(XEvent *ev)
+{
+ gboolean ret = FALSE;
+
+ if (ev->type == ButtonRelease || ev->type == ButtonPress) {
+ ObMenuEntryFrame *e;
+
+ if ((ev->xbutton.button < 4 || ev->xbutton.button > 5) &&
+ ((ev->type == ButtonRelease && menu_hide_delay_reached()) ||
+ ev->type == ButtonPress))
+ {
+ if ((e = menu_entry_frame_under(ev->xbutton.x_root,
+ ev->xbutton.y_root)))
+ {
+ if (ev->type == ButtonPress && e->frame->child)
+ menu_frame_select(e->frame->child, NULL, TRUE);
+ menu_frame_select(e->frame, e, TRUE);
+ if (ev->type == ButtonRelease)
+ menu_entry_frame_execute(e, ev->xbutton.state);
+ }
+ else
+ menu_frame_hide_all();
+ }
+ ret = TRUE;
+ }
+ else if (ev->type == KeyPress || ev->type == KeyRelease) {
+ guint mods;
+ ObMenuFrame *frame;
+
+ /* get the modifiers */
+ mods = obt_keyboard_only_modmasks(ev->xkey.state);
+
+ frame = find_active_or_last_menu();
+ if (frame == NULL)
+ g_assert_not_reached(); /* there is no active menu */
+
+ /* Allow control while going thru the menu */
+ else if (ev->type == KeyPress && (mods & ~ControlMask) == 0) {
+ gunichar unikey;
+ KeySym sym;
+
+ frame->got_press = TRUE;
+ frame->press_keycode = ev->xkey.keycode;
+ frame->press_doexec = FALSE;
+
+ sym = obt_keyboard_keypress_to_keysym(ev);
+
+ if (sym == XK_Escape) {
+ menu_frame_hide_all();
+ ret = TRUE;
+ }
+
+ else if (sym == XK_Left) {
+ /* Left goes to the parent menu */
+ if (frame->parent) {
+ /* remove focus from the child */
+ menu_frame_select(frame, NULL, TRUE);
+ /* and put it in the parent */
+ menu_frame_select(frame->parent, frame->parent->selected,
+ TRUE);
+ }
+ ret = TRUE;
+ }
+
+ else if (sym == XK_Right || sym == XK_Return || sym == XK_KP_Enter)
+ {
+ /* Right and enter goes to the selected submenu.
+ Enter executes instead if it's not on a submenu. */
+
+ if (frame->selected) {
+ const ObMenuEntryType t = frame->selected->entry->type;
+
+ if (t == OB_MENU_ENTRY_TYPE_SUBMENU) {
+ /* make sure it is visible */
+ menu_frame_select(frame, frame->selected, TRUE);
+ /* move focus to the child menu */
+ menu_frame_select_next(frame->child);
+ }
+ else if (sym != XK_Right) {
+ frame->press_doexec = TRUE;
+ }
+ }
+ ret = TRUE;
+ }
+
+ else if (sym == XK_Up) {
+ menu_frame_select_previous(frame);
+ ret = TRUE;
+ }
+
+ else if (sym == XK_Down) {
+ menu_frame_select_next(frame);
+ ret = TRUE;
+ }
+
+ else if (sym == XK_Home) {
+ menu_frame_select_first(frame);
+ ret = TRUE;
+ }
+
+ else if (sym == XK_End) {
+ menu_frame_select_last(frame);
+ ret = TRUE;
+ }
+
+ /* keyboard accelerator shortcuts. (if it was a valid key) */
+ else if (frame->entries &&
+ (unikey =
+ obt_keyboard_keypress_to_unichar(menu_frame_ic(frame),
+ ev)))
+ {
+ GList *start;
+ GList *it;
+ ObMenuEntryFrame *found = NULL;
+ guint num_found = 0;
+
+ /* start after the selected one */
+ start = frame->entries;
+ if (frame->selected) {
+ for (it = start; frame->selected != it->data;
+ it = g_list_next(it))
+ g_assert(it != NULL); /* nothing was selected? */
+ /* next with wraparound */
+ start = g_list_next(it);
+ if (start == NULL) start = frame->entries;
+ }
+
+ it = start;
+ do {
+ ObMenuEntryFrame *e = it->data;
+ gunichar entrykey = 0;
+
+ if (e->entry->type == OB_MENU_ENTRY_TYPE_NORMAL)
+ entrykey = e->entry->data.normal.shortcut;
+ else if (e->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU)
+ entrykey = e->entry->data.submenu.submenu->shortcut;
+
+ if (unikey == entrykey) {
+ if (found == NULL) found = e;
+ ++num_found;
+ }
+
+ /* next with wraparound */
+ it = g_list_next(it);
+ if (it == NULL) it = frame->entries;
+ } while (it != start);
+
+ if (found) {
+ menu_frame_select(frame, found, TRUE);
+
+ if (num_found == 1) {
+ if (found->entry->type == OB_MENU_ENTRY_TYPE_SUBMENU) {
+ /* move focus to the child menu */
+ menu_frame_select_next(frame->child);
+ }
+ else {
+ frame->press_doexec = TRUE;
+ }
+ }
+ ret = TRUE;
+ }
+ }
+ }
+
+ /* Use KeyRelease events for running things so that the key release
+ doesn't get sent to the focused application.
+
+ Allow ControlMask only, and don't bother if the menu is empty */
+ else if (ev->type == KeyRelease && (mods & ~ControlMask) == 0) {
+ if (frame->press_keycode == ev->xkey.keycode &&
+ frame->got_press &&
+ frame->press_doexec)
+ {
+ if (frame->selected)
+ menu_entry_frame_execute(frame->selected, ev->xkey.state);
+ }
+ }
+ }
+
+ return ret;
+}
+
+static gboolean event_look_for_menu_enter(XEvent *ev, gpointer data)
+{
+ const ObMenuFrame *f = (ObMenuFrame*)data;
+ ObMenuEntryFrame *e;
+ return ev->type == EnterNotify &&
+ (e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window)) &&
+ e->frame == f && !e->ignore_enters;
+}
+
+static void event_handle_menu(ObMenuFrame *frame, XEvent *ev)
+{
+ ObMenuFrame *f;
+ ObMenuEntryFrame *e;
+
+ switch (ev->type) {
+ case MotionNotify:
+ // We need to catch MotionNotify in addition to EnterNotify because
+ // it is possible for the menu to be opened under the mouse cursor, and
+ // moving the mouse should select the item.
+ if ((e = g_hash_table_lookup(menu_frame_map, &ev->xmotion.window))) {
+ if (e->ignore_enters)
+ --e->ignore_enters;
+ else if (!(f = find_active_menu()) ||
+ f == e->frame ||
+ f->parent == e->frame ||
+ f->child == e->frame)
+ menu_frame_select(e->frame, e, FALSE);
+ }
+ break;
+ case EnterNotify:
+ if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
+ if (e->ignore_enters)
+ --e->ignore_enters;
+ else if (!(f = find_active_menu()) ||
+ f == e->frame ||
+ f->parent == e->frame ||
+ f->child == e->frame)
+ menu_frame_select(e->frame, e, FALSE);
+ }
+ break;
+ case LeaveNotify:
+ /*ignore leaves when we're already in the window */
+ if (ev->xcrossing.detail == NotifyInferior)
+ break;
+
+ if ((e = g_hash_table_lookup(menu_frame_map, &ev->xcrossing.window))) {
+ /* check if an EnterNotify event is coming, and if not, then select
+ nothing in the menu */
+ if (!xqueue_exists_local(event_look_for_menu_enter, e->frame))
+ menu_frame_select(e->frame, NULL, FALSE);
+ }
+ break;
+ }
+}
+
+static gboolean event_handle_user_input(ObClient *client, XEvent *e)
+{
+ g_assert(e->type == ButtonPress || e->type == ButtonRelease ||
+ e->type == MotionNotify || e->type == KeyPress ||
+ e->type == KeyRelease);
+
+ if (menu_frame_visible) {
+ if (event_handle_menu_input(e))
+ /* don't use the event if the menu used it, but if the menu
+ didn't use it and it's a keypress that is bound, it will
+ close the menu and be used */
+ return TRUE;
+ }
+
+ /* if the keyboard interactive action uses the event then dont
+ use it for bindings. likewise is moveresize uses the event. */
+ if (actions_interactive_input_event(e) || moveresize_event(e))
+ return TRUE;
+
+ if (moveresize_in_progress)
+ /* make further actions work on the client being
+ moved/resized */
+ client = moveresize_client;
+
+ if (e->type == ButtonPress ||
+ e->type == ButtonRelease ||
+ e->type == MotionNotify)
+ {
+ /* the frame may not be "visible" but they can still click on it
+ in the case where it is animating before disappearing */
+ if (!client || !frame_iconify_animating(client->frame))
+ return mouse_event(client, e);
+ } else
+ return keyboard_event((focus_cycle_target ? focus_cycle_target :
+ (client ? client : focus_client)), e);
+
+ return FALSE;
+}
+
+static void focus_delay_dest(gpointer data)
+{
+ g_slice_free(ObFocusDelayData, data);
+ focus_delay_timeout_id = 0;
+ focus_delay_timeout_client = NULL;
+}
+
+static void unfocus_delay_dest(gpointer data)
+{
+ g_slice_free(ObFocusDelayData, data);
+ unfocus_delay_timeout_id = 0;
+ unfocus_delay_timeout_client = NULL;
+}
+
+static gboolean focus_delay_func(gpointer data)
+{
+ ObFocusDelayData *d = data;
+ Time old = event_curtime; /* save the curtime */
+
+ event_curtime = d->time;
+ event_curserial = d->serial;
+ if (client_focus(d->client) && config_focus_raise)
+ stacking_raise(CLIENT_AS_WINDOW(d->client));
+ event_curtime = old;
+ return FALSE; /* no repeat */
+}
+
+static gboolean unfocus_delay_func(gpointer data)
+{
+ ObFocusDelayData *d = data;
+ Time old = event_curtime; /* save the curtime */
+
+ event_curtime = d->time;
+ event_curserial = d->serial;
+ focus_nothing();
+ event_curtime = old;
+ return FALSE; /* no repeat */
+}
+
+static void focus_delay_client_dest(ObClient *client, gpointer data)
+{
+ if (focus_delay_timeout_client == client && focus_delay_timeout_id)
+ g_source_remove(focus_delay_timeout_id);
+ if (unfocus_delay_timeout_client == client && unfocus_delay_timeout_id)
+ g_source_remove(unfocus_delay_timeout_id);
+}
+
+void event_halt_focus_delay(void)
+{
+ /* ignore all enter events up till the event which caused this to occur */
+ if (event_curserial) event_ignore_enter_range(1, event_curserial);
+ if (focus_delay_timeout_id) g_source_remove(focus_delay_timeout_id);
+ if (unfocus_delay_timeout_id) g_source_remove(unfocus_delay_timeout_id);
+}
+
+gulong event_start_ignore_all_enters(void)
+{
+ return NextRequest(obt_display);
+}
+
+static void event_ignore_enter_range(gulong start, gulong end)
+{
+ ObSerialRange *r;
+
+ g_assert(start != 0);
+ g_assert(end != 0);
+
+ r = g_slice_new(ObSerialRange);
+ r->start = start;
+ r->end = end;
+ ignore_serials = g_slist_prepend(ignore_serials, r);
+
+ ob_debug_type(OB_DEBUG_FOCUS, "ignoring enters from %lu until %lu",
+ r->start, r->end);
+
+ /* increment the serial so we don't ignore events we weren't meant to */
+ OBT_PROP_ERASE(screen_support_win, MOTIF_WM_HINTS);
+}
+
+void event_end_ignore_all_enters(gulong start)
+{
+ /* Use (NextRequest-1) so that we ignore up to the current serial only.
+ Inside event_ignore_enter_range, we increment the serial by one, but if
+ we ignore that serial too, then any enter events generated by mouse
+ movement will be ignored until we create some further network traffic.
+ Instead ignore up to NextRequest-1, then when we increment the serial,
+ we will be *past* the range of ignored serials */
+ event_ignore_enter_range(start, NextRequest(obt_display)-1);
+}
+
+static gboolean is_enter_focus_event_ignored(gulong serial)
+{
+ GSList *it, *next;
+
+ for (it = ignore_serials; it; it = next) {
+ ObSerialRange *r = it->data;
+
+ next = g_slist_next(it);
+
+ if ((glong)(serial - r->end) > 0) {
+ /* past the end */
+ ignore_serials = g_slist_delete_link(ignore_serials, it);
+ g_slice_free(ObSerialRange, r);
+ }
+ else if ((glong)(serial - r->start) >= 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void event_cancel_all_key_grabs(void)
+{
+ if (actions_interactive_act_running()) {
+ actions_interactive_cancel_act();
+ ob_debug("KILLED interactive action");
+ }
+ else if (menu_frame_visible) {
+ menu_frame_hide_all();
+ ob_debug("KILLED open menus");
+ }
+ else if (moveresize_in_progress) {
+ moveresize_end(TRUE);
+ ob_debug("KILLED interactive moveresize");
+ }
+ else if (grab_on_keyboard()) {
+ ungrab_keyboard();
+ ob_debug("KILLED active grab on keyboard");
+ }
+ else
+ ungrab_passive_key();
+
+ XSync(obt_display, FALSE);
+}
+
+gboolean event_time_after(guint32 t1, guint32 t2)
+{
+ g_assert(t1 != CurrentTime);
+ g_assert(t2 != CurrentTime);
+
+ /*
+ Timestamp values wrap around (after about 49.7 days). The server, given
+ its current time is represented by timestamp T, always interprets
+ timestamps from clients by treating half of the timestamp space as being
+ later in time than T.
+ - http://tronche.com/gui/x/xlib/input/pointer-grabbing.html
+ */
+
+ /* TIME_HALF is not half of the number space of a Time type variable.
+ * Rather, it is half the number space of a timestamp value, which is
+ * always 32 bits. */
+#define TIME_HALF (guint32)(1 << 31)
+
+ if (t2 >= TIME_HALF)
+ /* t2 is in the second half so t1 might wrap around and be smaller than
+ t2 */
+ return t1 >= t2 || t1 < (t2 + TIME_HALF);
+ else
+ /* t2 is in the first half so t1 has to come after it */
+ return t1 >= t2 && t1 < (t2 + TIME_HALF);
+}
+
+gboolean find_timestamp(XEvent *e, gpointer data)
+{
+ const Time t = event_get_timestamp(e);
+ if (t && t >= event_curtime) {
+ event_curtime = t;
+ return TRUE;
+ }
+ else
+ return FALSE;
+}
+
+static Time next_time(void)
+{
+ /* Some events don't come with timestamps :(
+ ...but we can get one anyways >:) */
+
+ /* Generate a timestamp so there is guaranteed at least one in the queue
+ eventually */
+ XChangeProperty(obt_display, screen_support_win,
+ OBT_PROP_ATOM(WM_CLASS), OBT_PROP_ATOM(STRING),
+ 8, PropModeAppend, NULL, 0);
+
+ /* Grab the first timestamp available */
+ xqueue_exists(find_timestamp, NULL);
+
+ /*g_assert(event_curtime != CurrentTime);*/
+
+ /* Save the time so we don't have to do this again for this event */
+ return event_curtime;
+}
+
+Time event_time(void)
+{
+ if (event_curtime) return event_curtime;
+
+ return next_time();
+}
+
+Time event_source_time(void)
+{
+ return event_sourcetime;
+}
+
+void event_reset_time(void)
+{
+ next_time();
+}
+
+void event_update_user_time(void)
+{
+ event_last_user_time = event_time();
+}
+
+void event_reset_user_time(void)
+{
+ event_last_user_time = CurrentTime;
+}