~rootmos/xpanel

8fe372afb5b6580a6277797a3d9ff75895bcdf70 — Gustav Behm 3 years ago e78687b
Trigger custom actions when input is received
3 files changed, 76 insertions(+), 18 deletions(-)

M GNUmakefile
M README.md
M src/xpanel.c
M GNUmakefile => GNUmakefile +1 -1
@@ 2,7 2,7 @@ export BUILD ?= $(shell pwd)/build
export PREFIX ?= $(HOME)/.local

run: build
	(echo "löl"; sleep 3) | $(BUILD)/xpanel
	(echo "löl"; sleep 3) | $(BUILD)/xpanel -A 'echo $$0'

install: build
	install --strip -D -t $(PREFIX)/bin $(BUILD)/xpanel

M README.md => README.md +7 -6
@@ 10,10 10,11 @@ Draw current time in the lower left-hand corner:
usage: xpanel [OPTION]...

options:
  -0       use a null character to separate messages (default is newline)
  -a DIR   anchor window to border at DIR (N S W E NW SW NE SE)
  -t NAME  set window title to NAME
  -f FONT  use FONT
  -H       don't close the window when stdin closes
  -h       print this message
  -0        use a null character to separate messages (default is newline)
  -a DIR    anchor window to border at DIR (N S W E NW SW NE SE)
  -t NAME   set window title to NAME
  -f FONT   use FONT
  -H        don't close the window when stdin closes
  -A ACTION run ACTION (like system(3) does) when enter, space or mouse buttons are pressed ($0 is bound to the relevant button)
  -h        print this message
