1
0
mirror of https://github.com/artizirk/wdisplays.git synced 2024-11-28 02:41:00 +02:00

Add integration with kanshi daemon

This commit is contained in:
Jason Francis 2019-09-08 18:29:31 -04:00
parent a3d3d13a01
commit e208957fa2
6 changed files with 910 additions and 21 deletions

2
meson_options.txt Normal file
View File

@ -0,0 +1,2 @@
option('kanshi', type: 'feature', value: 'auto', description: 'Enable integration with the kanshi daemon')

95
src/kanshi.h Normal file
View File

@ -0,0 +1,95 @@
/*
* Copyright (C) 2017-2019 emersion
* Copyright (C) 2019 cyclopsian
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Parts of this file are taken from emersion/kanshi:
* https://github.com/emersion/kanshi/blob/38d27474b686fcc8324cc5e454741a49577c0988/include/config.h
* https://github.com/emersion/kanshi/blob/38d27474b686fcc8324cc5e454741a49577c0988/include/parser.h
*/
#ifndef WDISPLAYS_KANSHI_H
#define WDISPLAYS_KANSHI_H
#include <stdbool.h>
#include <wayland-client.h>
enum kanshi_output_field {
KANSHI_OUTPUT_ENABLED = 1 << 0,
KANSHI_OUTPUT_MODE = 1 << 1,
KANSHI_OUTPUT_POSITION = 1 << 2,
KANSHI_OUTPUT_SCALE = 1 << 3,
KANSHI_OUTPUT_TRANSFORM = 1 << 4,
};
struct kanshi_profile_output {
char *name;
unsigned int fields; // enum kanshi_output_field
struct wl_list link;
bool enabled;
struct {
int width, height;
int refresh; // mHz
} mode;
struct {
int x, y;
} position;
float scale;
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 commands;
struct wl_list outputs;
};
struct kanshi_config {
struct wl_list profiles;
};
/*
* Loads the kanshi config from the given file.
*/
struct kanshi_config *kanshi_parse_config(const char *path);
/*
* Saves the kanshi config to the given file.
*/
void kanshi_save_config(const char *path, struct kanshi_config *config);
/*
* Destroys the config structure.
*/
void kanshi_destroy_config(struct kanshi_config *config);
#endif

View File

