add i3-nagbar. tells you about config file errors (for example)
This commit is contained in:
141
src/cfgparse.y
141
src/cfgparse.y
@ -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 don’t enter the ev main loop anymore and after the
|
||||
* exec(), our old pid is no longer watched. So, ev won’t 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);
|
||||
|
@ -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 */
|
||||
|
24
src/log.c
24
src/log.c
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
27
src/main.c
27
src/main.c
@ -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);
|
||||
}
|
||||
|
50
src/util.c
50
src/util.c
@ -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();
|
||||
|
Reference in New Issue
Block a user