/* * X events * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts * * 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., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* for CARD32. */ #include "sdorfehs.h" /* * The event currently being processed. Mostly used in functions from action.c * which need to forward events to other windows. */ XEvent rp_current_event; /* RAISED is non zero if a raised message should be used 0 for a map message. */ void show_rudeness_msg(rp_window *win, int raised) { rp_vscreen *v = win->vscreen; rp_window_elem *elem = vscreen_find_window(&v->mapped_windows, win); if (v == rp_current_vscreen) { if (win->transient) marked_message_printf(0, 0, raised ? MESSAGE_RAISE_TRANSIENT : MESSAGE_MAP_TRANSIENT, elem->number, window_name(win)); else marked_message_printf(0, 0, raised ? MESSAGE_RAISE_WINDOW : MESSAGE_MAP_WINDOW, elem->number, window_name(win)); } else { if (win->transient) marked_message_printf(0, 0, raised ? MESSAGE_RAISE_TRANSIENT_VSCREEN : MESSAGE_MAP_TRANSIENT_VSCREEN, elem->number, window_name(win), win->vscreen->number); else marked_message_printf(0, 0, raised ? MESSAGE_RAISE_WINDOW_VSCREEN : MESSAGE_MAP_WINDOW_VSCREEN, elem->number, window_name(win), win->vscreen->number); } } static void new_window(XCreateWindowEvent *e) { rp_window *win; if (e->override_redirect) return; if (is_rp_window(e->window)) return; win = find_window(e->window); if (win == NULL) { /* We'll figure out which vscreen to put this window in later */ win = add_to_window_list(rp_current_screen, e->window); } update_window_information(win); PRINT_DEBUG(("created new window\n")); } static void unmap_notify(XEvent *ev) { rp_frame *frame; rp_window *win; /* ignore SubstructureNotify unmaps. */ if (ev->xunmap.event != ev->xunmap.window && ev->xunmap.send_event != True) return; /* FIXME: Should we only look in the mapped window list? */ win = find_window_in_list(ev->xunmap.window, &rp_mapped_window); if (win == NULL) return; switch (win->state) { case IconicState: PRINT_DEBUG(("Withdrawing iconized window '%s'\n", window_name(win))); if (ev->xunmap.send_event) withdraw_window(win); break; case NormalState: PRINT_DEBUG(("Withdrawing normal window '%s'\n", window_name(win))); /* * If the window was inside a frame, fill the frame with * another window. */ frame = find_windows_frame(win); if (frame) { cleanup_frame(frame); if (frame->number == win->vscreen->current_frame && rp_current_vscreen == win->vscreen) set_active_frame(frame, 0); /* Since we may have switched windows, call the hook. */ if (frame->win_number != EMPTY) hook_run(&rp_switch_win_hook); } withdraw_window(win); break; } update_window_names(win->vscreen->screen, defaults.window_fmt); } static void map_request(Window window) { rp_window *win; win = find_window(window); if (win == NULL) { PRINT_DEBUG(("Map request from an unknown window.\n")); XMapWindow(dpy, window); return; } if (unmanaged_window(window)) { PRINT_DEBUG(("Map request from an unmanaged window\n")); unmanage(win); XMapRaised(dpy, window); return; } PRINT_DEBUG(("Map request from a managed window\n")); switch (win->state) { case WithdrawnState: if (unmanaged_window(win->w)) { PRINT_DEBUG(("Mapping Unmanaged Window\n")); XMapWindow(dpy, win->w); break; } else { PRINT_DEBUG(("Mapping Withdrawn Window\n")); map_window(win); break; } break; case IconicState: PRINT_DEBUG(("Mapping Iconic window\n")); if (win->vscreen != rp_current_vscreen) { if (!rp_honour_vscreen_switch) { show_rudeness_msg(win, 1); break; } PRINT_DEBUG(("honouring raise from other vscreen\n")); } /* Depending on the rudeness level, actually map the window. */ if (win->last_access == 0) { if ((rp_honour_transient_map && win->transient) || (rp_honour_normal_map && !win->transient)) { set_current_vscreen(win->vscreen); set_active_window(win); } } else { if ((rp_honour_transient_raise && win->transient) || (rp_honour_normal_raise && !win->transient)) { set_current_vscreen(win->vscreen); set_active_window(win); } else show_rudeness_msg(win, 1); } break; default: PRINT_DEBUG(("Map request for window in unknown state %d\n", win->state)); } } static void destroy_window(XDestroyWindowEvent *ev) { rp_window *win; win = find_window(ev->window); if (win == NULL) return; ignore_badwindow++; /* * If, somehow, the window is not withdrawn before it is destroyed, * perform the necessary steps to withdraw the window before it is * unmanaged. */ if (win->state == IconicState) { PRINT_DEBUG(("Destroying Iconic Window (%s)\n", window_name(win))); withdraw_window(win); } else if (win->state == NormalState) { rp_frame *frame; PRINT_DEBUG(("Destroying Normal Window (%s)\n", window_name(win))); frame = find_windows_frame(win); if (frame) { cleanup_frame(frame); if (frame->number == win->vscreen->current_frame && rp_current_vscreen == win->vscreen) set_active_frame(frame, 0); /* Since we may have switched windows, call the hook. */ if (frame->win_number != EMPTY) hook_run(&rp_switch_win_hook); } withdraw_window(win); } /* * Now that the window is guaranteed to be in the unmapped window list, * we can safely stop managing it. */ unmanage(win); ignore_badwindow--; } static void configure_request(XConfigureRequestEvent *e) { XWindowChanges changes; rp_window *win; win = find_window(e->window); if (win) { if (e->value_mask & CWStackMode) { if (e->detail == Above && win->state != WithdrawnState) { if (win->vscreen == rp_current_vscreen) { /* * Depending on the rudeness level, * actually map the window. */ if ((rp_honour_transient_raise && win->transient) || (rp_honour_normal_raise && !win->transient)) { if (win->state == IconicState) set_active_window(win); else if (find_windows_frame(win)) goto_window(win); } else if (current_window() != win) { show_rudeness_msg(win, 1); } } else { show_rudeness_msg(win, 1); } } PRINT_DEBUG(("request CWStackMode %d\n", e->detail)); } PRINT_DEBUG(("'%s' window size: %d %d %d %d %d\n", window_name(win), win->x, win->y, win->width, win->height, win->border)); /* Collect the changes to be granted. */ if (e->value_mask & CWBorderWidth) { changes.border_width = e->border_width; win->border = e->border_width; PRINT_DEBUG(("request CWBorderWidth %d\n", e->border_width)); } if (e->value_mask & CWWidth) { changes.width = e->width; win->width = e->width; PRINT_DEBUG(("request CWWidth %d\n", e->width)); } if (e->value_mask & CWHeight) { changes.height = e->height; win->height = e->height; PRINT_DEBUG(("request CWHeight %d\n", e->height)); } if (e->value_mask & CWX) { changes.x = e->x; win->x = e->x; PRINT_DEBUG(("request CWX %d\n", e->x)); } if (e->value_mask & CWY) { changes.y = e->y; win->y = e->y; PRINT_DEBUG(("request CWY %d\n", e->y)); } if (e->value_mask & (CWX|CWY|CWBorderWidth|CWWidth|CWHeight)) { /* Grant the request, then immediately maximize it. */ XConfigureWindow(dpy, win->w, e->value_mask & (CWX|CWY|CWBorderWidth|CWWidth|CWHeight), &changes); XSync(dpy, False); if (win->state == NormalState) maximize(win); } } else { /* * Its an unmanaged window, so give it what it wants. But don't * change the stack mode. */ if (e->value_mask & CWX) changes.x = e->x; if (e->value_mask & CWY) changes.x = e->x; if (e->value_mask & CWWidth) changes.x = e->x; if (e->value_mask & CWHeight) changes.x = e->x; if (e->value_mask & CWBorderWidth) changes.x = e->x; XConfigureWindow(dpy, e->window, e->value_mask & (CWX|CWY|CWBorderWidth|CWWidth|CWHeight), &changes); } } static void client_msg(XClientMessageEvent *ev) { rp_screen *s = find_screen(ev->window); rp_window *win = NULL; PRINT_DEBUG(("Received client message for window 0x%lx.\n", ev->window)); if (s == NULL) { win = find_window_in_list(ev->window, &rp_mapped_window); if (!win) { PRINT_DEBUG(("can't find screen or window for " "XClientMessageEvent\n")); return; } s = win->vscreen->screen; } if (ev->window == s->root) { if (ev->format != 32) return; if (ev->message_type == _net_current_desktop) { rp_vscreen *v; PRINT_DEBUG(("Received _NET_CURRENT_DESKTOP = %ld\n", ev->data.l[0])); v = screen_find_vscreen_by_number(s, ev->data.l[0]); if (v) set_current_vscreen(v); } else { warnx("unsupported message type %ld\n", ev->message_type); } return; } if (ev->message_type == wm_change_state) { rp_window *win; PRINT_DEBUG(("WM_CHANGE_STATE\n")); win = find_window(ev->window); if (win == NULL) return; if (ev->format == 32 && ev->data.l[0] == IconicState) { /* * FIXME: This means clients can hide themselves * without the user's intervention. This is bad, but * Emacs is the only program I know of that iconifies * itself and this is generally from the user pressing * C-z. */ PRINT_DEBUG(("Iconify Request.\n")); if (win->state == NormalState) { rp_window *w = find_window_other(win->vscreen); if (w) set_active_window(w); else blank_frame(vscreen_get_frame( win->vscreen, win->vscreen->current_frame)); } } else { warnx("non-standard WM_CHANGE_STATE format"); } } else if (win && ev->message_type == _net_wm_state) { PRINT_DEBUG(("_NET_WM_STATE for window 0x%lx, action %ld, " "property 0x%lx\n", win->w, ev->data.l[0], ev->data.l[1])); if (ev->data.l[1] == _net_wm_state_fullscreen) { if (ev->data.l[0] == _NET_WM_STATE_ADD || (ev->data.l[0] == _NET_WM_STATE_TOGGLE && win->full_screen == 0)) window_full_screen(win); else window_full_screen(NULL); } } else if (win && ev->message_type == _net_active_window) { PRINT_DEBUG(("_NET_ACTIVE_WINDOW raise: 0x%lx\n", win->w)); map_request(win->w); } else { PRINT_DEBUG(("unknown client message type 0x%lx\n", ev->message_type)); } } static void handle_key(KeySym ks, unsigned int mod, rp_screen *s) { rp_action *key_action; rp_keymap *map = find_keymap(defaults.top_kmap); if (map == NULL) { warnx("unable to find %s keymap", defaults.top_kmap); return; } PRINT_DEBUG(("handling key...\n")); /* All functions hide the program bar and the frame indicator. */ if (defaults.bar_timeout > 0 && !defaults.bar_sticky) hide_bar(s, 1); hide_frame_indicator(); /* Disable any alarm that was going to go off. */ alarm(0); alarm_signalled = 0; /* Call the top level key pressed hook. */ hook_run(&rp_key_hook); PRINT_DEBUG(("handle_key\n")); /* * Read a key and execute the command associated with it on the default * keymap. Ignore the key if it doesn't have a binding. */ if ((key_action = find_keybinding(ks, x11_mask_to_rp_mask(mod), map))) { cmdret *result; PRINT_DEBUG(("%s\n", key_action->data)); if (defaults.bar_sticky) hide_bar(s, 0); result = command(1, key_action->data); if (result) { if (result->output) message(result->output); cmdret_free(result); } } else { if (defaults.bar_sticky) hide_bar(s, 0); PRINT_DEBUG(("Impossible: No matching key")); } } static void key_press(XEvent *ev) { rp_screen *s; unsigned int modifier; KeySym ks; s = rp_current_screen; if (!s) return; modifier = ev->xkey.state; cook_keycode(&ev->xkey, &ks, &modifier, NULL, 0, 1); handle_key(ks, modifier, s); } static void button_press(XEvent *ev) { rp_screen *s; XButtonEvent *xbe = (XButtonEvent *)ev; s = rp_current_screen; if (!s) return; if (xbe->window == s->bar_window) bar_handle_click(s, xbe); } static void property_notify(XEvent *ev) { rp_window *win; PRINT_DEBUG(("atom: %ld (%s)\n", ev->xproperty.atom, XGetAtomName(dpy, ev->xproperty.atom))); win = find_window(ev->xproperty.window); if (!win) return; if (ev->xproperty.atom == _net_wm_pid) { struct rp_child_info *child_info; PRINT_DEBUG(("updating _NET_WM_PID\n")); child_info = get_child_info(win->w, 1); if (child_info && !child_info->window_mapped) { if (child_info->frame) { PRINT_DEBUG(("frame=%p\n", child_info->frame)); win->intended_frame_number = child_info->frame->number; /* * Only map the first window in the launch * frame. */ child_info->window_mapped = 1; } } } else if (ev->xproperty.atom == XA_WM_NAME) { PRINT_DEBUG(("updating window name\n")); if (update_window_name(win)) { update_window_names(win->vscreen->screen, defaults.window_fmt); hook_run(&rp_title_changed_hook); } } else if (ev->xproperty.atom == XA_WM_NORMAL_HINTS) { PRINT_DEBUG(("updating window normal hints\n")); update_normal_hints(win); if (win->state == NormalState) maximize(win); } else if (ev->xproperty.atom == XA_WM_TRANSIENT_FOR) { PRINT_DEBUG(("Transient for\n")); win->transient = XGetTransientForHint(dpy, win->w, &win->transient_for); } else if (ev->xproperty.atom == _net_wm_state) { check_state(win); } else { PRINT_DEBUG(("Unhandled property notify event: %ld\n", ev->xproperty.atom)); } } static void colormap_notify(XEvent *ev) { rp_window *win; win = find_window(ev->xcolormap.window); if (win != NULL) { XWindowAttributes attr; /* * SDL sets the colormap just before destroying the window, so * ignore BadWindow errors. */ ignore_badwindow++; XGetWindowAttributes(dpy, win->w, &attr); win->colormap = attr.colormap; if (win == current_window() && !rp_current_screen->bar_is_raised) { XInstallColormap(dpy, win->colormap); } ignore_badwindow--; } } static void focus_change(XFocusChangeEvent *ev) { rp_window *win; /* We're only interested in the NotifyGrab mode */ if (ev->mode != NotifyGrab) return; win = find_window(ev->window); if (win != NULL) { PRINT_DEBUG(("Re-grabbing prefix key\n")); grab_top_level_keys(win->w); } } static void mapping_notify(XMappingEvent *ev) { ungrab_keys_all_wins(); switch (ev->request) { case MappingModifier: update_modifier_map(); /* This is meant to fall through. */ case MappingKeyboard: XRefreshKeyboardMapping(ev); break; } grab_keys_all_wins(); } static void configure_notify(XConfigureEvent *ev) { rp_screen *s; s = find_screen(ev->window); if (s != NULL) /* * This is a root window of a screen, look if its width or * height changed: */ screen_update(s, ev->x, ev->y, ev->width, ev->height); } /* * This is called when an application has requested the selection. Copied from * rxvt. */ static void selection_request(XSelectionRequestEvent *rq) { XEvent ev; CARD32 target_list[4]; Atom target; static Atom xa_targets = None; static Atom xa_text = None; /* XXX */ XTextProperty ct; XICCEncodingStyle style; char *cl[4]; if (xa_text == None) xa_text = XInternAtom(dpy, "TEXT", False); if (xa_targets == None) xa_targets = XInternAtom(dpy, "TARGETS", False); ev.xselection.type = SelectionNotify; ev.xselection.property = None; ev.xselection.display = rq->display; ev.xselection.requestor = rq->requestor; ev.xselection.selection = rq->selection; ev.xselection.target = rq->target; ev.xselection.time = rq->time; if (rq->target == xa_targets) { target_list[0] = (CARD32) xa_targets; target_list[1] = (CARD32) xa_string; target_list[2] = (CARD32) xa_text; target_list[3] = (CARD32) xa_compound_text; XChangeProperty(dpy, rq->requestor, rq->property, rq->target, (8 * sizeof(target_list[0])), PropModeReplace, (unsigned char *) target_list, (sizeof(target_list) / sizeof(target_list[0]))); ev.xselection.property = rq->property; } else if (rq->target == xa_string || rq->target == xa_compound_text || rq->target == xa_text) { if (rq->target == xa_string) { style = XStringStyle; target = xa_string; } else { target = xa_compound_text; style = (rq->target == xa_compound_text) ? XCompoundTextStyle : XStdICCTextStyle; } cl[0] = selection.text; XmbTextListToTextProperty(dpy, cl, 1, style, &ct); XChangeProperty(dpy, rq->requestor, rq->property, target, 8, PropModeReplace, ct.value, ct.nitems); ev.xselection.property = rq->property; } XSendEvent(dpy, rq->requestor, False, 0, &ev); } static void selection_clear(void) { free(selection.text); selection.text = NULL; selection.len = 0; } /* Given an event, call the correct function to handle it. */ static void delegate_event(XEvent *ev) { if (rp_have_xrandr) xrandr_notify(ev); switch (ev->type) { case ConfigureRequest: PRINT_DEBUG(("--- Handling ConfigureRequest ---\n")); configure_request(&ev->xconfigurerequest); break; case CreateNotify: PRINT_DEBUG(("--- Handling CreateNotify ---\n")); new_window(&ev->xcreatewindow); break; case DestroyNotify: PRINT_DEBUG(("--- Handling DestroyNotify ---\n")); destroy_window(&ev->xdestroywindow); break; case ClientMessage: PRINT_DEBUG(("--- Handling ClientMessage ---\n")); client_msg(&ev->xclient); break; case ColormapNotify: PRINT_DEBUG(("--- Handling ColormapNotify ---\n")); colormap_notify(ev); break; case PropertyNotify: PRINT_DEBUG(("--- Handling PropertyNotify ---\n")); property_notify(ev); break; case MapRequest: PRINT_DEBUG(("--- Handling MapRequest ---\n")); map_request(ev->xmap.window); break; case KeyPress: PRINT_DEBUG(("--- Handling KeyPress ---\n")); key_press(ev); break; case ButtonPress: PRINT_DEBUG(("--- Handling ButtonPress ---\n")); button_press(ev); break; case UnmapNotify: PRINT_DEBUG(("--- Handling UnmapNotify ---\n")); unmap_notify(ev); break; case FocusOut: PRINT_DEBUG(("--- Handling FocusOut ---\n")); focus_change(&ev->xfocus); break; case FocusIn: PRINT_DEBUG(("--- Handling FocusIn ---\n")); focus_change(&ev->xfocus); break; case MappingNotify: PRINT_DEBUG(("--- Handling MappingNotify ---\n")); mapping_notify(&ev->xmapping); break; case SelectionRequest: selection_request(&ev->xselectionrequest); break; case SelectionClear: selection_clear(); break; case ConfigureNotify: if (!rp_have_xrandr) { PRINT_DEBUG(("--- Handling ConfigureNotify ---\n")); configure_notify(&ev->xconfigure); } break; case MapNotify: case Expose: case MotionNotify: case KeyRelease: case ReparentNotify: case EnterNotify: case SelectionNotify: case CirculateRequest: /* Ignore these events. */ break; default: PRINT_DEBUG(("--- Unknown event %d ---\n", -ev->type)); } } static void handle_signals(void) { /* An alarm means we need to hide the popup windows. */ if (alarm_signalled > 0) { rp_screen *cur; PRINT_DEBUG(("SIGALRM received\n")); /* Only hide the bar if it times out. */ if (defaults.bar_timeout > 0) { list_for_each_entry(cur, &rp_screens, node) { hide_bar(cur, 0); } } hide_frame_indicator(); alarm_signalled = 0; } if (chld_signalled > 0) { rp_child_info *cur; struct list_head *iter, *tmp; /* Report and remove terminated processes. */ list_for_each_safe_entry(cur, iter, tmp, &rp_children, node) { if (cur->terminated) { /* Report any child that didn't return 0. */ if (cur->status != 0) marked_message_printf(0, 0, "/bin/sh -c \"%s\" finished (%d)", cur->cmd, cur->status); list_del(&cur->node); free(cur->cmd); free(cur); } } chld_signalled = 0; } if (hup_signalled > 0) { PRINT_DEBUG(("restarting\n")); hook_run(&rp_restart_hook); clean_up(); execvp(myargv[0], myargv); } if (kill_signalled > 0) { PRINT_DEBUG(("exiting\n")); hook_run(&rp_quit_hook); clean_up(); exit(0); } /* Report any X11 errors that have occurred. */ if (rp_error_msg) { marked_message_printf(0, 6, "X error: %s", rp_error_msg); free(rp_error_msg); rp_error_msg = NULL; } } /* The main loop. */ void listen_for_events(void) { struct pollfd pfd[3]; int pollfifo = 1; memset(&pfd, 0, sizeof(pfd)); pfd[0].fd = ConnectionNumber(dpy); pfd[0].events = POLLIN; pfd[1].fd = rp_glob_screen.control_socket_fd; pfd[1].events = POLLIN; pfd[2].fd = rp_glob_screen.bar_fifo_fd; pfd[2].events = POLLIN; /* Loop forever. */ for (;;) { handle_signals(); if (!XPending(dpy)) { if (pollfifo && rp_glob_screen.bar_fifo_fd == -1) pollfifo = 0; else if (pollfifo) pfd[2].fd = rp_glob_screen.bar_fifo_fd; poll(pfd, pollfifo ? 3 : 2, -1); if (pollfifo && (pfd[2].revents & (POLLERR|POLLNVAL))) { warnx("error polling on FIFO"); pollfifo = 0; continue; } if (pollfifo && (pfd[2].revents & (POLLHUP|POLLIN))) bar_read_fifo(); if (pfd[1].revents & (POLLHUP|POLLIN)) receive_command(); if (!XPending(dpy)) continue; } XNextEvent(dpy, &rp_current_event); delegate_event(&rp_current_event); XSync(dpy, False); } }