X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fmainx.c;h=e779361bf94b5b14be38b0079b9efa445476ea5f;hb=61f9a793476c426da908ab3a216902caa6575df8;hp=baaaa2906121e00647ab42fc260acb6186a9bb48;hpb=07b92c27926cd020071e4934bf4a25dee74362fe;p=i3%2Fi3 diff --git a/src/mainx.c b/src/mainx.c index baaaa290..e779361b 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -3,13 +3,12 @@ * * i3 - an improved dynamic tiling window manager * - * © 2009 Michael Stapelberg and contributors + * © 2009-2010 Michael Stapelberg and contributors * * See file LICENSE for license information. * */ #include -#include #include #include #include @@ -17,266 +16,261 @@ #include #include #include - -#include +#include +#include +#include #include #include -#include +#include +#include #include #include #include #include #include -#include -#include "data.h" -#include "queue.h" -#include "table.h" -#include "font.h" -#include "layout.h" +#include + +#include "config.h" +#include "data.h" #include "debug.h" #include "handlers.h" +#include "click.h" +#include "i3.h" +#include "layout.h" +#include "queue.h" +#include "table.h" #include "util.h" #include "xcb.h" +#include "randr.h" #include "xinerama.h" +#include "manage.h" +#include "ipc.h" +#include "log.h" +#include "sighandler.h" + +static int xkb_event_base; -#define TERMINAL "/usr/pkg/bin/urxvt" +int xkb_current_group; +xcb_connection_t *global_conn; + +/* This is the path to i3, copied from argv[0] when starting up */ +char **start_argv; + +/* This is our connection to X11 for use with XKB */ Display *xkbdpy; -TAILQ_HEAD(bindings_head, Binding) bindings = TAILQ_HEAD_INITIALIZER(bindings); -xcb_event_handlers_t evenths; +xcb_key_symbols_t *keysyms; + +/* The list of key bindings */ +struct bindings_head *bindings; -xcb_window_t root_win; -xcb_atom_t atoms[9]; +/* The list of exec-lines */ +struct autostarts_head autostarts = TAILQ_HEAD_INITIALIZER(autostarts); +/* The list of assignments */ +struct assignments_head assignments = TAILQ_HEAD_INITIALIZER(assignments); + +/* This is a list of Stack_Windows, global, for easier/faster access on expose events */ +struct stack_wins_head stack_wins = SLIST_HEAD_INITIALIZER(stack_wins); + +/* The event handlers need to be global because they are accessed by our custom event handler + in handle_button_press(), needed for graphical resizing */ +xcb_event_handlers_t evenths; +xcb_atom_t atoms[NUM_ATOMS]; -char *pattern = "-misc-fixed-medium-r-normal--13-120-75-75-C-70-iso8859-1"; +xcb_window_t root; int num_screens = 0; +/* The depth of the root screen (used e.g. for creating new pixmaps later) */ +uint8_t root_depth; + +/* We hope that XKB is supported and set this to false */ +bool xkb_supported = true; + /* - * - * TODO: what exactly does this, what happens if we leave stuff out? + * This callback is only a dummy, see xcb_prepare_cb and xcb_check_cb. + * See also man libev(3): "ev_prepare" and "ev_check" - customise your event loop * */ -void manage_window(xcb_property_handlers_t *prophs, xcb_connection_t *c, xcb_window_t window, window_attributes_t wa) -{ - printf("managing window.\n"); - xcb_drawable_t d = { window }; - xcb_get_geometry_cookie_t geomc; - xcb_get_geometry_reply_t *geom; - xcb_get_window_attributes_reply_t *attr = 0; - if(wa.tag == TAG_COOKIE) - { - attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0); - if(!attr) - return; - if(attr->map_state != XCB_MAP_STATE_VIEWABLE) - { - printf("Window 0x%08x is not mapped. Ignoring.\n", window); - free(attr); - return; - } - wa.tag = TAG_VALUE; - wa.u.override_redirect = attr->override_redirect; - } - if(!wa.u.override_redirect && table_get(byChild, window)) - { - printf("Window 0x%08x already managed. Ignoring.\n", window); - free(attr); - return; - } - if(wa.u.override_redirect) - { - printf("Window 0x%08x has override-redirect set. Ignoring.\n", window); - free(attr); - return; - } - geomc = xcb_get_geometry(c, d); - if(!attr) - { - wa.tag = TAG_COOKIE; - wa.u.cookie = xcb_get_window_attributes(c, window); - attr = xcb_get_window_attributes_reply(c, wa.u.cookie, 0); - } - geom = xcb_get_geometry_reply(c, geomc, 0); - if(attr && geom) - { - reparent_window(c, window, attr->visual, geom->root, geom->depth, geom->x, geom->y, geom->width, geom->height); - xcb_property_changed(prophs, XCB_PROPERTY_NEW_VALUE, window, WM_NAME); - } - free(attr); - free(geom); +static void xcb_got_event(EV_P_ struct ev_io *w, int revents) { + /* empty, because xcb_prepare_cb and xcb_check_cb are used */ } /* - * reparent_window() gets called when a new window was opened and becomes a child of the root - * window, or it gets called by us when we manage the already existing windows at startup. + * Flush before blocking (and waiting for new events) * - * Essentially, this is the point, where we take over control. + */ +static void xcb_prepare_cb(EV_P_ ev_prepare *w, int revents) { + xcb_flush(evenths.c); +} + +/* + * Instead of polling the X connection socket we leave this to + * xcb_poll_for_event() which knows better than we can ever know. * */ -void reparent_window(xcb_connection_t *conn, xcb_window_t child, - xcb_visualid_t visual, xcb_window_t root, uint8_t depth, - int16_t x, int16_t y, uint16_t width, uint16_t height) { - - xcb_get_property_cookie_t wm_type_cookie, strut_cookie; - - /* Place requests for propertys ASAP */ - wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); - strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); - - Client *new = table_get(byChild, child); - if (new == NULL) { - /* TODO: When does this happen for existing clients? Is that a bug? */ - printf("oh, it's new\n"); - new = calloc(sizeof(Client), 1); - /* We initialize x and y with the invalid coordinates -1 so that they will - get updated at the next render_layout() at any case */ - new->rect.x = -1; - new->rect.y = -1; - } - uint32_t mask = 0; - uint32_t values[3]; - - /* Update the data structures */ - CUR_CELL->currently_focused = new; - new->container = CUR_CELL; - - new->frame = xcb_generate_id(conn); - new->child = child; - new->rect.width = width; - new->rect.height = height; - - /* Don’t generate events for our new window, it should *not* be managed */ - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[0] = 1; - - /* We want to know when… */ - mask |= XCB_CW_EVENT_MASK; - values[1] = XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */ - XCB_EVENT_MASK_BUTTON_RELEASE | - XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */ - XCB_EVENT_MASK_ENTER_WINDOW; /* …user moves cursor inside our window */ - - printf("Reparenting 0x%08x under 0x%08x.\n", child, new->frame); - - i3Font *font = load_font(conn, pattern); - width = min(width, c_ws->rect.x + c_ws->rect.width); - height = min(height, c_ws->rect.y + c_ws->rect.height); - - Rect framerect = {x, y, - width + 2 + 2, /* 2 px border at each side */ - height + 2 + 2 + font->height}; /* 2 px border plus font’s height */ - - /* Yo dawg, I heard you like windows, so I create a window around your window… */ - new->frame = create_window(conn, framerect, XCB_WINDOW_CLASS_INPUT_OUTPUT, mask, values); - - /* Put the client inside the save set. Upon termination (whether killed or normal exit - does not matter) of the window manager, these clients will be correctly reparented - to their most closest living ancestor (= cleanup) */ - xcb_change_save_set(conn, XCB_SET_MODE_INSERT, child); - - /* Generate a graphics context for the titlebar */ - new->titlegc = xcb_generate_id(conn); - xcb_create_gc(conn, new->titlegc, new->frame, 0, 0); - - /* Put our data structure (Client) into the table */ - table_put(byParent, new->frame, new); - table_put(byChild, child, new); - - /* Moves the original window into the new frame we've created for it */ - new->awaiting_useless_unmap = true; - xcb_void_cookie_t cookie = xcb_reparent_window_checked(conn, child, new->frame, 0, font->height); - check_error(conn, cookie, "Could not reparent window"); - - /* We are interested in property changes */ - mask = XCB_CW_EVENT_MASK; - values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE | - XCB_EVENT_MASK_STRUCTURE_NOTIFY | - XCB_EVENT_MASK_ENTER_WINDOW; - cookie = xcb_change_window_attributes_checked(conn, child, mask, values); - check_error(conn, cookie, "Could not change window attributes"); - - /* We need to grab the mouse buttons for click to focus */ - xcb_grab_button(conn, false, child, XCB_EVENT_MASK_BUTTON_PRESS, - XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, root, XCB_NONE, - 1 /* left mouse button */, - XCB_BUTTON_MASK_ANY /* don’t filter for any modifiers */); - - /* Focus the new window */ - xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME); - - /* Get _NET_WM_WINDOW_TYPE (to see if it’s a dock) */ - xcb_atom_t *atom; - xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL); - if (preply != NULL && preply->value_len > 0 && (atom = xcb_get_property_value(preply))) { - for (int i = 0; i < xcb_get_property_value_length(preply); i++) - if (atom[i] == atoms[_NET_WM_WINDOW_TYPE_DOCK]) { - printf("Window is a dock.\n"); - new->dock = true; - new->titlebar_position = TITLEBAR_OFF; - new->force_reconfigure = true; - new->container = NULL; - SLIST_INSERT_HEAD(&(c_ws->dock_clients), new, dock_clients); - } - } +static void xcb_check_cb(EV_P_ ev_check *w, int revents) { + xcb_generic_event_t *event; - /* Get _NET_WM_STRUT_PARTIAL to determine the client’s requested height */ - uint32_t *strut; - preply = xcb_get_property_reply(conn, strut_cookie, NULL); - if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) { - /* We only use a subset of the provided values, namely the reserved space at the top/bottom - of the screen. This is because the only possibility for bars is at to be at the top/bottom - with maximum horizontal size. - TODO: bars at the top */ - new->desired_height = strut[3]; - printf("the client wants to be %d pixels height\n", new->desired_height); + while ((event = xcb_poll_for_event(evenths.c)) != NULL) { + xcb_event_handle(&evenths, event); + free(event); } +} - /* Insert into the currently active container, if it’s not a dock window */ - if (!new->dock) - CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients); +/* + * When using xmodmap to change the keyboard mapping, this event + * is only sent via XKB. Therefore, we need this special handler. + * + */ +static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { + DLOG("Handling XKB event\n"); + XkbEvent ev; + + /* When using xmodmap, every change (!) gets an own event. + * Therefore, we just read all events and only handle the + * mapping_notify once. */ + bool mapping_changed = false; + while (XPending(xkbdpy)) { + XNextEvent(xkbdpy, (XEvent*)&ev); + /* While we should never receive a non-XKB event, + * better do sanity checking */ + if (ev.type != xkb_event_base) + continue; + + if (ev.any.xkb_type == XkbMapNotify) { + mapping_changed = true; + continue; + } - render_layout(conn); -} + if (ev.any.xkb_type != XkbStateNotify) { + ELOG("Unknown XKB event received (type %d)\n", ev.any.xkb_type); + continue; + } -void manage_existing_windows(xcb_connection_t *c, xcb_property_handlers_t *prophs, xcb_window_t root) { - xcb_query_tree_cookie_t wintree; - xcb_query_tree_reply_t *rep; - int i, len; - xcb_window_t *children; - xcb_get_window_attributes_cookie_t *cookies; + /* See The XKB Extension: Library Specification, section 14.1 */ + /* We check if the current group (each group contains + * two levels) has been changed. Mode_switch activates + * group XkbGroup2Index */ + if (xkb_current_group == ev.state.group) + continue; - wintree = xcb_query_tree(c, root); - rep = xcb_query_tree_reply(c, wintree, 0); - if(!rep) - return; - len = xcb_query_tree_children_length(rep); - cookies = malloc(len * sizeof(*cookies)); - if(!cookies) - { - free(rep); - return; - } - children = xcb_query_tree_children(rep); - for(i = 0; i < len; ++i) - cookies[i] = xcb_get_window_attributes(c, children[i]); - for(i = 0; i < len; ++i) - { - window_attributes_t wa = { TAG_COOKIE, { cookies[i] } }; - manage_window(prophs, c, children[i], wa); + xkb_current_group = ev.state.group; + + if (ev.state.group == XkbGroup2Index) { + DLOG("Mode_switch enabled\n"); + grab_all_keys(global_conn, true); + } + + if (ev.state.group == XkbGroup1Index) { + DLOG("Mode_switch disabled\n"); + ungrab_all_keys(global_conn); + grab_all_keys(global_conn, false); + } } - free(rep); + + if (!mapping_changed) + return; + + DLOG("Keyboard mapping changed, updating keybindings\n"); + xcb_key_symbols_free(keysyms); + keysyms = xcb_key_symbols_alloc(global_conn); + + xcb_get_numlock_mask(global_conn); + + ungrab_all_keys(global_conn); + DLOG("Re-grabbing...\n"); + translate_keysyms(); + grab_all_keys(global_conn, (xkb_current_group == XkbGroup2Index)); + DLOG("Done\n"); } + int main(int argc, char *argv[], char *env[]) { - int i, screens; - xcb_connection_t *c; + int i, screens, opt; + char *override_configpath = NULL; + bool autostart = true; + bool only_check_config = false; + bool force_xinerama = false; + xcb_connection_t *conn; xcb_property_handlers_t prophs; - xcb_window_t root; + xcb_intern_atom_cookie_t atom_cookies[NUM_ATOMS]; + static struct option long_options[] = { + {"no-autostart", no_argument, 0, 'a'}, + {"config", required_argument, 0, 'c'}, + {"version", no_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {"force-xinerama", no_argument, 0, 0}, + {0, 0, 0, 0} + }; + int option_index = 0; + + setlocale(LC_ALL, ""); + + /* Disable output buffering to make redirects in .xsession actually useful for debugging */ + if (!isatty(fileno(stdout))) + setbuf(stdout, NULL); + + start_argv = argv; + + while ((opt = getopt_long(argc, argv, "c:Cvahld:V", long_options, &option_index)) != -1) { + switch (opt) { + case 'a': + LOG("Autostart disabled using -a\n"); + autostart = false; + break; + case 'c': + override_configpath = sstrdup(optarg); + break; + case 'C': + LOG("Checking configuration file only (-C)\n"); + only_check_config = true; + break; + case 'v': + printf("i3 version " I3_VERSION " © 2009 Michael Stapelberg and contributors\n"); + exit(EXIT_SUCCESS); + case 'V': + set_verbosity(true); + break; + case 'd': + LOG("Enabling debug loglevel %s\n", optarg); + add_loglevel(optarg); + break; + case 'l': + /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */ + break; + case 0: + if (strcmp(long_options[option_index].name, "force-xinerama") == 0) { + force_xinerama = true; + ELOG("Using Xinerama instead of RandR. This option should be " + "avoided at all cost because it does not refresh the list " + "of screens, so you cannot configure displays at runtime. " + "Please check if your driver really does not support RandR " + "and disable this option as soon as you can.\n"); + break; + } + /* fall-through */ + default: + fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]); + fprintf(stderr, "\n"); + fprintf(stderr, "-a: disable autostart\n"); + fprintf(stderr, "-v: display version and exit\n"); + fprintf(stderr, "-V: enable verbose mode\n"); + fprintf(stderr, "-d : enable debug loglevel \n"); + fprintf(stderr, "-c : use the provided configfile instead\n"); + fprintf(stderr, "-C: check configuration file and exit\n"); + fprintf(stderr, "--force-xinerama: Use Xinerama instead of RandR. This " + "option should only be used if you are stuck with the " + "nvidia closed source driver which does not support RandR.\n"); + exit(EXIT_FAILURE); + } + } + + LOG("i3 version " I3_VERSION " starting\n"); /* Initialize the table data structures for each workspace */ init_table(); @@ -284,10 +278,49 @@ int main(int argc, char *argv[], char *env[]) { memset(&evenths, 0, sizeof(xcb_event_handlers_t)); memset(&prophs, 0, sizeof(xcb_property_handlers_t)); - byChild = alloc_table(); - byParent = alloc_table(); + conn = global_conn = xcb_connect(NULL, &screens); + + if (xcb_connection_has_error(conn)) + die("Cannot open display\n"); - c = xcb_connect(NULL, &screens); + load_configuration(conn, override_configpath, false); + if (only_check_config) { + LOG("Done checking configuration file. Exiting.\n"); + exit(0); + } + + /* Create the initial container on the first workspace. This used to + * be part of init_table, but since it possibly requires an X + * connection and a loaded configuration (default mode for new + * containers may be stacking, which requires a new window to be + * created), it had to be delayed. */ + expand_table_cols(TAILQ_FIRST(workspaces)); + expand_table_rows(TAILQ_FIRST(workspaces)); + + /* Place requests for the atoms we need as soon as possible */ + #define REQUEST_ATOM(name) atom_cookies[name] = xcb_intern_atom(conn, 0, strlen(#name), #name); + + REQUEST_ATOM(_NET_SUPPORTED); + REQUEST_ATOM(_NET_WM_STATE_FULLSCREEN); + REQUEST_ATOM(_NET_SUPPORTING_WM_CHECK); + REQUEST_ATOM(_NET_WM_NAME); + REQUEST_ATOM(_NET_WM_STATE); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE); + REQUEST_ATOM(_NET_WM_DESKTOP); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DOCK); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_UTILITY); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR); + REQUEST_ATOM(_NET_WM_WINDOW_TYPE_SPLASH); + REQUEST_ATOM(_NET_WM_STRUT_PARTIAL); + REQUEST_ATOM(WM_PROTOCOLS); + REQUEST_ATOM(WM_DELETE_WINDOW); + REQUEST_ATOM(UTF8_STRING); + REQUEST_ATOM(WM_STATE); + REQUEST_ATOM(WM_CLIENT_LEADER); + REQUEST_ATOM(_NET_CURRENT_DESKTOP); + REQUEST_ATOM(_NET_ACTIVE_WINDOW); + REQUEST_ATOM(_NET_WORKAREA); /* TODO: this has to be more beautiful somewhen */ int major, minor, error; @@ -295,68 +328,146 @@ int main(int argc, char *argv[], char *env[]) { major = XkbMajorVersion; minor = XkbMinorVersion; - int evBase, errBase; + int errBase; - if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &evBase, &errBase, &major, &minor, &error)) == NULL) { - fprintf(stderr, "XkbOpenDisplay() failed\n"); - return 1; + if ((xkbdpy = XkbOpenDisplay(getenv("DISPLAY"), &xkb_event_base, &errBase, &major, &minor, &error)) == NULL) { + ELOG("ERROR: XkbOpenDisplay() failed, disabling XKB support\n"); + xkb_supported = false; } - int i1; - if (!XkbQueryExtension(xkbdpy,&i1,&evBase,&errBase,&major,&minor)) { - fprintf(stderr, "XKB not supported by X-server\n"); - return 1; + if (xkb_supported) { + if (fcntl(ConnectionNumber(xkbdpy), F_SETFD, FD_CLOEXEC) == -1) { + fprintf(stderr, "Could not set FD_CLOEXEC on xkbdpy\n"); + return 1; + } + + int i1; + if (!XkbQueryExtension(xkbdpy,&i1,&xkb_event_base,&errBase,&major,&minor)) { + fprintf(stderr, "XKB not supported by X-server\n"); + return 1; + } + /* end of ugliness */ + + if (!XkbSelectEvents(xkbdpy, XkbUseCoreKbd, + XkbMapNotifyMask | XkbStateNotifyMask, + XkbMapNotifyMask | XkbStateNotifyMask)) { + fprintf(stderr, "Could not set XKB event mask\n"); + return 1; + } } - /* end of ugliness */ - xcb_event_handlers_init(c, &evenths); - for(i = 2; i < 128; ++i) + /* Initialize event loop using libev */ + struct ev_loop *loop = ev_loop_new(0); + if (loop == NULL) + die("Could not initialize libev. Bad LIBEV_FLAGS?\n"); + + struct ev_io *xcb_watcher = scalloc(sizeof(struct ev_io)); + struct ev_io *xkb = scalloc(sizeof(struct ev_io)); + struct ev_check *xcb_check = scalloc(sizeof(struct ev_check)); + struct ev_prepare *xcb_prepare = scalloc(sizeof(struct ev_prepare)); + + ev_io_init(xcb_watcher, xcb_got_event, xcb_get_file_descriptor(conn), EV_READ); + ev_io_start(loop, xcb_watcher); + + if (xkb_supported) { + ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ); + ev_io_start(loop, xkb); + + /* Flush the buffer so that libev can properly get new events */ + XFlush(xkbdpy); + } + + ev_check_init(xcb_check, xcb_check_cb); + ev_check_start(loop, xcb_check); + + ev_prepare_init(xcb_prepare, xcb_prepare_cb); + ev_prepare_start(loop, xcb_prepare); + + /* Grab the server to delay any events until we enter the eventloop */ + xcb_grab_server(conn); + + xcb_event_handlers_init(conn, &evenths); + + /* DEBUG: Trap all events and print them */ + for (i = 2; i < 128; ++i) xcb_event_set_handler(&evenths, i, handle_event, 0); - for(i = 0; i < 256; ++i) + for (i = 0; i < 256; ++i) xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0); - /* Expose = an Application should redraw itself. That is, we have to redraw our - * contents (= top/bottom bar, titlebars for each window) */ - xcb_event_set_expose_handler(&evenths, handle_expose_event, 0); + /* Expose = an Application should redraw itself, in this case it’s our titlebars. */ + xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); - /* Key presses/releases are pretty obvious, I think */ - xcb_event_set_key_press_handler(&evenths, handle_key_press, 0); - xcb_event_set_key_release_handler(&evenths, handle_key_release, 0); + /* Key presses are pretty obvious, I think */ + xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL); /* Enter window = user moved his mouse over the window */ - xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0); + xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL); /* Button press = user pushed a mouse button over one of our windows */ - xcb_event_set_button_press_handler(&evenths, handle_button_press, 0); + xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL); - xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0); + /* Map notify = there is a new window */ + xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs); - xcb_property_handlers_init(&prophs, &evenths); - xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs); + /* Unmap notify = window disappeared. When sent from a client, we don’t manage + it any longer. Usually, the client destroys the window shortly afterwards. */ + xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL); - xcb_event_set_client_message_handler(&evenths, handle_client_message, 0); + /* Destroy notify is handled the same as unmap notify */ + xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL); - xcb_watch_wm_name(&prophs, 128, handle_windowname_change, 0); + /* Configure notify = window’s configuration (geometry, stacking, …). We only need + it to set up ignore the following enter_notify events */ + xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL); - root = xcb_aux_get_screen(c, screens)->root; - root_win = root; + /* Configure request = window tried to change size on its own */ + xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL); + + /* Motion notify = user moved his cursor (over the root window and may + * cross virtual screen boundaries doing that) */ + xcb_event_set_motion_notify_handler(&evenths, handle_motion_notify, NULL); + + /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */ + xcb_event_set_mapping_notify_handler(&evenths, handle_mapping_notify, NULL); + + /* Client message are sent to the root window. The only interesting client message + for us is _NET_WM_STATE, we honour _NET_WM_STATE_FULLSCREEN */ + xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL); + + /* Initialize the property handlers */ + xcb_property_handlers_init(&prophs, &evenths); + + /* Watch size hints (to obey correct aspect ratio) */ + xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL); + + /* Get the root window and set the event mask */ + xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens); + root = root_screen->root; + root_depth = root_screen->root_depth; uint32_t mask = XCB_CW_EVENT_MASK; - uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | XCB_EVENT_MASK_PROPERTY_CHANGE | XCB_EVENT_MASK_ENTER_WINDOW}; - xcb_change_window_attributes(c, root, mask, values); + uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | + XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video + projector), the root window gets a + ConfigureNotify */ + XCB_EVENT_MASK_POINTER_MOTION | + XCB_EVENT_MASK_PROPERTY_CHANGE | + XCB_EVENT_MASK_ENTER_WINDOW }; + xcb_void_cookie_t cookie; + cookie = xcb_change_window_attributes_checked(conn, root, mask, values); + check_error(conn, cookie, "Another window manager seems to be running"); /* Setup NetWM atoms */ - /* TODO: needs cleanup, needs more xcb (asynchronous), needs more error checking */ -#define GET_ATOM(name) { \ - xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(c, xcb_intern_atom(c, 0, strlen(#name), #name), NULL); \ - if (!reply) { \ - printf("Could not get atom " #name "\n"); \ - exit(-1); \ - } \ - atoms[name] = reply->atom; \ - free(reply); \ -} + #define GET_ATOM(name) { \ + xcb_intern_atom_reply_t *reply = xcb_intern_atom_reply(conn, atom_cookies[name], NULL); \ + if (!reply) { \ + ELOG("Could not get atom " #name "\n"); \ + exit(-1); \ + } \ + atoms[name] = reply->atom; \ + free(reply); \ + } GET_ATOM(_NET_SUPPORTED); GET_ATOM(_NET_WM_STATE_FULLSCREEN); @@ -366,96 +477,123 @@ int main(int argc, char *argv[], char *env[]) { GET_ATOM(_NET_WM_WINDOW_TYPE); GET_ATOM(_NET_WM_DESKTOP); GET_ATOM(_NET_WM_WINDOW_TYPE_DOCK); + GET_ATOM(_NET_WM_WINDOW_TYPE_DIALOG); + GET_ATOM(_NET_WM_WINDOW_TYPE_UTILITY); + GET_ATOM(_NET_WM_WINDOW_TYPE_TOOLBAR); + GET_ATOM(_NET_WM_WINDOW_TYPE_SPLASH); GET_ATOM(_NET_WM_STRUT_PARTIAL); + GET_ATOM(WM_PROTOCOLS); + GET_ATOM(WM_DELETE_WINDOW); GET_ATOM(UTF8_STRING); + GET_ATOM(WM_STATE); + GET_ATOM(WM_CLIENT_LEADER); + GET_ATOM(_NET_CURRENT_DESKTOP); + GET_ATOM(_NET_ACTIVE_WINDOW); + GET_ATOM(_NET_WORKAREA); - xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, window_type_handler, NULL); + xcb_property_set_handler(&prophs, atoms[_NET_WM_WINDOW_TYPE], UINT_MAX, handle_window_type, NULL); /* TODO: In order to comply with EWMH, we have to watch _NET_WM_STRUT_PARTIAL */ - check_error(c, xcb_change_property_checked(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED"); + /* Watch _NET_WM_NAME (= title of the window in UTF-8) property */ + xcb_property_set_handler(&prophs, atoms[_NET_WM_NAME], 128, handle_windowname_change, NULL); - xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root); + /* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */ + xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, NULL); - xcb_change_property(c, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME] , atoms[UTF8_STRING], 8, strlen("i3"), "i3"); + /* Watch WM_NAME (= title of the window in compound text) property for legacy applications */ + xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL); - #define BIND(key, modifier, cmd) { \ - Binding *new = malloc(sizeof(Binding)); \ - new->keycode = key; \ - new->mods = modifier; \ - new->command = cmd; \ - TAILQ_INSERT_TAIL(&bindings, new, bindings); \ - } + /* Watch WM_CLASS (= class of the window) */ + xcb_property_set_handler(&prophs, WM_CLASS, 128, handle_windowclass_change, NULL); - /* 38 = 'a' */ - BIND(38, BIND_MODE_SWITCH, "foo"); - - BIND(30, 0, "exec /usr/pkg/bin/urxvt"); - - BIND(41, BIND_MOD_1, "f"); - - BIND(44, BIND_MOD_1, "h"); - BIND(45, BIND_MOD_1, "j"); - BIND(46, BIND_MOD_1, "k"); - BIND(47, BIND_MOD_1, "l"); - - BIND(44, BIND_MOD_1 | BIND_CONTROL, "sh"); - BIND(45, BIND_MOD_1 | BIND_CONTROL, "sj"); - BIND(46, BIND_MOD_1 | BIND_CONTROL, "sk"); - BIND(47, BIND_MOD_1 | BIND_CONTROL, "sl"); - - BIND(44, BIND_MOD_1 | BIND_SHIFT, "mh"); - BIND(45, BIND_MOD_1 | BIND_SHIFT, "mj"); - BIND(46, BIND_MOD_1 | BIND_SHIFT, "mk"); - BIND(47, BIND_MOD_1 | BIND_SHIFT, "ml"); - - BIND(10, BIND_MOD_1 , "1"); - BIND(11, BIND_MOD_1 , "2"); - BIND(12, BIND_MOD_1 , "3"); - BIND(13, BIND_MOD_1 , "4"); - BIND(14, BIND_MOD_1 , "5"); - BIND(15, BIND_MOD_1 , "6"); - BIND(16, BIND_MOD_1 , "7"); - BIND(17, BIND_MOD_1 , "8"); - BIND(18, BIND_MOD_1 , "9"); - BIND(19, BIND_MOD_1 , "0"); - - Binding *bind; - TAILQ_FOREACH(bind, &bindings, bindings) { - printf("Grabbing %d\n", bind->keycode); - if (bind->mods & BIND_MODE_SWITCH) - xcb_grab_key(c, 0, root, 0, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_SYNC); - else xcb_grab_key(c, 0, root, bind->mods, bind->keycode, XCB_GRAB_MODE_SYNC, XCB_GRAB_MODE_ASYNC); - } + /* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */ + xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL); - /* check for Xinerama */ - printf("Checking for Xinerama...\n"); - initialize_xinerama(c); + /* Watch WM_HINTS (contains the urgent property) */ + xcb_property_set_handler(&prophs, WM_HINTS, UINT_MAX, handle_hints, NULL); - start_application(TERMINAL); + /* Set up the atoms we support */ + check_error(conn, xcb_change_property_checked(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], + ATOM, 32, 7, atoms), "Could not set _NET_SUPPORTED"); + /* Set up the window manager’s name */ + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTING_WM_CHECK], WINDOW, 32, 1, &root); + xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_WM_NAME], atoms[UTF8_STRING], 8, strlen("i3"), "i3"); - xcb_flush(c); + keysyms = xcb_key_symbols_alloc(conn); - manage_existing_windows(c, &prophs, root); + xcb_get_numlock_mask(conn); + + translate_keysyms(); + grab_all_keys(conn, false); + + int randr_base; + if (force_xinerama) { + initialize_xinerama(conn); + } else { + DLOG("Checking for XRandR...\n"); + initialize_randr(conn, &randr_base); + + xcb_event_set_handler(&evenths, + randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY, + handle_screen_change, + NULL); + } + + xcb_flush(conn); /* Get pointer position to see on which screen we’re starting */ - xcb_query_pointer_cookie_t pointer_cookie = xcb_query_pointer(c, root); - xcb_query_pointer_reply_t *reply = xcb_query_pointer_reply(c, pointer_cookie, NULL); - if (reply == NULL) { - printf("Could not get pointer position\n"); + xcb_query_pointer_reply_t *reply; + if ((reply = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, root), NULL)) == NULL) { + ELOG("Could not get pointer position\n"); return 1; } - i3Screen *screen = get_screen_containing(reply->root_x, reply->root_y); + Output *screen = get_output_containing(reply->root_x, reply->root_y); if (screen == NULL) { - printf("ERROR: No such screen\n"); - return 0; + ELOG("ERROR: No screen at %d x %d, starting on the first screen\n", + reply->root_x, reply->root_y); + screen = get_first_output(); + } + + DLOG("Starting on %p\n", screen->current_workspace); + c_ws = screen->current_workspace; + + manage_existing_windows(conn, &prophs, root); + + /* Create the UNIX domain socket for IPC */ + if (config.ipc_socket_path != NULL) { + int ipc_socket = ipc_create_socket(config.ipc_socket_path); + if (ipc_socket == -1) { + ELOG("Could not create the IPC socket, IPC disabled\n"); + } else { + struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); + ev_io_init(ipc_io, ipc_new_client, ipc_socket, EV_READ); + ev_io_start(loop, ipc_io); + } } - if (screen->current_workspace != 0) { - printf("Ok, I need to go to the other workspace\n"); - c_ws = &workspaces[screen->current_workspace]; + + /* Handle the events which arrived until now */ + xcb_check_cb(NULL, NULL, 0); + + setup_signal_handler(); + + /* Ignore SIGPIPE to survive errors when an IPC client disconnects + * while we are sending him a message */ + signal(SIGPIPE, SIG_IGN); + + /* Ungrab the server to receive events and enter libev’s eventloop */ + xcb_ungrab_server(conn); + + /* Autostarting exec-lines */ + struct Autostart *exec; + if (autostart) { + TAILQ_FOREACH(exec, &autostarts, autostarts) { + LOG("auto-starting %s\n", exec->command); + start_application(exec->command); + } } - xcb_event_wait_for_event_loop(&evenths); + ev_loop(loop, 0); /* not reached */ return 0;