```

M src/xpanel.c => src/xpanel.c +68 -11
@@ 1,10 1,12 @@
#include <errno.h>
#include <poll.h>
#include <signal.h>
#include <sys/signalfd.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>

#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <X11/Xatom.h>
#include <X11/Xft/Xft.h>



@@ 26,6 28,7 @@ enum anchor {
struct options {
    const char* font;
    const char* name;
    const char* action;
    enum anchor anchor;
    char sep;
    size_t initial_buffer_size;


@@ 130,7 133,9 @@ static void x11_init(struct x11_state* x11, const struct options* opts)
                     &cls);
    XFree(name.value);

    XSelectInput(dpy, xw, StructureNotifyMask | ExposureMask);
    XSelectInput(dpy, xw,
                 StructureNotifyMask | ExposureMask
                 | KeyPressMask | ButtonPressMask | ButtonReleaseMask);

    XSync(dpy, False);



@@ 377,8 382,33 @@ static void update_window_position(struct x11_state* x11,
    XMoveResizeWindow(x11->dpy, x11->xw, x, y, ct->extent_x, ct->extent_y);
}

static void trigger_action(const struct options* opts, const char* trigger)
{
    trace("triggering action");
    if(!opts->action) {
        return;
    }

    pid_t p = fork(); CHECK(p, "fork");
    if(p == 0) {
        p = fork(); CHECK(p, "fork");
        if(p == 0) {
            debug("running action (%d): %s", getpid(), opts->action);
            int r = execl("/bin/sh", "sh", "-c", opts->action, trigger, NULL);
            CHECK(r, "execl");
        } else {
            exit(0);
        }
    } else {
        trace("waiting for intermediate child: %d", p);
        int r = waitpid(p, NULL, 0);
        CHECK(r, "waitpid");
    }
}

static void x11_handle_event(struct x11_state* x11, struct xft_state* xft,
                             struct state* st, const struct content* ct)
                             struct state* st, const struct content* ct,
                             const struct options* opts)
{
    while(XPending(x11->dpy)) {
        XEvent ev;


@@ 415,6 445,24 @@ static void x11_handle_event(struct x11_state* x11, struct xft_state* xft,
            draw_window(x11, xft);
        } else if(ev.type == GraphicsExpose) {
        } else if(ev.type == NoExpose) {
        } else if(ev.type == KeyPress) {
            KeySym ks = XkbKeycodeToKeysym(
                x11->dpy, ev.xkey.keycode, 0,
                ev.xkey.state & ShiftMask ? 1 : 0);
            if(ks == XK_Escape || ks == XK_q
               || (ks == XK_w && ev.xkey.state & ControlMask)) {
                debug("keyboard triggered graceful shutdown");
                st->running = 0;
            } else if(ks == XK_Return) {
                trigger_action(opts, "return");
            } else if(ks == XK_space) {
                trigger_action(opts, "space");
            }
        } else if (ev.type == ButtonPress) {
            char buf[10];
            snprintf(LIT(buf), "mouse%u", ev.xbutton.button);
            trigger_action(opts, buf);
        } else if (ev.type == ButtonRelease) {
        } else {
            warning("ignored event: type=%d", ev.type);
        }


@@ 529,12 577,13 @@ static void print_usage(int fd, const char* prog)
    dprintf(fd, "usage: %s [OPTION]...\n", prog);
    dprintf(fd, "\n");
    dprintf(fd, "options:\n");
    dprintf(fd, "  -0       use a null character to separate messages (default is newline)\n");
    dprintf(fd, "  -a DIR   anchor window to border at DIR (N S W E NW SW NE SE)\n");
    dprintf(fd, "  -t NAME  set window title to NAME\n");
    dprintf(fd, "  -f FONT  use FONT\n");
    dprintf(fd, "  -H       don't close the window when stdin closes\n");
    dprintf(fd, "  -h       print this message\n");
    dprintf(fd, "  -0        use a null character to separate messages (default is newline)\n");
    dprintf(fd, "  -a DIR    anchor window to border at DIR (N S W E NW SW NE SE)\n");
    dprintf(fd, "  -t NAME   set window title to NAME\n");
    dprintf(fd, "  -f FONT   use FONT\n");
    dprintf(fd, "  -H        don't close the window when stdin closes\n");
    dprintf(fd, "  -A ACTION run ACTION (like system(3) does) when enter, space or mouse buttons are pressed ($0 is bound to the relevant button)\n");
    dprintf(fd, "  -h        print this message\n");
}

static void parse_options(struct options* o, int argc, char* argv[])


@@ 545,9 594,10 @@ static void parse_options(struct options* o, int argc, char* argv[])
    o->initial_buffer_size = 128;
    o->font = "sans:pixelsize=34";
    o->hang = 0;
    o->action = NULL;

    int res;
    while((res = getopt(argc, argv, "a:0t:f:Hh")) != -1) {
    while((res = getopt(argc, argv, "a:0t:f:HA:h")) != -1) {
        switch(res) {
        case 'a':
            if(strcmp(optarg, "N") == 0) {


@@ 585,6 635,10 @@ static void parse_options(struct options* o, int argc, char* argv[])
        case 'H':
            o->hang = 1;
            break;
        case 'A':
            o->action = strdup(optarg);
            CHECK_MALLOC(o->action);
            break;
        case 'h':
        default:
            print_usage(res == 'h' ? 1 : 2, argv[0]);


@@ 633,13 687,14 @@ int main(int argc, char* argv[])
    while(st.running) {
        int r = poll(fds, 2 + receiving_input, -1);
        CHECK(r, "poll");
        trace("poll");

        if(fds[0].revents & POLLIN) {
            signalfd_handle_event(sfd, &st);
        }

        if(fds[1].revents & POLLIN) {
            x11_handle_event(&x11, &xft, &st, &ct);
            x11_handle_event(&x11, &xft, &st, &ct, &opts);
        }

        if(receiving_input) {


@@ 653,8 708,10 @@ int main(int argc, char* argv[])
            }

            if(fds[2].revents & POLLHUP) {
                trace("HUP");
                receiving_input = 0;
                if(!opts.hang) {
                    debug("HUP and not hanging: initiate graceful shutdown");
                    st.running = 0;
                }
            }