Add `exec` to execute commands when a configuration is matched

This commit is contained in:
Guillaume Maudoux 2019-08-07 17:13:09 +02:00 committed by Simon Ser
parent 15029bd28b
commit 5a30abdf0b
4 changed files with 139 additions and 0 deletions

View File

@ -29,11 +29,17 @@ struct kanshi_profile_output {
enum wl_output_transform transform;
};
struct kanshi_profile_command {
struct wl_list link;
char *command;
};
struct kanshi_profile {
struct wl_list link;
char *name;
// Wildcard outputs are stored at the end of the list
struct wl_list outputs;
struct wl_list commands;
};
struct kanshi_config {

View File

@ -35,6 +35,37 @@ Directives are followed by space-separated arguments. Arguments can be quoted
On *sway*(1), output names and descriptions can be obtained via
*swaymsg -t get_outputs*.
*exec* <command>
An exec directive executes a command when the profile was successfully
applied. This can be used to update the compositor state to the profile
when not done automatically.
Commands are executed asynchronously and their order may not be preserved.
If you need to execute sequential commands, you should collect in one exec
statement or in a separate script.
On *sway*(1) for example, *exec* can be used to move workspaces to the
right output:
```
multihead {
output eDP-1 enable
output DP-1 enable transform 270
exec swaymsg workspace 1, move workspace to eDP-1
}
```
Note that some extra care must be taken with outputs identified by an
output description as the real name may change:
```
complex {
output "Some Other Company GTBZ 2525" mode 1920x1200
exec swaymsg workspace 1, move workspace to output '"Some Other Company GTBZ 2525"'
}
```
# OUTPUT DIRECTIVES
*enable*|*disable*

53
main.c
View File

@ -1,8 +1,12 @@
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <wayland-client.h>
#include "config.h"
@ -71,10 +75,59 @@ static struct kanshi_profile *match(struct kanshi_state *state,
}
static void exec_command(char *cmd) {
pid_t pid, child;
if ((pid = fork()) == 0) {
setsid();
sigset_t set;
sigemptyset(&set);
sigprocmask(SIG_SETMASK, &set, NULL);
if ((child = fork()) == 0) {
execl("/bin/sh", "/bin/sh", "-c", cmd, (void *)NULL);
fprintf(stderr, "Executing command '%s' failed: %s", cmd, strerror(errno));
exit(-1);
}
if (child < 0) {
fprintf(stderr, "Impossible to fork a new process to execute"
" command '%s': %s", cmd, strerror(errno));
exit(0);
}
// Try to give some meaningful information on the command success
int wstatus;
if (waitpid(child, &wstatus, 0) != child) {
perror("waitpid");
exit(0);
}
if (WIFEXITED(wstatus)) {
fprintf(stderr, "Command '%s' returned with exit status %d.\n",
cmd, WEXITSTATUS(wstatus));
} else {
fprintf(stderr, "Command '%s' was killed, aborted or disappeared"
" in dire circumstances.\n", cmd);
}
exit(0); // Close child process
}
if (pid < 0) {
perror("Impossible to fork a new process");
}
}
static void execute_profile_commands(struct kanshi_profile *profile) {
struct kanshi_profile_command *command;
wl_list_for_each(command, &profile->commands, link) {
fprintf(stderr, "Running command '%s'\n", command->command);
exec_command(command->command);
}
}
static void config_handle_succeeded(void *data,
struct zwlr_output_configuration_v1 *config) {
struct kanshi_pending_profile *pending = data;
zwlr_output_configuration_v1_destroy(config);
fprintf(stderr, "running commands for configuration '%s'\n", pending->profile->name);
execute_profile_commands(pending->profile);
fprintf(stderr, "configuration for profile '%s' applied\n",
pending->profile->name);
pending->state->current_profile = pending->profile;

View File

@ -91,6 +91,24 @@ static bool parser_read_quoted(struct kanshi_parser *parser) {
}
}
static bool parser_read_line(struct kanshi_parser *parser) {
while (1) {
int ch = parser_peek_char(parser);
if (ch < 0) {
return false;
}
if (ch == '\n' || ch == '\0') {
parser->tok_str[parser->tok_str_len] = '\0';
return true;
}
if (!parser_append_tok_ch(parser, parser_read_char(parser))) {
return false;
}
}
}
static bool parser_read_str(struct kanshi_parser *parser) {
while (1) {
int ch = parser_peek_char(parser);
@ -338,9 +356,32 @@ static struct kanshi_profile_output *parse_profile_output(
}
}
static struct kanshi_profile_command *parse_profile_command(
struct kanshi_parser *parser) {
// Skip the 'exec' directive.
if (!parser_expect_token(parser, KANSHI_TOKEN_STR)) {
return NULL;
}
if (!parser_read_line(parser)) {
return NULL;
}
if (parser->tok_str_len <= 0) {
fprintf(stderr, "Ignoring empty command in config file on line %d\n",
parser->line);
return NULL;
}
struct kanshi_profile_command *command = calloc(1, sizeof(*command));
command->command = strdup(parser->tok_str);
return command;
}
static struct kanshi_profile *parse_profile(struct kanshi_parser *parser) {
struct kanshi_profile *profile = calloc(1, sizeof(*profile));
wl_list_init(&profile->outputs);
wl_list_init(&profile->commands);
// First parse an optional profile name
parser->tok_str_len = 0;
@ -391,6 +432,14 @@ static struct kanshi_profile *parse_profile(struct kanshi_parser *parser) {
} else {
wl_list_insert(&profile->outputs, &output->link);
}
} else if (strcmp(directive, "exec") == 0) {
struct kanshi_profile_command *command =
parse_profile_command(parser);
if (command == NULL) {
return NULL;
}
// Insert commands at the end to preserve order
wl_list_insert(profile->commands.prev, &command->link);
} else {
fprintf(stderr, "unknown directive '%s' in profile '%s'\n",
directive, profile->name);