@ -7,15 +7,31 @@ gtk = dependency('gtk+-3.0')
assert(gdk.get_pkgconfig_variable('targets').split().contains('wayland'), 'Wayland GDK backend not present') assert(gdk.get_pkgconfig_variable('targets').split().contains('wayland'), 'Wayland GDK backend not present')
epoxy = dependency('epoxy') epoxy = dependency('epoxy')
wdisplays_src = files(
'main.c',
'outputs.c',
'render.c',
'glviewport.c',
'overlay.c'
)
wdisplays_args = []
kanshictl = find_program('kanshictl', required: get_option('kanshi'))
if kanshictl.found()
wdisplays_src += files(
'parser.c'
)
wdisplays_args += [
'-DHAVE_KANSHI',
'-DKANSHICTL_PATH="@0@"'.format(kanshictl.path())
]
endif
executable( executable(
'wdisplays', 'wdisplays',
[ [
'main.c', wdisplays_src,
'outputs.c', resources
'render.c',
'glviewport.c',
'overlay.c',
resources,
], ],
dependencies : [ dependencies : [
m_dep, m_dep,
@ -25,5 +41,6 @@ executable(
epoxy, epoxy,
gtk gtk
], ],
c_args: wdisplays_args,
install: true install: true
) )

View File

@ -27,7 +27,6 @@
* https://github.com/emersion/kanshi/blob/38d27474b686fcc8324cc5e454741a49577c0988/main.c * https://github.com/emersion/kanshi/blob/38d27474b686fcc8324cc5e454741a49577c0988/main.c
*/ */
#define _GNU_SOURCE
#include <assert.h> #include <assert.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
@ -40,7 +39,12 @@
#include <fcntl.h> #include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <glib.h>
#include "wdisplays.h" #include "wdisplays.h"
#ifdef HAVE_KANSHI
#include "kanshi.h"
#endif
#include "wlr-output-management-unstable-v1-client-protocol.h" #include "wlr-output-management-unstable-v1-client-protocol.h"
#include "xdg-output-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h"
@ -66,6 +70,109 @@ static void destroy_pending(struct wd_pending_config *pending) {
free(pending); free(pending);
} }
#ifdef HAVE_KANSHI
static struct kanshi_profile *find_active_profile(
const struct kanshi_config *config, const struct wl_list *outputs) {
struct kanshi_profile *profile;
int head_count = wl_list_length(outputs);
wl_list_for_each(profile, &config->profiles, link) {
unsigned found_count = 0;
struct kanshi_profile_output *output;
wl_list_for_each(output, &profile->outputs, link) {
struct wd_head_config *head;
wl_list_for_each(head, outputs, link) {
if (strcmp(output->name, head->head->name) == 0) {
found_count++;
break;
}
}
}
if (found_count == head_count) {
return profile;
}
}
return NULL;
}
static void update_profile_output(struct kanshi_profile_output *output,
const struct wd_head_config *head) {
output->enabled = head->enabled;
output->fields |= KANSHI_OUTPUT_ENABLED;
if (head->enabled) {
output->fields |= KANSHI_OUTPUT_MODE | KANSHI_OUTPUT_POSITION
| KANSHI_OUTPUT_SCALE | KANSHI_OUTPUT_TRANSFORM;
output->mode.width = head->width;
output->mode.height = head->height;
output->mode.refresh = head->refresh;
output->position.x = head->x;
output->position.y = head->y;
output->scale = head->scale;
output->transform = head->transform;
}
}
static bool save_config(const struct wl_list *outputs) {
g_autofree const char *config_dir = g_strjoin("/",
g_get_user_config_dir(), "kanshi/config.d", NULL);
if (g_mkdir_with_parents(config_dir, 0700) == -1) {
fprintf(stderr, "g_mkdir_with_parents failed: %s: %s\n",
config_dir, strerror(errno));
return false;
}
g_autofree const char *config_path = g_strjoin("/",
config_dir, "50-wdisplays", NULL);
struct kanshi_config *config = kanshi_parse_config(config_path);
if (config == NULL) {
config = calloc(1, sizeof(*config));
wl_list_init(&config->profiles);
}
struct kanshi_profile *profile = find_active_profile(config, outputs);
if (profile == NULL) {
profile = calloc(1, sizeof(*profile));
wl_list_init(&profile->outputs);
struct wd_head_config *head;
wl_list_for_each(head, outputs, link) {
struct kanshi_profile_output *output = calloc(1, sizeof(*output));
output->name = strdup(head->head->name);
update_profile_output(output, head);
wl_list_insert(profile->outputs.prev, &output->link);
}
wl_list_insert(&config->profiles, &profile->link);
} else {
struct kanshi_profile_output *output;
wl_list_for_each(output, &profile->outputs, link) {
struct wd_head_config *head;
wl_list_for_each(head, outputs, link) {
if (strcmp(output->name, head->head->name) == 0) {
update_profile_output(output, head);
break;
}
}
}
}
kanshi_save_config(config_path, config);
kanshi_destroy_config(config);
GError *error = NULL;
gchar *argv[] = { KANSHICTL_PATH, "reload", NULL };
gint status;
g_spawn_sync(NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL,
NULL, NULL, &status, &error);
if (error != NULL) {
fprintf(stderr, "Could not execute " KANSHICTL_PATH ": %s\n",
error->message);
g_error_free(error);
return false;
}
if (status != EXIT_SUCCESS) {
fprintf(stderr, "Could not execute " KANSHICTL_PATH "\n");
return false;
}
return true;
}
#endif
static void config_handle_succeeded(void *data, static void config_handle_succeeded(void *data,
struct zwlr_output_configuration_v1 *config) { struct zwlr_output_configuration_v1 *config) {
struct wd_pending_config *pending = data; struct wd_pending_config *pending = data;
@ -103,13 +210,21 @@ static const struct zwlr_output_configuration_v1_listener config_listener = {
void wd_apply_state(struct wd_state *state, struct wl_list *new_outputs, void wd_apply_state(struct wd_state *state, struct wl_list *new_outputs,
struct wl_display *display) { struct wl_display *display) {
struct zwlr_output_configuration_v1 *config =
zwlr_output_manager_v1_create_configuration(state->output_manager, state->serial);
struct wd_pending_config *pending = calloc(1, sizeof(*pending)); struct wd_pending_config *pending = calloc(1, sizeof(*pending));
pending->state = state; pending->state = state;
pending->outputs = new_outputs; pending->outputs = new_outputs;
#ifdef HAVE_KANSHI
if (save_config(new_outputs)) {
wd_ui_apply_done(pending->state, pending->outputs);
destroy_pending(pending);
return;
}
#endif
struct zwlr_output_configuration_v1 *config =
zwlr_output_manager_v1_create_configuration(state->output_manager, state->serial);
zwlr_output_configuration_v1_add_listener(config, &config_listener, pending); zwlr_output_configuration_v1_add_listener(config, &config_listener, pending);
ssize_t i = -1; ssize_t i = -1;
@ -177,20 +292,13 @@ static void wd_frame_destroy(struct wd_frame *frame) {
} }
static int create_shm_file(size_t size, const char *fmt, ...) { static int create_shm_file(size_t size, const char *fmt, ...) {
char *shm_name = NULL;
int fd = -1; int fd = -1;
va_list vl; va_list vl;
va_start(vl, fmt); va_start(vl, fmt);
int result = vasprintf(&shm_name, fmt, vl); char *shm_name = g_strdup_vprintf(fmt, vl);
va_end(vl); va_end(vl);
if (result == -1) {
fprintf(stderr, "asprintf: %s\n", strerror(errno));
shm_name = NULL;
return -1;
}
fd = shm_open(shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); fd = shm_open(shm_name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (fd == -1) { if (fd == -1) {
fprintf(stderr, "shm_open: %s\n", strerror(errno)); fprintf(stderr, "shm_open: %s\n", strerror(errno));

668
src/parser.c Normal file
View File

@ -0,0 +1,668 @@
/*
* Copyright (C) 2017-2019 emersion
* Copyright (C) 2019 cyclopsian
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE X CONSORTIUM BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Parts of this file are taken from emersion/kanshi:
* https://github.com/emersion/kanshi/blob/38d27474b686fcc8324cc5e454741a49577c0988/parser.c
* https://github.com/emersion/kanshi/blob/38d27474b686fcc8324cc5e454741a49577c0988/include/config.h
*/
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kanshi.h"
enum kanshi_token_type {
KANSHI_TOKEN_LBRACKET,
KANSHI_TOKEN_RBRACKET,
KANSHI_TOKEN_STR,
KANSHI_TOKEN_NEWLINE,
KANSHI_TOKEN_COMMENT,
};
struct kanshi_parser {
FILE *f;
int next;
int line, col;
enum kanshi_token_type tok_type;
char tok_str[1024];
size_t tok_str_len;
};
static char commentSign = '#';
static const char *token_type_str(enum kanshi_token_type t) {
switch (t) {
case KANSHI_TOKEN_LBRACKET:
return "'{'";
case KANSHI_TOKEN_RBRACKET:
return "'}'";
case KANSHI_TOKEN_STR:
return "string";
case KANSHI_TOKEN_NEWLINE:
return "newline";
case KANSHI_TOKEN_COMMENT:
return "comment";
}
assert(0);
}
static int parser_read_char(struct kanshi_parser *parser) {
if (parser->next >= 0) {
int ch = parser->next;
parser->next = -1;
return ch;
}
errno = 0;
int ch = fgetc(parser->f);
if (ch == EOF) {
if (errno != 0) {
fprintf(stderr, "fgetc failed: %s\n", strerror(errno));
} else {
return '\0';
}
return -1;
}
if (ch == '\n') {
parser->line++;
parser->col = 0;
} else {
parser->col++;
}
return ch;
}
static int parser_peek_char(struct kanshi_parser *parser) {
int ch = parser_read_char(parser);
parser->next = ch;
return ch;
}
static bool parser_append_tok_ch(struct kanshi_parser *parser, char ch) {
// Always keep enough room for a terminating NULL char
if (parser->tok_str_len + 1 >= sizeof(parser->tok_str)) {
fprintf(stderr, "string too long\n");
return false;
}
parser->tok_str[parser->tok_str_len] = ch;
parser->tok_str_len++;
return true;
}
static bool parser_read_quoted(struct kanshi_parser *parser) {
while (1) {
int ch = parser_read_char(parser);
if (ch < 0) {
return false;
} else if (ch == '\0') {
fprintf(stderr, "unterminated quoted string\n");
return false;
}
if (ch == '"') {
parser->tok_str[parser->tok_str_len] = '\0';
return true;
}
if (!parser_append_tok_ch(parser, ch)) {
return false;
}
}
}
static void parser_ignore_line(struct kanshi_parser *parser) {
while (1) {
int ch = parser_read_char(parser);
if (ch < 0) {
return;
}
if (ch == '\n' || ch == '\0') {
return;
}
}
}
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);
if (ch < 0) {
return false;
}
if (isspace(ch) || ch == '{' || ch == '}' || 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_next_token(struct kanshi_parser *parser) {
while (1) {
int ch = parser_read_char(parser);
if (ch < 0) {
return ch;
}
if (ch == '{') {
parser->tok_type = KANSHI_TOKEN_LBRACKET;
return true;
} else if (ch == '}') {
parser->tok_type = KANSHI_TOKEN_RBRACKET;
return true;
} else if (ch == '\n') {
parser->tok_type = KANSHI_TOKEN_NEWLINE;
return true;
} else if (isspace(ch)) {
continue;
} else if (ch == '"') {
parser->tok_type = KANSHI_TOKEN_STR;
parser->tok_str_len = 0;
return parser_read_quoted(parser);
} else if (ch == commentSign) {
parser->tok_type = KANSHI_TOKEN_COMMENT;
parser->tok_str_len = 0;
return true;
} else {
parser->tok_type = KANSHI_TOKEN_STR;
parser->tok_str[0] = ch;
parser->tok_str_len = 1;
return parser_read_str(parser);
}
}
}
static bool parser_expect_token(struct kanshi_parser *parser,
enum kanshi_token_type want) {
if (!parser_next_token(parser)) {
return false;
}
if (parser->tok_type != want) {
fprintf(stderr, "expected %s, got %s\n",
token_type_str(want), token_type_str(parser->tok_type));
return false;
}
return true;
}
static bool parse_int(int *dst, const char *str) {
char *end;
errno = 0;
int v = strtol(str, &end, 10);
if (errno != 0 || end[0] != '\0' || str[0] == '\0') {
return false;
}
*dst = v;
return true;
}
static bool parse_mode(struct kanshi_profile_output *output, char *str) {
const char *width = strtok(str, "x");
const char *height = strtok(NULL, "@");
const char *refresh = strtok(NULL, "");
if (width == NULL || height == NULL) {
fprintf(stderr, "invalid output mode: missing width/height\n");
return false;
}
if (!parse_int(&output->mode.width, width)) {
fprintf(stderr, "invalid output mode: invalid width\n");
return false;
}
if (!parse_int(&output->mode.height, height)) {
fprintf(stderr, "invalid output mode: invalid height\n");
return false;
}
if (refresh != NULL) {
char *end;
errno = 0;
float v = strtof(refresh, &end);
if (errno != 0 || (end[0] != '\0' && strcmp(end, "Hz") != 0) ||
str[0] == '\0') {
fprintf(stderr, "invalid output mode: invalid refresh rate\n");
return false;
}
output->mode.refresh = v * 1000;
}
return true;
}
static bool parse_position(struct kanshi_profile_output *output, char *str) {
const char *x = strtok(str, ",");
const char *y = strtok(NULL, "");
if (x == NULL || y == NULL) {
fprintf(stderr, "invalid output position: missing x/y\n");
return false;
}
if (!parse_int(&output->position.x, x)) {
fprintf(stderr, "invalid output position: invalid x\n");
return false;
}
if (!parse_int(&output->position.y, y)) {
fprintf(stderr, "invalid output position: invalid y\n");
return false;
}
return true;
}
static bool parse_float(float *dst, const char *str) {
char *end;
errno = 0;
float v = strtof(str, &end);
if (errno != 0 || end[0] != '\0' || str[0] == '\0') {
return false;
}
*dst = v;
return true;
}
static bool parse_transform(enum wl_output_transform *dst, const char *str) {
if (strcmp(str, "normal") == 0) {
*dst = WL_OUTPUT_TRANSFORM_NORMAL;
} else if (strcmp(str, "90") == 0) {
*dst = WL_OUTPUT_TRANSFORM_90;
} else if (strcmp(str, "180") == 0) {
*dst = WL_OUTPUT_TRANSFORM_180;
} else if (strcmp(str, "270") == 0) {
*dst = WL_OUTPUT_TRANSFORM_270;
} else if (strcmp(str, "flipped") == 0) {
*dst = WL_OUTPUT_TRANSFORM_FLIPPED;
} else if (strcmp(str, "flipped-90") == 0) {
*dst = WL_OUTPUT_TRANSFORM_FLIPPED_90;
} else if (strcmp(str, "flipped-180") == 0) {
*dst = WL_OUTPUT_TRANSFORM_FLIPPED_180;
} else if (strcmp(str, "flipped-270") == 0) {
*dst = WL_OUTPUT_TRANSFORM_FLIPPED_270;
} else {
return false;
}
return true;
}
static struct kanshi_profile_output *parse_profile_output(
struct kanshi_parser *parser) {
struct kanshi_profile_output *output = calloc(1, sizeof(*output));
if (!parser_expect_token(parser, KANSHI_TOKEN_STR)) {
return NULL;
}
output->name = strdup(parser->tok_str);
bool has_key = false;
enum kanshi_output_field key;
while (1) {
if (!parser_next_token(parser)) {
return NULL;
}
switch (parser->tok_type) {
case KANSHI_TOKEN_STR:
if (has_key) {
char *value = parser->tok_str;
switch (key) {
case KANSHI_OUTPUT_MODE:
if (!parse_mode(output, value)) {
return NULL;
}
break;
case KANSHI_OUTPUT_POSITION:
if (!parse_position(output, value)) {
return NULL;
}
break;
case KANSHI_OUTPUT_SCALE:
if (!parse_float(&output->scale, value)) {
fprintf(stderr, "invalid output scale\n");
return NULL;
}
break;
case KANSHI_OUTPUT_TRANSFORM:
if (!parse_transform(&output->transform, value)) {
fprintf(stderr, "invalid output transform\n");
return NULL;
}
break;
default:
assert(0);
}
has_key = false;
output->fields |= key;
} else {
has_key = true;
const char *key_str = parser->tok_str;
if (strcmp(key_str, "enable") == 0) {
output->enabled = true;
output->fields |= KANSHI_OUTPUT_ENABLED;
has_key = false;
} else if (strcmp(key_str, "disable") == 0) {
output->enabled = false;
output->fields |= KANSHI_OUTPUT_ENABLED;
has_key = false;
} else if (strcmp(key_str, "mode") == 0) {
key = KANSHI_OUTPUT_MODE;
} else if (strcmp(key_str, "position") == 0) {
key = KANSHI_OUTPUT_POSITION;
} else if (strcmp(key_str, "scale") == 0) {
key = KANSHI_OUTPUT_SCALE;
} else if (strcmp(key_str, "transform") == 0) {
key = KANSHI_OUTPUT_TRANSFORM;
} else {
fprintf(stderr,
"unknown directive '%s' in profile output '%s'\n",
key_str, output->name);
return NULL;
}
}
break;
case KANSHI_TOKEN_NEWLINE:
return output;
case KANSHI_TOKEN_COMMENT:
parser_ignore_line(parser);
return output;
default:
fprintf(stderr, "unexpected %s in output\n",
token_type_str(parser->tok_type));
return NULL;
}
}
}
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;
if (!parser_read_str(parser)) {
fprintf(stderr, "expected new profile, got %s\n",
token_type_str(parser->tok_type));
return NULL;
}
profile->name = (parser->tok_str_len == 0) ? NULL : strdup(parser->tok_str);
// Then parse the opening bracket
if (!parser_expect_token(parser, KANSHI_TOKEN_LBRACKET)) {
return NULL;
}
// Use the bracket position to generate a default profile name
if (profile->name == NULL) {
char generated_name[100];
int ret = snprintf(generated_name, sizeof(generated_name),
"<anonymous at line %d, col %d>", parser->line, parser->col);
if (ret >= 0) {
profile->name = strdup(generated_name);
} else {
profile->name = strdup("<anonymous>");
}
}
// Parse the profile commands until the closing bracket
while (1) {
if (!parser_next_token(parser)) {
return NULL;
}
switch (parser->tok_type) {
case KANSHI_TOKEN_RBRACKET:
return profile;
case KANSHI_TOKEN_STR:;
const char *directive = parser->tok_str;
if (strcmp(directive, "output") == 0) {
struct kanshi_profile_output *output =
parse_profile_output(parser);
if (output == NULL) {
return NULL;
}
// Store wildcard outputs at the end of the list
if (strcmp(output->name, "*") == 0) {
wl_list_insert(profile->outputs.prev, &output->link);
} 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);
return NULL;
}
break;
case KANSHI_TOKEN_NEWLINE:
break; // No-op
case KANSHI_TOKEN_COMMENT:
parser_ignore_line(parser);
break; // No-op
default:
fprintf(stderr, "unexpected %s in profile '%s'\n",
token_type_str(parser->tok_type), profile->name);
return NULL;
}
}
}
static struct kanshi_config *_parse_config(struct kanshi_parser *parser) {
struct kanshi_config *config = calloc(1, sizeof(*config));
wl_list_init(&config->profiles);
while (1) {
int ch = parser_peek_char(parser);
if (ch < 0) {
return NULL;
} else if (ch == 0) {
return config;
} else if (ch == commentSign) {
parser_ignore_line(parser);
continue;
} else if (isspace(ch)) {
parser_read_char(parser);
continue;
}
struct kanshi_profile *profile = parse_profile(parser);
if (!profile) {
return NULL;
}
// Inset at the end to preserve ordering
wl_list_insert(config->profiles.prev, &profile->link);
}
}
struct kanshi_config *kanshi_parse_config(const char *path) {
FILE *f = fopen(path, "r");
if (f == NULL) {
return NULL;
}
struct kanshi_parser parser = {
.f = f,
.next = -1,
.line = 1,
};
struct kanshi_config *config = _parse_config(&parser);
fclose(f);
if (config == NULL) {
fprintf(stderr, "failed to parse config file: "
"error on line %d, column %d\n", parser.line, parser.col);
return NULL;
}
return config;
}
static const char *transform_to_string(enum wl_output_transform transform) {
switch (transform) {
case WL_OUTPUT_TRANSFORM_NORMAL:
return "normal";
case WL_OUTPUT_TRANSFORM_90:
return "90";
case WL_OUTPUT_TRANSFORM_180:
return "180";
case WL_OUTPUT_TRANSFORM_270:
return "270";
case WL_OUTPUT_TRANSFORM_FLIPPED:
return "flipped";
case WL_OUTPUT_TRANSFORM_FLIPPED_90:
return "flipped-90";
case WL_OUTPUT_TRANSFORM_FLIPPED_180:
return "flipped-180";
case WL_OUTPUT_TRANSFORM_FLIPPED_270:
return "flipped-270";
}
return NULL;
}
void kanshi_save_config(const char *path, struct kanshi_config *config) {
FILE *f = fopen(path, "w");
if (f == NULL) {
fprintf(stderr, "fopen: %s: %s\n", path, strerror(errno));
return;
}
fprintf(f, "# DO NOT EDIT - file autogenerated by wdisplays\n\n");
struct kanshi_profile *profile;
wl_list_for_each(profile, &config->profiles, link) {
fprintf(f, "{\n");
struct kanshi_profile_output *profile_output;
wl_list_for_each(profile_output, &profile->outputs, link) {
fprintf(f, "\toutput \"%s\"", profile_output->name);
if (profile_output->fields & KANSHI_OUTPUT_ENABLED) {
fprintf(f, " enable");
if (profile_output->fields & KANSHI_OUTPUT_MODE) {
fprintf(f, " mode %dx%d@%0.3fHz",
profile_output->mode.width, profile_output->mode.height,
profile_output->mode.refresh / 1000.f);
}
if (profile_output->fields & KANSHI_OUTPUT_POSITION) {
fprintf(f, " position %d,%d",
profile_output->position.x, profile_output->position.y);
}
if (profile_output->fields & KANSHI_OUTPUT_SCALE) {
fprintf(f, " scale %f", profile_output->scale);
}
if (profile_output->fields & KANSHI_OUTPUT_TRANSFORM) {
fprintf(f, " transform %s",
transform_to_string(profile_output->transform));
}
} else {
fprintf(f, " disable");
}
fprintf(f, "\n");
}
fprintf(f, "}\n\n");
}
fclose(f);
}
void kanshi_destroy_config(struct kanshi_config *config) {
struct kanshi_profile *profile, *tmp_profile;
wl_list_for_each_safe(profile, tmp_profile, &config->profiles, link) {
struct kanshi_profile_output *output, *tmp_output;
wl_list_for_each_safe(output, tmp_output, &profile->outputs, link) {
free(output->name);
wl_list_remove(&output->link);
free(output);
}
struct kanshi_profile_command *command, *tmp_command;
wl_list_for_each_safe(command, tmp_command, &profile->commands, link) {
free(command->command);
wl_list_remove(&command->link);
free(command);
}
wl_list_remove(&profile->link);
if (profile->name != NULL) {
free(profile->name);
}
free(profile);
}
free(config);
}

View File

@ -28,8 +28,8 @@
* https://github.com/emersion/kanshi/blob/38d27474b686fcc8324cc5e454741a49577c0988/include/config.h * https://github.com/emersion/kanshi/blob/38d27474b686fcc8324cc5e454741a49577c0988/include/config.h
*/ */
#ifndef WDISPLAY_WDISPLAY_H #ifndef WDISPLAYS_WDISPLAYS_H
#define WDISPLAY_WDISPLAY_H #define WDISPLAYS_WDISPLAYS_H
#define HEADS_MAX 64 #define HEADS_MAX 64
#define HOVER_USECS (100 * 1000) #define HOVER_USECS (100 * 1000)
@ -250,7 +250,6 @@ struct wd_state {
struct wd_render_data render; struct wd_render_data render;
}; };
/* /*
* Creates the application state structure. * Creates the application state structure.
*/ */