X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fmain.c;h=8bae9957db894e3fac02363b37e857ef49f9ad4f;hb=99e91c804989990d977ee69c5ff4fcf6964c9f2d;hp=38412d53de07d69f0810191d4d59fa5bb6eadf1a;hpb=df2236c5ee5f4ad9f386c887e5f4d0647138a0ee;p=i3%2Fi3 diff --git a/src/main.c b/src/main.c index 38412d53..8bae9957 100644 --- a/src/main.c +++ b/src/main.c @@ -1,8 +1,10 @@ +#undef I3__FILE__ +#define I3__FILE__ "main.c" /* * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2011 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) * * main.c: Initialization, main loop * @@ -14,6 +16,8 @@ #include #include #include +#include +#include #include "all.h" #include "sd-daemon.h" @@ -23,6 +27,9 @@ * RLIM_INFINITY for i3 debugging versions. */ struct rlimit original_rlimit_core; +/** The number of file descriptors passed via socket activation. */ +int listen_fds; + static int xkb_event_base; int xkb_current_group; @@ -45,7 +52,13 @@ xcb_timestamp_t last_timestamp = XCB_CURRENT_TIME; xcb_screen_t *root_screen; xcb_window_t root; + +/* Color depth, visual id and colormap to use when creating windows and + * pixmaps. Will use 32 bit depth and an appropriate visual, if available, + * otherwise the root window’s default (usually 24 bit TrueColor). */ uint8_t root_depth; +xcb_visualid_t visual_id; +xcb_colormap_t colormap; struct ev_loop *main_loop; @@ -110,7 +123,7 @@ static void xcb_check_cb(EV_P_ ev_check *w, int revents) { DLOG("Expected X11 Error received for sequence %x\n", event->sequence); else { xcb_generic_error_t *error = (xcb_generic_error_t*)event; - ELOG("X11 Error received! sequence 0x%x, error_code = %d\n", + DLOG("X11 Error received (probably harmless)! sequence 0x%x, error_code = %d\n", error->sequence, error->error_code); } free(event); @@ -198,34 +211,67 @@ static void xkb_got_event(EV_P_ struct ev_io *w, int revents) { * Exit handler which destroys the main_loop. Will trigger cleanup handlers. * */ -static void i3_exit() { +static void i3_exit(void) { /* We need ev >= 4 for the following code. Since it is not *that* important (it * only makes sure that there are no i3-nagbar instances left behind) we still * support old systems with libev 3. */ #if EV_VERSION_MAJOR >= 4 ev_loop_destroy(main_loop); #endif + + if (*shmlogname != '\0') { + fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname); + fflush(stderr); + shm_unlink(shmlogname); + } +} + +/* + * (One-shot) Handler for all signals with default action "Term", see signal(7) + * + * Unlinks the SHM log and re-raises the signal. + * + */ +static void handle_signal(int sig, siginfo_t *info, void *data) { + fprintf(stderr, "Received signal %d, terminating\n", sig); + if (*shmlogname != '\0') { + fprintf(stderr, "Closing SHM log \"%s\"\n", shmlogname); + shm_unlink(shmlogname); + } + fflush(stderr); + raise(sig); } int main(int argc, char *argv[]) { + /* Keep a symbol pointing to the I3_VERSION string constant so that we have + * it in gdb backtraces. */ + const char *i3_version __attribute__ ((unused)) = I3_VERSION; char *override_configpath = NULL; bool autostart = true; char *layout_path = NULL; bool delete_layout_path = false; bool force_xinerama = false; + char *fake_outputs = NULL; bool disable_signalhandler = false; static struct option long_options[] = { {"no-autostart", no_argument, 0, 'a'}, {"config", required_argument, 0, 'c'}, {"version", no_argument, 0, 'v'}, + {"moreversion", no_argument, 0, 'm'}, + {"more-version", no_argument, 0, 'm'}, + {"more_version", no_argument, 0, 'm'}, {"help", no_argument, 0, 'h'}, {"layout", required_argument, 0, 'L'}, {"restart", required_argument, 0, 0}, {"force-xinerama", no_argument, 0, 0}, {"force_xinerama", no_argument, 0, 0}, {"disable-signalhandler", no_argument, 0, 0}, + {"shmlog-size", required_argument, 0, 0}, + {"shmlog_size", required_argument, 0, 0}, {"get-socketpath", no_argument, 0, 0}, {"get_socketpath", no_argument, 0, 0}, + {"fake_outputs", required_argument, 0, 0}, + {"fake-outputs", required_argument, 0, 0}, {0, 0, 0, 0} }; int option_index = 0, opt; @@ -242,11 +288,16 @@ int main(int argc, char *argv[]) { srand(time(NULL)); + /* Init logging *before* initializing debug_build to guarantee early + * (file) logging. */ init_logging(); + /* On non-release builds, disable SHM logging by default. */ + shmlog_size = (is_debug_build() ? 25 * 1024 * 1024 : 0); + start_argv = argv; - while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) { + while ((opt = getopt_long(argc, argv, "c:CvmaL:hld:V", long_options, &option_index)) != -1) { switch (opt) { case 'a': LOG("Autostart disabled using -a\n"); @@ -266,14 +317,20 @@ int main(int argc, char *argv[]) { only_check_config = true; break; case 'v': - printf("i3 version " I3_VERSION " © 2009-2011 Michael Stapelberg and contributors\n"); + printf("i3 version " I3_VERSION " © 2009-2012 Michael Stapelberg and contributors\n"); exit(EXIT_SUCCESS); + break; + case 'm': + printf("Binary i3 version: " I3_VERSION " © 2009-2012 Michael Stapelberg and contributors\n"); + display_running_version(); + exit(EXIT_SUCCESS); + break; case 'V': set_verbosity(true); break; case 'd': - LOG("Enabling debug loglevel %s\n", optarg); - add_loglevel(optarg); + LOG("Enabling debug logging\n"); + set_debug_logging(true); break; case 'l': /* DEPRECATED, ignored for the next 3 versions (3.e, 3.f, 3.g) */ @@ -293,27 +350,40 @@ int main(int argc, char *argv[]) { break; } else if (strcmp(long_options[option_index].name, "get-socketpath") == 0 || strcmp(long_options[option_index].name, "get_socketpath") == 0) { - char *socket_path = socket_path_from_x11(); + char *socket_path = root_atom_contents("I3_SOCKET_PATH"); if (socket_path) { printf("%s\n", socket_path); - return 0; + exit(EXIT_SUCCESS); } - return 1; + exit(EXIT_FAILURE); + } else if (strcmp(long_options[option_index].name, "shmlog-size") == 0 || + strcmp(long_options[option_index].name, "shmlog_size") == 0) { + shmlog_size = atoi(optarg); + /* Re-initialize logging immediately to get as many + * logmessages as possible into the SHM log. */ + init_logging(); + LOG("Limiting SHM log size to %d bytes\n", shmlog_size); + break; } else if (strcmp(long_options[option_index].name, "restart") == 0) { FREE(layout_path); layout_path = sstrdup(optarg); delete_layout_path = true; break; + } else if (strcmp(long_options[option_index].name, "fake-outputs") == 0 || + strcmp(long_options[option_index].name, "fake_outputs") == 0) { + LOG("Initializing fake outputs: %s\n", optarg); + fake_outputs = sstrdup(optarg); + break; } /* fall-through */ default: - fprintf(stderr, "Usage: %s [-c configfile] [-d loglevel] [-a] [-v] [-V] [-C]\n", argv[0]); + fprintf(stderr, "Usage: %s [-c configfile] [-d all] [-a] [-v] [-V] [-C]\n", argv[0]); fprintf(stderr, "\n"); fprintf(stderr, "\t-a disable autostart ('exec' lines in config)\n"); fprintf(stderr, "\t-c use the provided configfile instead\n"); fprintf(stderr, "\t-C validate configuration file and exit\n"); - fprintf(stderr, "\t-d enable debug output with the specified loglevel\n"); + fprintf(stderr, "\t-d all enable debug output\n"); fprintf(stderr, "\t-L path to the serialized layout during restarts\n"); fprintf(stderr, "\t-v display version and exit\n"); fprintf(stderr, "\t-V enable verbose mode\n"); @@ -321,11 +391,17 @@ int main(int argc, char *argv[]) { fprintf(stderr, "\t--force-xinerama\n" "\tUse Xinerama instead of RandR.\n" "\tThis option should only be used if you are stuck with the\n" - "\tnvidia closed source driver which does not support RandR.\n"); + "\told nVidia closed source driver (older than 302.17), which does\n" + "\tnot support RandR.\n"); fprintf(stderr, "\n"); fprintf(stderr, "\t--get-socketpath\n" "\tRetrieve the i3 IPC socket path from X11, print it, then exit.\n"); fprintf(stderr, "\n"); + fprintf(stderr, "\t--shmlog-size \n" + "\tLimits the size of the i3 SHM log to bytes. Setting this\n" + "\tto 0 disables SHM logging entirely.\n" + "\tThe default is %d bytes.\n", shmlog_size); + fprintf(stderr, "\n"); fprintf(stderr, "If you pass plain text arguments, i3 will interpret them as a command\n" "to send to a currently running i3 (like i3-msg). This allows you to\n" "use nice and logical commands, such as:\n" @@ -341,7 +417,7 @@ int main(int argc, char *argv[]) { /* If the user passes more arguments, we act like i3-msg would: Just send * the arguments as an IPC message to i3. This allows for nice semantic * commands such as 'i3 border none'. */ - if (optind < argc) { + if (!only_check_config && optind < argc) { /* We enable verbose mode so that the user knows what’s going on. * This should make it easier to find mistakes when the user passes * arguments by mistake. */ @@ -360,8 +436,8 @@ int main(int argc, char *argv[]) { } optind++; } - LOG("Command is: %s (%d bytes)\n", payload, strlen(payload)); - char *socket_path = socket_path_from_x11(); + DLOG("Command is: %s (%zd bytes)\n", payload, strlen(payload)); + char *socket_path = root_atom_contents("I3_SOCKET_PATH"); if (!socket_path) { ELOG("Could not get i3 IPC socket path\n"); return 1; @@ -395,13 +471,11 @@ int main(int argc, char *argv[]) { return 0; } - /* I3_VERSION contains either something like this: - * "4.0.2 (2011-11-11, branch "release")". - * or: "4.0.2-123-gCOFFEEBABE (2011-11-11, branch "next")". - * - * So we check for the offset of the first opening round bracket to - * determine whether this is a git version or a release version. */ - if ((strchr(I3_VERSION, '(') - I3_VERSION) > 10) { + /* Enable logging to handle the case when the user did not specify --shmlog-size */ + init_logging(); + + /* Try to enable core dumps by default when running a debug build */ + if (is_debug_build()) { struct rlimit limit = { RLIM_INFINITY, RLIM_INFINITY }; setrlimit(RLIMIT_CORE, &limit); @@ -421,7 +495,7 @@ int main(int argc, char *argv[]) { } } - LOG("i3 (tree) version " I3_VERSION " starting\n"); + LOG("i3 " I3_VERSION " starting\n"); conn = xcb_connect(NULL, &conn_screen); if (xcb_connection_has_error(conn)) @@ -438,7 +512,17 @@ int main(int argc, char *argv[]) { root_screen = xcb_aux_get_screen(conn, conn_screen); root = root_screen->root; + + /* By default, we use the same depth and visual as the root window, which + * usually is TrueColor (24 bit depth) and the corresponding visual. + * However, we also check if a 32 bit depth and visual are available (for + * transparency) and use it if so. */ root_depth = root_screen->root_depth; + visual_id = root_screen->root_visual; + colormap = root_screen->default_colormap; + + DLOG("root_depth = %d, visual_id = 0x%08x.\n", root_depth, visual_id); + xcb_get_geometry_cookie_t gcookie = xcb_get_geometry(conn, root); xcb_query_pointer_cookie_t pointercookie = xcb_query_pointer(conn, root); @@ -544,16 +628,7 @@ int main(int argc, char *argv[]) { property_handlers_init(); - /* Set up the atoms we support */ - xcb_atom_t supported_atoms[] = { -#define xmacro(atom) A_ ## atom, -#include "atoms.xmacro" -#undef xmacro - }; - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTED, XCB_ATOM_ATOM, 32, 16, supported_atoms); - /* Set up the window manager’s name */ - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_SUPPORTING_WM_CHECK, XCB_ATOM_WINDOW, 32, 1, &root); - xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, A__NET_WM_NAME, A_UTF8_STRING, 8, strlen("i3"), "i3"); + ewmh_setup_hints(); keysyms = xcb_key_symbols_alloc(conn); @@ -575,21 +650,30 @@ int main(int argc, char *argv[]) { free(greply); - /* Force Xinerama (for drivers which don't support RandR yet, esp. the - * nVidia binary graphics driver), when specified either in the config - * file or on command-line */ - if (force_xinerama || config.force_xinerama) { + /* Setup fake outputs for testing */ + if (fake_outputs == NULL && config.fake_outputs != NULL) + fake_outputs = config.fake_outputs; + + if (fake_outputs != NULL) { + fake_outputs_init(fake_outputs); + FREE(fake_outputs); + config.fake_outputs = NULL; + } else if (force_xinerama || config.force_xinerama) { + /* Force Xinerama (for drivers which don't support RandR yet, esp. the + * nVidia binary graphics driver), when specified either in the config + * file or on command-line */ xinerama_init(); } else { DLOG("Checking for XRandR...\n"); randr_init(&randr_base); } + scratchpad_fix_resolution(); + xcb_query_pointer_reply_t *pointerreply; Output *output = NULL; if (!(pointerreply = xcb_query_pointer_reply(conn, pointercookie, NULL))) { ELOG("Could not query pointer position, using first screen\n"); - output = get_first_output(); } else { DLOG("Pointer at %d, %d\n", pointerreply->root_x, pointerreply->root_y); output = get_output_containing(pointerreply->root_x, pointerreply->root_y); @@ -618,14 +702,26 @@ int main(int argc, char *argv[]) { /* Also handle the UNIX domain sockets passed via socket activation. The * parameter 1 means "remove the environment variables", we don’t want to * pass these to child processes. */ - int fds = sd_listen_fds(1); - if (fds < 0) + listen_fds = sd_listen_fds(0); + if (listen_fds < 0) ELOG("socket activation: Error in sd_listen_fds\n"); - else if (fds == 0) + else if (listen_fds == 0) DLOG("socket activation: no sockets passed\n"); else { - for (int fd = SD_LISTEN_FDS_START; fd < (SD_LISTEN_FDS_START + fds); fd++) { + int flags; + for (int fd = SD_LISTEN_FDS_START; + fd < (SD_LISTEN_FDS_START + listen_fds); + fd++) { DLOG("socket activation: also listening on fd %d\n", fd); + + /* sd_listen_fds() enables FD_CLOEXEC by default. + * However, we need to keep the file descriptors open for in-place + * restarting, therefore we explicitly disable FD_CLOEXEC. */ + if ((flags = fcntl(fd, F_GETFD)) < 0 || + fcntl(fd, F_SETFD, flags & ~FD_CLOEXEC) < 0) { + ELOG("Could not disable FD_CLOEXEC on fd %d\n", fd); + } + struct ev_io *ipc_io = scalloc(sizeof(struct ev_io)); ev_io_init(ipc_io, ipc_new_client, fd, EV_READ); ev_io_start(main_loop, ipc_io); @@ -660,10 +756,55 @@ int main(int argc, char *argv[]) { xcb_flush(conn); - manage_existing_windows(root); + /* What follows is a fugly consequence of X11 protocol race conditions like + * the following: In an i3 in-place restart, i3 will reparent all windows + * to the root window, then exec() itself. In the new process, it calls + * manage_existing_windows. However, in case any application sent a + * generated UnmapNotify message to the WM (as GIMP does), this message + * will be handled by i3 *after* managing the window, thus i3 thinks the + * window just closed itself. In reality, the message was sent in the time + * period where i3 wasn’t running yet. + * + * To prevent this, we grab the server (disables processing of any other + * connections), then discard all pending events (since we didn’t do + * anything, there cannot be any meaningful responses), then ungrab the + * server. */ + xcb_grab_server(conn); + { + xcb_aux_sync(conn); + xcb_generic_event_t *event; + while ((event = xcb_poll_for_event(conn)) != NULL) { + free(event); + } + manage_existing_windows(root); + } + xcb_ungrab_server(conn); + + struct sigaction action; + + action.sa_sigaction = handle_signal; + action.sa_flags = SA_NODEFER | SA_RESETHAND | SA_SIGINFO; + sigemptyset(&action.sa_mask); if (!disable_signalhandler) setup_signal_handler(); + else { + /* Catch all signals with default action "Core", see signal(7) */ + if (sigaction(SIGQUIT, &action, NULL) == -1 || + sigaction(SIGILL, &action, NULL) == -1 || + sigaction(SIGABRT, &action, NULL) == -1 || + sigaction(SIGFPE, &action, NULL) == -1 || + sigaction(SIGSEGV, &action, NULL) == -1) + ELOG("Could not setup signal handler"); + } + + /* Catch all signals with default action "Term", see signal(7) */ + if (sigaction(SIGHUP, &action, NULL) == -1 || + sigaction(SIGINT, &action, NULL) == -1 || + sigaction(SIGALRM, &action, NULL) == -1 || + sigaction(SIGUSR1, &action, NULL) == -1 || + sigaction(SIGUSR2, &action, NULL) == -1) + ELOG("Could not setup signal handler"); /* Ignore SIGPIPE to survive errors when an IPC client disconnects * while we are sending him a message */