summaryrefslogtreecommitdiff
path: root/ipc-client/main.c
blob: fca5b446601b6be550f9fa2c8829962eec3968f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
/** Copyright 2011-2013 Thorsten Wißmann. All rights reserved.
 *
 * This software is licensed under the "Simplified BSD License".
 * See LICENSE for details */

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <getopt.h>
#include <signal.h>
#include <regex.h>
#include <assert.h>

#include "ipc-client.h"
#include "../src/globals.h"
#include "../src/ipc-protocol.h"
#include "client-utils.h"

#define HERBSTCLIENT_VERSION_STRING \
    "herbstclient " HERBSTLUFT_VERSION " (built on " BUILD_DATE ")\n"

void print_help(char* command, FILE* file);
void init_hook_regex(int argc, char* argv[]);
void destroy_hook_regex();

int g_ensure_newline = 1; // if set, output ends with a newline
bool g_null_char_as_delim = false; // if true, the null character is used as delimiter
bool g_print_last_arg_only = false; // if true, prints only the last argument of a hook
int g_wait_for_hook = 0; // if set, do not execute command but wait
bool g_quiet = false;
regex_t* g_hook_regex = NULL;
int g_hook_regex_count = 0;
int g_hook_count = 1; // count of hooks to wait for, 0 means: forever

static void quit_herbstclient(int signal) {
    // TODO: better solution to quit x connection more softly?
    fprintf(stderr, "interrupted by signal %d\n", signal);
    destroy_hook_regex();
    exit(EXIT_FAILURE);
}

void init_hook_regex(int argc, char* argv[]) {
    g_hook_regex = (regex_t*)malloc(sizeof(regex_t)*argc);
    assert(g_hook_regex != NULL);
    int i;
    // create all regexes
    for (i = 0; i < argc; i++) {
        int status = regcomp(g_hook_regex + i, argv[i], REG_NOSUB|REG_EXTENDED);
        if (status != 0) {
            char buf[ERROR_STRING_BUF_SIZE];
            regerror(status, g_hook_regex + i, buf, ERROR_STRING_BUF_SIZE);
            fprintf(stderr, "Cannot parse regex \"%s\": ", argv[i]);
            fprintf(stderr, "%s\n", buf);
            destroy_hook_regex();
            exit(EXIT_FAILURE);
        }
    }
    g_hook_regex_count = argc;
}

void destroy_hook_regex() {
    int i;
    for (i = 0; i < g_hook_regex_count; i++) {
        regfree(g_hook_regex + i);
    }
    free(g_hook_regex);
}


void print_help(char* command, FILE* file) {
    // Eventually replace this and the option parsing with some fancy macro
    // based thing? Is the cost of maintainance really that high?

    fprintf(file,
        "Usage: %s [OPTIONS] COMMAND [ARGS ...]\n"
        "       %s [OPTIONS] [--wait|--idle] [FILTER ...]\n",
        command, command);

    char* help_string =
        "Send a COMMAND with optional arguments ARGS to a running "
        "herbstluftwm instance.\n\n"
        "Options:\n"
        "\t-n, --no-newline: Do not print a newline if output does not end "
            "with a newline.\n"
        "\t-0, --print0: Use the null character as delimiter between the "
            "output of hooks.\n"
        "\t-l, --last-arg: Print only the last argument of a hook.\n"
        "\t-i, --idle: Wait for hooks instead of executing commands.\n"
        "\t-w, --wait: Same as --idle but exit after first --count hooks.\n"
        "\t-c, --count COUNT: Let --wait exit after COUNT hooks were "
            "received and printed. The default of COUNT is 1.\n"
        "\t-q, --quiet: Do not print error messages if herbstclient cannot "
            "connect to the running herbstluftwm instance.\n"
        "\t-v, --version: Print the herbstclient version. To get the "
            "herbstluftwm version, use 'herbstclient version'.\n"
        "\t-h, --help: Print this help."
        "\n"
        "See the man page (herbstclient(1)) for more details.\n";
    fputs(help_string, file);
}

