add i3-nagbar. tells you about config file errors (for example)

This commit is contained in:
Michael Stapelberg
2011-07-10 14:33:19 +02:00
parent b63a559c28
commit c55abca115
17 changed files with 864 additions and 45 deletions

View File

@ -9,10 +9,11 @@
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#include <libgen.h>
#include "all.h"
static pid_t configerror_pid = -1;
static Match current_match;
typedef struct yy_buffer_state *YY_BUFFER_STATE;
@ -30,17 +31,18 @@ static struct context *context;
//int yydebug = 1;
void yyerror(const char *error_message) {
context->has_errors = true;
ELOG("\n");
ELOG("CONFIG: %s\n", error_message);
ELOG("CONFIG: in file \"%s\", line %d:\n",
context->filename, context->line_number);
ELOG("CONFIG: %s\n", context->line_copy);
ELOG("CONFIG: ");
char buffer[context->last_column+1];
buffer[context->last_column] = '\0';
for (int c = 1; c <= context->last_column; c++)
if (c >= context->first_column)
printf("^");
else printf(" ");
printf("\n");
buffer[c-1] = (c >= context->first_column ? '^' : ' ');
ELOG("CONFIG: %s\n", buffer);
ELOG("\n");
}
@ -146,32 +148,11 @@ static char *migrate_config(char *input, off_t size) {
close(readpipe[0]);
dup2(readpipe[1], 1);
/* start the migration script, search PATH first */
char *migratepath = "i3-migrate-config-to-v4.pl";
execlp(migratepath, migratepath, NULL);
/* if the script is not in path, maybe the user installed to a strange
* location and runs the i3 binary with an absolute path. We use
* argv[0]s dirname */
char *pathbuf = strdup(start_argv[0]);
char *dir = dirname(pathbuf);
asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl");
execlp(migratepath, migratepath, NULL);
#if defined(__linux__)
/* on linux, we have one more fall-back: dirname(/proc/self/exe) */
char buffer[BUFSIZ];
if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
warn("could not read /proc/self/exe");
exit(1);
}
dir = dirname(buffer);
asprintf(&migratepath, "%s/%s", dir, "i3-migrate-config-to-v4.pl");
execlp(migratepath, migratepath, NULL);
#endif
warn("Could not start i3-migrate-config-to-v4.pl");
exit(2);
static char *argv[] = {
NULL, /* will be replaced by the executable path */
NULL
};
exec_i3_utility("i3-migrate-config-to-v4.pl", argv);
}
/* parent */
@ -237,6 +218,98 @@ static char *migrate_config(char *input, off_t size) {
return converted;
}
/*
* Handler which will be called when we get a SIGCHLD for the nagbar, meaning
* it exited (or could not be started, depending on the exit code).
*
*/
static void nagbar_exited(EV_P_ ev_child *watcher, int revents) {
ev_child_stop(EV_A_ watcher);
if (!WIFEXITED(watcher->rstatus)) {
fprintf(stderr, "ERROR: i3-nagbar did not exit normally.\n");
return;
}
int exitcode = WEXITSTATUS(watcher->rstatus);
printf("i3-nagbar process exited with status %d\n", exitcode);
if (exitcode == 2) {
fprintf(stderr, "ERROR: i3-nagbar could not be found. Is it correctly installed on your system?\n");
}
configerror_pid = -1;
}
/*
* Starts an i3-nagbar process which alerts the user that his configuration
* file contains one or more errors. Also offers two buttons: One to launch an
* $EDITOR on the config file and another one to launch a $PAGER on the error
* logfile.
*
*/
static void start_configerror_nagbar(const char *config_path) {
fprintf(stderr, "Would start i3-nagscreen now\n");
configerror_pid = fork();
if (configerror_pid == -1) {
warn("Could not fork()");
return;
}
/* child */
if (configerror_pid == 0) {
char *editaction,
*pageraction;
if (asprintf(&editaction, TERM_EMU " -e $EDITOR \"%s\"", config_path) == -1)
exit(1);
if (asprintf(&pageraction, TERM_EMU " -e $PAGER \"%s\"", errorfilename) == -1)
exit(1);
char *argv[] = {
NULL, /* will be replaced by the executable path */
"-m",
"You have an error in your i3 config file!",
"-b",
"edit config",
editaction,
(errorfilename ? "-b" : NULL),
"show errors",
pageraction,
NULL
};
exec_i3_utility("i3-nagbar", argv);
}
/* parent */
/* install a child watcher */
ev_child *child = smalloc(sizeof(ev_child));
ev_child_init(child, &nagbar_exited, configerror_pid, 0);
ev_child_start(main_loop, child);
}
/*
* Kills the configerror i3-nagbar process, if any.
*
* Called when reloading/restarting.
*
* If wait_for_it is set (restarting), this function will waitpid(), otherwise,
* ev is assumed to handle it (reloading).
*
*/
void kill_configerror_nagbar(bool wait_for_it) {
if (configerror_pid == -1)
return;
if (kill(configerror_pid, SIGTERM) == -1)
warn("kill(configerror_nagbar) failed");
if (!wait_for_it)
return;
/* When restarting, we dont enter the ev main loop anymore and after the
* exec(), our old pid is no longer watched. So, ev wont handle SIGCHLD
* for us and we would end up with a <defunct> process. Therefore we
* waitpid() here. */
waitpid(configerror_pid, NULL, 0);
}
void parse_file(const char *f) {
SLIST_HEAD(variables_head, Variable) variables = SLIST_HEAD_INITIALIZER(&variables);
int fd, ret, read_bytes = 0;
@ -390,6 +463,10 @@ void parse_file(const char *f) {
exit(1);
}
if (context->has_errors) {
start_configerror_nagbar(f);
}
FREE(context->line_copy);
free(context);
free(new);

View File

@ -380,6 +380,7 @@ reload:
TOK_RELOAD
{
printf("reloading\n");
kill_configerror_nagbar(false);
load_configuration(conn, NULL, true);
x_set_i3_atoms();
/* Send an IPC event just in case the ws names have changed */

View File

@ -14,6 +14,7 @@
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include "util.h"
#include "log.h"
@ -23,6 +24,23 @@
static uint64_t loglevel = 0;
static bool verbose = true;
static FILE *errorfile;
char *errorfilename;
/*
* Initializes logging by creating an error logfile in /tmp (or
* XDG_RUNTIME_DIR, see get_process_filename()).
*
*/
void init_logging() {
errorfilename = get_process_filename("errorlog");
if (errorfilename == NULL) {
ELOG("Could not initialize errorlog\n");
return;
}
errorfile = fopen(errorfilename, "w");
}
/*
* Set verbosity of i3. If verbose is set to true, informative messages will
@ -101,6 +119,12 @@ void errorlog(char *fmt, ...) {
va_start(args, fmt);
vlog(fmt, args);
va_end(args);
/* also log to the error logfile, if opened */
va_start(args, fmt);
vfprintf(errorfile, fmt, args);
fflush(errorfile);
va_end(args);
}
/*

View File

@ -19,6 +19,8 @@ xcb_connection_t *conn;
xcb_window_t root;
uint8_t root_depth;
struct ev_loop *main_loop;
xcb_key_symbols_t *keysyms;
/* Those are our connections to X11 for use with libXcursor and XKB */
@ -178,6 +180,8 @@ int main(int argc, char *argv[]) {
if (!isatty(fileno(stdout)))
setbuf(stdout, NULL);
init_logging();
start_argv = argv;
while ((opt = getopt_long(argc, argv, "c:CvaL:hld:V", long_options, &option_index)) != -1) {
@ -254,6 +258,13 @@ int main(int argc, char *argv[]) {
if (xcb_connection_has_error(conn))
errx(EXIT_FAILURE, "Cannot open display\n");
/* Initialize the libev event loop. This needs to be done before loading
* the config file because the parser will install an ev_child watcher
* for the nagbar when config errors are found. */
main_loop = EV_DEFAULT;
if (main_loop == NULL)
die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
xcb_screen_t *root_screen = xcb_aux_get_screen(conn, screens);
root = root_screen->root;
root_depth = root_screen->root_depth;
@ -395,10 +406,6 @@ int main(int argc, char *argv[]) {
tree_render();
struct ev_loop *loop = ev_loop_new(0);
if (loop == NULL)
die("Could not initialize libev. Bad LIBEV_FLAGS?\n");
/* Create the UNIX domain socket for IPC */
int ipc_socket = ipc_create_socket(config.ipc_socket_path);
if (ipc_socket == -1) {
@ -407,7 +414,7 @@ int main(int argc, char *argv[]) {
free(config.ipc_socket_path);
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);
ev_io_start(main_loop, ipc_io);
}
/* Set up i3 specific atoms like I3_SOCKET_PATH and I3_CONFIG_PATH */
@ -419,22 +426,22 @@ int main(int argc, char *argv[]) {
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);
ev_io_start(main_loop, xcb_watcher);
if (xkb_supported) {
ev_io_init(xkb, xkb_got_event, ConnectionNumber(xkbdpy), EV_READ);
ev_io_start(loop, xkb);
ev_io_start(main_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_check_start(main_loop, xcb_check);
ev_prepare_init(xcb_prepare, xcb_prepare_cb);
ev_prepare_start(loop, xcb_prepare);
ev_prepare_start(main_loop, xcb_prepare);
xcb_flush(conn);
@ -456,5 +463,5 @@ int main(int argc, char *argv[]) {
}
}
ev_loop(loop, 0);
ev_loop(main_loop, 0);
}

View File

@ -19,6 +19,7 @@
#include <fcntl.h>
#include <pwd.h>
#include <yajl/yajl_version.h>
#include <libgen.h>
#include "all.h"
@ -118,6 +119,53 @@ void start_application(const char *command) {
wait(0);
}
/*
* exec()s an i3 utility, for example the config file migration script or
* i3-nagbar. This function first searches $PATH for the given utility named,
* then falls back to the dirname() of the i3 executable path and then falls
* back to the dirname() of the target of /proc/self/exe (on linux).
*
* This function should be called after fork()ing.
*
* The first argument of the given argv vector will be overwritten with the
* executable name, so pass NULL.
*
* If the utility cannot be found in any of these locations, it exits with
* return code 2.
*
*/
void exec_i3_utility(char *name, char *argv[]) {
/* start the migration script, search PATH first */
char *migratepath = name;
argv[0] = migratepath;
execvp(migratepath, argv);
/* if the script is not in path, maybe the user installed to a strange
* location and runs the i3 binary with an absolute path. We use
* argv[0]s dirname */
char *pathbuf = strdup(start_argv[0]);
char *dir = dirname(pathbuf);
asprintf(&migratepath, "%s/%s", dir, name);
argv[0] = migratepath;
execvp(migratepath, argv);
#if defined(__linux__)
/* on linux, we have one more fall-back: dirname(/proc/self/exe) */
char buffer[BUFSIZ];
if (readlink("/proc/self/exe", buffer, BUFSIZ) == -1) {
warn("could not read /proc/self/exe");
exit(1);
}
dir = dirname(buffer);
asprintf(&migratepath, "%s/%s", dir, name);
argv[0] = migratepath;
execvp(migratepath, argv);
#endif
warn("Could not start %s", name);
exit(2);
}
/*
* Checks a generic cookie for errors and quits with the given message if there
* was an error.
@ -358,6 +406,8 @@ char *store_restart_layout() {
void i3_restart(bool forget_layout) {
char *restart_filename = forget_layout ? NULL : store_restart_layout();
kill_configerror_nagbar(true);
restore_geometry();
ipc_shutdown();