int main_hook(int argc, char* argv[]) {
    init_hook_regex(argc, argv);
    Display* display = XOpenDisplay(NULL);
    if (!display) {
        if (!g_quiet) {
            fprintf(stderr, "Cannot open display\n");
        }
        return EXIT_FAILURE;
    }
    HCConnection* con = hc_connect_to_display(display);
    signal(SIGTERM, quit_herbstclient);
    signal(SIGINT,  quit_herbstclient);
    signal(SIGQUIT, quit_herbstclient);
    while (1) {
        bool print_signal = true;
        int hook_argc;
        char** hook_argv;
        if (!hc_next_hook(con, &hook_argc, &hook_argv)) {
            fprintf(stderr, "Cannot listen for hooks\n");
            destroy_hook_regex();
            return EXIT_FAILURE;
        }
        for (int i = 0; i < argc && i < hook_argc; i++) {
            if (0 != regexec(g_hook_regex + i, hook_argv[i], 0, NULL, 0)) {
                // found an regex that did not match
                // so skip this
                print_signal = false;
                break;
            }
        }
        if (print_signal) {
            if (g_print_last_arg_only) {
                // just drop hooks without content
                if (hook_argc >= 1) {
                    printf("%s", hook_argv[hook_argc-1]);
                }
            } else {
                // just print as list
                for (int i = 0; i < hook_argc; i++) {
                    printf("%s%s", i ? "\t" : "", hook_argv[i]);
                }
            }
            if (g_null_char_as_delim) {
                putchar(0);
            } else {
                printf("\n");
            }
            fflush(stdout);
        }
        argv_free(hook_argc, hook_argv);
        if (print_signal) {
            // check counter
            if (g_hook_count == 1) {
                break;
            } else if (g_hook_count > 1) {
                g_hook_count--;
            }
        }
    }
    hc_disconnect(con);
    XCloseDisplay(display);
    destroy_hook_regex();
    return 0;
}

int main(int argc, char* argv[]) {
    static struct option long_options[] = {
        {"no-newline", 0, 0, 'n'},
        {"print0", 0, 0, '0'},
        {"last-arg", 0, 0, 'l'},
        {"wait", 0, 0, 'w'},
        {"count", 1, 0, 'c'},
        {"idle", 0, 0, 'i'},
        {"quiet", 0, 0, 'q'},
        {"version", 0, 0, 'v'},
        {"help", 0, 0, 'h'},
        {0, 0, 0, 0}
    };
    // parse options
    while (1) {
        int option_index = 0;
        int c = getopt_long(argc, argv, "+n0lwc:iqhv", long_options, &option_index);
        if (c == -1) break;
        switch (c) {
            case 'i':
                g_hook_count = 0;
                g_wait_for_hook = 1;
                break;
            case 'c':
                g_hook_count = atoi(optarg);
                printf("setting to  %s\n", optarg);
                break;
            case 'w':
                g_wait_for_hook = 1;
                break;
            case 'n':
                g_ensure_newline = 0;
                break;
            case '0':
                g_null_char_as_delim = true;
                break;
            case 'l':
                g_print_last_arg_only = true;
                break;
            case 'q':
                g_quiet = true;
                break;
            case 'h':
                print_help(argv[0], stdout);
                exit(EXIT_SUCCESS);
            case 'v':
                fputs(HERBSTCLIENT_VERSION_STRING, stdout);
                exit(EXIT_SUCCESS);
            default:
                exit(EXIT_FAILURE);
        }
    }
    int arg_index = optind; // index of the first-non-option argument
    if ((argc - arg_index == 0) && !g_wait_for_hook) {
        // if there are no non-option arguments, and no --idle/--wait, display
        // the help and exit
        print_help(argv[0], stderr);
        exit(EXIT_FAILURE);
    }
    // do communication
    int command_status;
    if (g_wait_for_hook == 1) {
        // install signals
        command_status = main_hook(argc-arg_index, argv+arg_index);
    } else {
        GString* output;
        bool suc = hc_send_command_once(argc-arg_index, argv+arg_index,
                                        &output, &command_status);
        if (!suc) {
            fprintf(stderr, "Error: Could not send command.\n");
            return EXIT_FAILURE;
        }
        FILE* file = stdout; // on success, output to stdout
        if (command_status != 0) { // any error, output to stderr
            file = stderr;
        }
        fputs(output->str, file);
        if (g_ensure_newline) {
            if (output->len > 0 && output->str[output->len - 1] != '\n') {
                fputs("\n", file);
            }
        }
        if (command_status == HERBST_NEED_MORE_ARGS) { // needs more arguments
            fprintf(stderr, "%s: not enough arguments\n", argv[arg_index]); // first argument == cmd
        }
        g_string_free(output, true);
    }
    return command_status;
}