mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-20 03:35:41 +02:00
commit
4dba602bfd
21 changed files with 1002 additions and 77 deletions
|
@ -12,6 +12,8 @@
|
||||||
(https://codeberg.org/dnkl/yambar/issues/10).
|
(https://codeberg.org/dnkl/yambar/issues/10).
|
||||||
* river: added documentation
|
* river: added documentation
|
||||||
(https://codeberg.org/dnkl/yambar/issues/9).
|
(https://codeberg.org/dnkl/yambar/issues/9).
|
||||||
|
* script: new module, adds support for custom user scripts
|
||||||
|
(https://codeberg.org/dnkl/yambar/issues/11).
|
||||||
|
|
||||||
|
|
||||||
### Deprecated
|
### Deprecated
|
||||||
|
@ -25,11 +27,14 @@
|
||||||
(https://codeberg.org/dnkl/yambar/issues/12).
|
(https://codeberg.org/dnkl/yambar/issues/12).
|
||||||
* mpd: fix compilation with clang
|
* mpd: fix compilation with clang
|
||||||
(https://codeberg.org/dnkl/yambar/issues/16).
|
(https://codeberg.org/dnkl/yambar/issues/16).
|
||||||
|
* Crash when the alpha component in a color value was 0.
|
||||||
|
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
### Contributors
|
### Contributors
|
||||||
|
|
||||||
|
* [JorwLNKwpH](https://codeberg.org/JorwLNKwpH)
|
||||||
|
|
||||||
|
|
||||||
## 1.5.0
|
## 1.5.0
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,8 @@ bar:
|
||||||
|
|
||||||
For details, see the man pages (**yambar**(5) is a good start).
|
For details, see the man pages (**yambar**(5) is a good start).
|
||||||
|
|
||||||
|
Example configurations can be found in [examples](examples/configurations).
|
||||||
|
|
||||||
|
|
||||||
## Modules
|
## Modules
|
||||||
|
|
||||||
|
@ -76,6 +78,9 @@ Available modules:
|
||||||
* mpd
|
* mpd
|
||||||
* network
|
* network
|
||||||
* removables
|
* removables
|
||||||
|
* river
|
||||||
|
* script (see script [examples](examples/scripts))
|
||||||
|
* sway-xkb
|
||||||
* xkb (_XCB backend only_)
|
* xkb (_XCB backend only_)
|
||||||
* xwindow (_XCB backend only_)
|
* xwindow (_XCB backend only_)
|
||||||
|
|
||||||
|
|
12
bar/bar.c
12
bar/bar.c
|
@ -36,19 +36,16 @@ calculate_widths(const struct private *b, int *left, int *center, int *right)
|
||||||
|
|
||||||
for (size_t i = 0; i < b->left.count; i++) {
|
for (size_t i = 0; i < b->left.count; i++) {
|
||||||
struct exposable *e = b->left.exps[i];
|
struct exposable *e = b->left.exps[i];
|
||||||
assert(e != NULL);
|
|
||||||
*left += b->left_spacing + e->width + b->right_spacing;
|
*left += b->left_spacing + e->width + b->right_spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < b->center.count; i++) {
|
for (size_t i = 0; i < b->center.count; i++) {
|
||||||
struct exposable *e = b->center.exps[i];
|
struct exposable *e = b->center.exps[i];
|
||||||
assert(e != NULL);
|
|
||||||
*center += b->left_spacing + e->width + b->right_spacing;
|
*center += b->left_spacing + e->width + b->right_spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < b->right.count; i++) {
|
for (size_t i = 0; i < b->right.count; i++) {
|
||||||
struct exposable *e = b->right.exps[i];
|
struct exposable *e = b->right.exps[i];
|
||||||
assert(e != NULL);
|
|
||||||
*right += b->left_spacing + e->width + b->right_spacing;
|
*right += b->left_spacing + e->width + b->right_spacing;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,30 +79,24 @@ expose(const struct bar *_bar)
|
||||||
for (size_t i = 0; i < bar->left.count; i++) {
|
for (size_t i = 0; i < bar->left.count; i++) {
|
||||||
struct module *m = bar->left.mods[i];
|
struct module *m = bar->left.mods[i];
|
||||||
struct exposable *e = bar->left.exps[i];
|
struct exposable *e = bar->left.exps[i];
|
||||||
|
|
||||||
if (e != NULL)
|
if (e != NULL)
|
||||||
e->destroy(e);
|
e->destroy(e);
|
||||||
|
|
||||||
bar->left.exps[i] = module_begin_expose(m);
|
bar->left.exps[i] = module_begin_expose(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < bar->center.count; i++) {
|
for (size_t i = 0; i < bar->center.count; i++) {
|
||||||
struct module *m = bar->center.mods[i];
|
struct module *m = bar->center.mods[i];
|
||||||
struct exposable *e = bar->center.exps[i];
|
struct exposable *e = bar->center.exps[i];
|
||||||
|
|
||||||
if (e != NULL)
|
if (e != NULL)
|
||||||
e->destroy(e);
|
e->destroy(e);
|
||||||
|
|
||||||
bar->center.exps[i] = module_begin_expose(m);
|
bar->center.exps[i] = module_begin_expose(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < bar->right.count; i++) {
|
for (size_t i = 0; i < bar->right.count; i++) {
|
||||||
struct module *m = bar->right.mods[i];
|
struct module *m = bar->right.mods[i];
|
||||||
struct exposable *e = bar->right.exps[i];
|
struct exposable *e = bar->right.exps[i];
|
||||||
|
|
||||||
if (e != NULL)
|
if (e != NULL)
|
||||||
e->destroy(e);
|
e->destroy(e);
|
||||||
|
|
||||||
bar->right.exps[i] = module_begin_expose(m);
|
bar->right.exps[i] = module_begin_expose(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -307,7 +298,6 @@ destroy(struct bar *bar)
|
||||||
for (size_t i = 0; i < b->left.count; i++) {
|
for (size_t i = 0; i < b->left.count; i++) {
|
||||||
struct module *m = b->left.mods[i];
|
struct module *m = b->left.mods[i];
|
||||||
struct exposable *e = b->left.exps[i];
|
struct exposable *e = b->left.exps[i];
|
||||||
|
|
||||||
if (e != NULL)
|
if (e != NULL)
|
||||||
e->destroy(e);
|
e->destroy(e);
|
||||||
m->destroy(m);
|
m->destroy(m);
|
||||||
|
@ -315,7 +305,6 @@ destroy(struct bar *bar)
|
||||||
for (size_t i = 0; i < b->center.count; i++) {
|
for (size_t i = 0; i < b->center.count; i++) {
|
||||||
struct module *m = b->center.mods[i];
|
struct module *m = b->center.mods[i];
|
||||||
struct exposable *e = b->center.exps[i];
|
struct exposable *e = b->center.exps[i];
|
||||||
|
|
||||||
if (e != NULL)
|
if (e != NULL)
|
||||||
e->destroy(e);
|
e->destroy(e);
|
||||||
m->destroy(m);
|
m->destroy(m);
|
||||||
|
@ -323,7 +312,6 @@ destroy(struct bar *bar)
|
||||||
for (size_t i = 0; i < b->right.count; i++) {
|
for (size_t i = 0; i < b->right.count; i++) {
|
||||||
struct module *m = b->right.mods[i];
|
struct module *m = b->right.mods[i];
|
||||||
struct exposable *e = b->right.exps[i];
|
struct exposable *e = b->right.exps[i];
|
||||||
|
|
||||||
if (e != NULL)
|
if (e != NULL)
|
||||||
e->destroy(e);
|
e->destroy(e);
|
||||||
m->destroy(m);
|
m->destroy(m);
|
||||||
|
|
|
@ -121,6 +121,8 @@ seat_destroy(struct seat *seat)
|
||||||
if (seat == NULL)
|
if (seat == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
free(seat->name);
|
||||||
|
|
||||||
if (seat->pointer.theme != NULL)
|
if (seat->pointer.theme != NULL)
|
||||||
wl_cursor_theme_destroy(seat->pointer.theme);
|
wl_cursor_theme_destroy(seat->pointer.theme);
|
||||||
if (seat->wl_pointer != NULL)
|
if (seat->wl_pointer != NULL)
|
||||||
|
@ -134,7 +136,9 @@ seat_destroy(struct seat *seat)
|
||||||
void *
|
void *
|
||||||
bar_backend_wayland_new(void)
|
bar_backend_wayland_new(void)
|
||||||
{
|
{
|
||||||
return calloc(1, sizeof(struct wayland_backend));
|
struct wayland_backend *backend = calloc(1, sizeof(struct wayland_backend));
|
||||||
|
backend->pipe_fds[0] = backend->pipe_fds[1] = -1;
|
||||||
|
return backend;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -947,6 +951,11 @@ cleanup(struct bar *_bar)
|
||||||
struct private *bar = _bar->private;
|
struct private *bar = _bar->private;
|
||||||
struct wayland_backend *backend = bar->backend.data;
|
struct wayland_backend *backend = bar->backend.data;
|
||||||
|
|
||||||
|
if (backend->pipe_fds[0] >= 0)
|
||||||
|
close(backend->pipe_fds[0]);
|
||||||
|
if (backend->pipe_fds[1] >= 0)
|
||||||
|
close(backend->pipe_fds[1]);
|
||||||
|
|
||||||
tll_foreach(backend->buffers, it) {
|
tll_foreach(backend->buffers, it) {
|
||||||
if (it->item.wl_buf != NULL)
|
if (it->item.wl_buf != NULL)
|
||||||
wl_buffer_destroy(it->item.wl_buf);
|
wl_buffer_destroy(it->item.wl_buf);
|
||||||
|
|
3
config.c
3
config.c
|
@ -53,6 +53,9 @@ conf_to_color(const struct yml_node *node)
|
||||||
uint16_t blue = hex_byte(&hex[4]);
|
uint16_t blue = hex_byte(&hex[4]);
|
||||||
uint16_t alpha = hex_byte(&hex[6]);
|
uint16_t alpha = hex_byte(&hex[6]);
|
||||||
|
|
||||||
|
if (alpha == 0)
|
||||||
|
return (pixman_color_t){0, 0, 0, 0};
|
||||||
|
|
||||||
alpha |= alpha << 8;
|
alpha |= alpha << 8;
|
||||||
int alpha_div = 0xffff / alpha;
|
int alpha_div = 0xffff / alpha;
|
||||||
|
|
||||||
|
|
|
@ -714,11 +714,107 @@ bar:
|
||||||
true:
|
true:
|
||||||
string:
|
string:
|
||||||
margin: 5
|
margin: 5
|
||||||
text: "{id}: {state}"```
|
text: "{id}: {state}"
|
||||||
|
```
|
||||||
|
|
||||||
|
# SCRIPT
|
||||||
|
|
||||||
|
This module executes a user-provided script (or binary!) that writes
|
||||||
|
tags on its stdout.
|
||||||
|
|
||||||
|
The script can either exit immediately after writing a set of tags, in
|
||||||
|
which case yambar will display those tags until yambar is
|
||||||
|
terminated. Or, the script can continue executing and update yambar
|
||||||
|
with new tag sets, either periodically, or when there is new data to
|
||||||
|
feed to yambar.
|
||||||
|
|
||||||
|
Tag sets, or _transactions_, are separated by an empty line. Each
|
||||||
|
_tag_ is a single line on the format:
|
||||||
|
|
||||||
|
```
|
||||||
|
name|type|value
|
||||||
|
```
|
||||||
|
|
||||||
|
Where _name_ is what you also use to refer to the tag in the yambar
|
||||||
|
configuration, _type_ is one of the tag types defined in
|
||||||
|
*yambar-tags*(5), and _value_ is the tag’s value.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
var1|string|hello
|
||||||
|
var2|int|13
|
||||||
|
|
||||||
|
var1|string|world
|
||||||
|
var2|int|37
|
||||||
|
```
|
||||||
|
|
||||||
|
The example above consists of two transactions. Each transaction has
|
||||||
|
two tags: one string tag and one integer tag. The second transaction
|
||||||
|
replaces the tags from the first transaction.
|
||||||
|
|
||||||
|
Supported _types_ are:
|
||||||
|
|
||||||
|
- string
|
||||||
|
- int
|
||||||
|
- bool
|
||||||
|
- float
|
||||||
|
- range:n-m (e.g. *var|range:0-100|57*)
|
||||||
|
|
||||||
|
## TAGS
|
||||||
|
|
||||||
|
User defined.
|
||||||
|
|
||||||
|
## CONFIGURATION
|
||||||
|
|
||||||
|
[[ *Name*
|
||||||
|
:[ *Type*
|
||||||
|
:[ *Req*
|
||||||
|
:[ *Description*
|
||||||
|
| path
|
||||||
|
: string
|
||||||
|
: yes
|
||||||
|
: Path to script/binary to execute. Must be an absolute path.
|
||||||
|
| args
|
||||||
|
: list of strings
|
||||||
|
: no
|
||||||
|
: Arguments to pass to the script/binary.
|
||||||
|
|
||||||
|
## EXAMPLES
|
||||||
|
|
||||||
|
Here is an "hello world" example script:
|
||||||
|
|
||||||
|
```
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
echo "test|string|hello"
|
||||||
|
echo ""
|
||||||
|
sleep 3
|
||||||
|
|
||||||
|
echo "test|string|world"
|
||||||
|
echo ""
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
```
|
||||||
|
|
||||||
|
This script will emit a single string tag, _test_, and alternate its
|
||||||
|
value between *hello* and *world* every three seconds.
|
||||||
|
|
||||||
|
A corresponding yambar configuration could look like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
bar:
|
||||||
|
left:
|
||||||
|
- script:
|
||||||
|
path: /path/to/script.sh
|
||||||
|
args: []
|
||||||
|
content: {string: {text: "{test}"}}
|
||||||
|
```
|
||||||
|
|
||||||
# SWAY-XKB
|
# SWAY-XKB
|
||||||
|
|
||||||
This module uses *Sway* extenions to the I3 IPC API to monitor input
|
This module uses *Sway* extensions to the I3 IPC API to monitor input
|
||||||
devices' active XKB layout. As such, it requires Sway to be running.
|
devices' active XKB layout. As such, it requires Sway to be running.
|
||||||
|
|
||||||
*Note* that the _content_ configuration option is a *template*;
|
*Note* that the _content_ configuration option is a *template*;
|
||||||
|
|
124
examples/scripts/cpu.sh
Executable file
124
examples/scripts/cpu.sh
Executable file
|
@ -0,0 +1,124 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# cpu.sh - measures CPU usage at a configurable sample interval
|
||||||
|
#
|
||||||
|
# Usage: cpu.sh INTERVAL_IN_SECONDS
|
||||||
|
#
|
||||||
|
# This script will emit the following tags on stdout (N is the number
|
||||||
|
# of logical CPUs):
|
||||||
|
#
|
||||||
|
# Name Type
|
||||||
|
# --------------------
|
||||||
|
# cpu range 0-100
|
||||||
|
# cpu0 range 0-100
|
||||||
|
# cpu1 range 0-100
|
||||||
|
# ...
|
||||||
|
# cpuN-1 range 0-100
|
||||||
|
#
|
||||||
|
# I.e. ‘cpu’ is the average (or aggregated) CPU usage, while cpuX is a
|
||||||
|
# specific CPU’s usage.
|
||||||
|
#
|
||||||
|
# Example configuration (update every second):
|
||||||
|
#
|
||||||
|
# - script:
|
||||||
|
# path: /path/to/cpu.sh
|
||||||
|
# args: [1]
|
||||||
|
# content: {string: {text: "{cpu}%"}}
|
||||||
|
#
|
||||||
|
|
||||||
|
interval=${1}
|
||||||
|
|
||||||
|
case ${interval} in
|
||||||
|
''|*[!0-9]*)
|
||||||
|
echo "interval must be an integer"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Get number of CPUs, by reading /proc/stat
|
||||||
|
# The output looks like:
|
||||||
|
#
|
||||||
|
# cpu A B C D ...
|
||||||
|
# cpu0 A B C D ...
|
||||||
|
# cpu1 A B C D ...
|
||||||
|
# cpuN A B C D ...
|
||||||
|
#
|
||||||
|
# The first line is a summary line, accounting *all* CPUs
|
||||||
|
IFS=$'\n' readarray -t all_cpu_stats < <(grep -e "^cpu" /proc/stat)
|
||||||
|
cpu_count=$((${#all_cpu_stats[@]} - 1))
|
||||||
|
|
||||||
|
# Arrays of ‘previous’ idle and total stats, needed to calculate the
|
||||||
|
# difference between each sample.
|
||||||
|
prev_idle=()
|
||||||
|
prev_total=()
|
||||||
|
for i in $(seq ${cpu_count}); do
|
||||||
|
prev_idle+=(0)
|
||||||
|
prev_total+=(0)
|
||||||
|
done
|
||||||
|
|
||||||
|
prev_average_idle=0
|
||||||
|
prev_average_total=0
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
IFS=$'\n' readarray -t all_cpu_stats < <(grep -e "^cpu" /proc/stat)
|
||||||
|
|
||||||
|
usage=() # CPU usage in percent, 0 <= x <= 100
|
||||||
|
|
||||||
|
average_idle=0 # All CPUs idle time since boot
|
||||||
|
average_total=0 # All CPUs total time since boot
|
||||||
|
|
||||||
|
for i in $(seq 0 $((cpu_count - 1))); do
|
||||||
|
# Split this CPUs stats into an array
|
||||||
|
stats=($(echo "${all_cpu_stats[$((i + 1))]}"))
|
||||||
|
|
||||||
|
# man procfs(5)
|
||||||
|
user=${stats[1]}
|
||||||
|
nice=${stats[2]}
|
||||||
|
system=${stats[3]}
|
||||||
|
idle=${stats[4]}
|
||||||
|
iowait=${stats[5]}
|
||||||
|
irq=${stats[6]}
|
||||||
|
softirq=${stats[7]}
|
||||||
|
steal=${stats[8]}
|
||||||
|
guest=${stats[9]}
|
||||||
|
guestnice=${stats[10]}
|
||||||
|
|
||||||
|
# Guest time already accounted for in user
|
||||||
|
user=$((user - guest))
|
||||||
|
nice=$((nice - guestnice))
|
||||||
|
|
||||||
|
idle=$((idle + iowait))
|
||||||
|
|
||||||
|
total=$((user + nice + system + irq + softirq + idle + steal + guest + guestnice))
|
||||||
|
|
||||||
|
average_idle=$((average_idle + idle))
|
||||||
|
average_total=$((average_total + total))
|
||||||
|
|
||||||
|
# Diff since last sample
|
||||||
|
diff_idle=$((idle - prev_idle[i]))
|
||||||
|
diff_total=$((total - prev_total[i]))
|
||||||
|
|
||||||
|
usage[i]=$((100 * (diff_total - diff_idle) / diff_total))
|
||||||
|
|
||||||
|
prev_idle[i]=${idle}
|
||||||
|
prev_total[i]=${total}
|
||||||
|
done
|
||||||
|
|
||||||
|
diff_average_idle=$((average_idle - prev_average_idle))
|
||||||
|
diff_average_total=$((average_total - prev_average_total))
|
||||||
|
|
||||||
|
average_usage=$((100 * (diff_average_total - diff_average_idle) / diff_average_total))
|
||||||
|
|
||||||
|
prev_average_idle=${average_idle}
|
||||||
|
prev_average_total=${average_total}
|
||||||
|
|
||||||
|
echo "cpu|range:0-100|${average_usage}"
|
||||||
|
for i in $(seq 0 $((cpu_count - 1))); do
|
||||||
|
echo "cpu${i}|range:0-100|${usage[i]}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
sleep "${interval}"
|
||||||
|
done
|
6
main.c
6
main.c
|
@ -326,7 +326,7 @@ main(int argc, char *const *argv)
|
||||||
thrd_t bar_thread;
|
thrd_t bar_thread;
|
||||||
thrd_create(&bar_thread, (int (*)(void *))bar->run, bar);
|
thrd_create(&bar_thread, (int (*)(void *))bar->run, bar);
|
||||||
|
|
||||||
/* Now unblock. We should be only thread receiving SIGINT */
|
/* Now unblock. We should be only thread receiving SIGINT/SIGTERM */
|
||||||
pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL);
|
pthread_sigmask(SIG_UNBLOCK, &signal_mask, NULL);
|
||||||
|
|
||||||
if (pid_file != NULL) {
|
if (pid_file != NULL) {
|
||||||
|
@ -336,8 +336,9 @@ main(int argc, char *const *argv)
|
||||||
|
|
||||||
while (!aborted) {
|
while (!aborted) {
|
||||||
struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}};
|
struct pollfd fds[] = {{.fd = abort_fd, .events = POLLIN}};
|
||||||
int r __attribute__((unused)) = poll(fds, 1, -1);
|
int r __attribute__((unused)) = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||||
|
|
||||||
|
if (fds[0].revents & (POLLIN | POLLHUP)) {
|
||||||
/*
|
/*
|
||||||
* Either the bar aborted (triggering the abort_fd), or user
|
* Either the bar aborted (triggering the abort_fd), or user
|
||||||
* killed us (triggering the signal handler which sets
|
* killed us (triggering the signal handler which sets
|
||||||
|
@ -346,6 +347,7 @@ main(int argc, char *const *argv)
|
||||||
assert(aborted || r == 1);
|
assert(aborted || r == 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (aborted)
|
if (aborted)
|
||||||
LOG_INFO("aborted: %s (%d)", strsignal(aborted), aborted);
|
LOG_INFO("aborted: %s (%d)", strsignal(aborted), aborted);
|
||||||
|
|
|
@ -291,7 +291,10 @@ update_status(struct module *mod, int capacity_fd, int energy_fd, int power_fd,
|
||||||
const char *status = readline_from_fd(status_fd);
|
const char *status = readline_from_fd(status_fd);
|
||||||
enum state state;
|
enum state state;
|
||||||
|
|
||||||
if (strcmp(status, "Full") == 0)
|
if (status == NULL) {
|
||||||
|
LOG_WARN("failed to read battery state");
|
||||||
|
state = STATE_DISCHARGING;
|
||||||
|
} else if (strcmp(status, "Full") == 0)
|
||||||
state = STATE_FULL;
|
state = STATE_FULL;
|
||||||
else if (strcmp(status, "Charging") == 0)
|
else if (strcmp(status, "Charging") == 0)
|
||||||
state = STATE_CHARGING;
|
state = STATE_CHARGING;
|
||||||
|
|
|
@ -9,7 +9,7 @@ mpd = dependency('libmpdclient')
|
||||||
xcb_xkb = dependency('xcb-xkb', required: get_option('backend-x11'))
|
xcb_xkb = dependency('xcb-xkb', required: get_option('backend-x11'))
|
||||||
|
|
||||||
# Module name -> (source-list, dep-list)
|
# Module name -> (source-list, dep-list)
|
||||||
deps = {
|
mod_data = {
|
||||||
'alsa': [[], [m, alsa]],
|
'alsa': [[], [m, alsa]],
|
||||||
'backlight': [[], [m, udev]],
|
'backlight': [[], [m, udev]],
|
||||||
'battery': [[], [udev]],
|
'battery': [[], [udev]],
|
||||||
|
@ -19,11 +19,12 @@ deps = {
|
||||||
'mpd': [[], [mpd]],
|
'mpd': [[], [mpd]],
|
||||||
'network': [[], []],
|
'network': [[], []],
|
||||||
'removables': [[], [dynlist, udev]],
|
'removables': [[], [dynlist, udev]],
|
||||||
'sway_xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
|
'script': [[], []],
|
||||||
|
'sway-xkb': [['i3-common.c', 'i3-common.h'], [dynlist, json]],
|
||||||
}
|
}
|
||||||
|
|
||||||
if backend_x11
|
if backend_x11
|
||||||
deps += {
|
mod_data += {
|
||||||
'xkb': [[], [xcb_stuff, xcb_xkb]],
|
'xkb': [[], [xcb_stuff, xcb_xkb]],
|
||||||
'xwindow': [[], [xcb_stuff]],
|
'xwindow': [[], [xcb_stuff]],
|
||||||
}
|
}
|
||||||
|
@ -48,25 +49,25 @@ if backend_wayland
|
||||||
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
|
command: [wscanner_prog, 'private-code', '@INPUT@', '@OUTPUT@'])
|
||||||
endforeach
|
endforeach
|
||||||
|
|
||||||
deps += {
|
mod_data += {
|
||||||
'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], []],
|
'river': [[wl_proto_src + wl_proto_headers + river_proto_src + river_proto_headers], [dynlist]],
|
||||||
}
|
}
|
||||||
endif
|
endif
|
||||||
|
|
||||||
foreach mod, data : deps
|
foreach mod, data : mod_data
|
||||||
sources = data[0]
|
sources = data[0]
|
||||||
dep = data[1]
|
deps = data[1]
|
||||||
|
|
||||||
if plugs_as_libs
|
if plugs_as_libs
|
||||||
shared_module(mod, '@0@.c'.format(mod), sources,
|
shared_module(mod, '@0@.c'.format(mod), sources,
|
||||||
dependencies: [module_sdk] + dep,
|
dependencies: [module_sdk] + deps,
|
||||||
name_prefix: 'module_',
|
name_prefix: 'module_',
|
||||||
install: true,
|
install: true,
|
||||||
install_dir: join_paths(get_option('libdir'), 'yambar'))
|
install_dir: join_paths(get_option('libdir'), 'yambar'))
|
||||||
else
|
else
|
||||||
modules += [declare_dependency(
|
modules += [declare_dependency(
|
||||||
sources: ['@0@.c'.format(mod)] + sources,
|
sources: ['@0@.c'.format(mod)] + sources,
|
||||||
dependencies: [module_sdk] + dep,
|
dependencies: [module_sdk] + deps,
|
||||||
compile_args: '-DHAVE_PLUGIN_@0@'.format(mod))]
|
compile_args: '-DHAVE_PLUGIN_@0@'.format(mod.underscorify()))]
|
||||||
endif
|
endif
|
||||||
endforeach
|
endforeach
|
||||||
|
|
|
@ -90,7 +90,8 @@ content(struct module *mod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t seat_count = m->title != NULL ? tll_length(m->seats) : 0;
|
const size_t seat_count = m->title != NULL && !m->is_starting_up
|
||||||
|
? tll_length(m->seats) : 0;
|
||||||
struct exposable *tag_parts[32 + seat_count];
|
struct exposable *tag_parts[32 + seat_count];
|
||||||
|
|
||||||
for (unsigned i = 0; i < 32; i++) {
|
for (unsigned i = 0; i < 32; i++) {
|
||||||
|
@ -122,7 +123,7 @@ content(struct module *mod)
|
||||||
tag_set_destroy(&tags);
|
tag_set_destroy(&tags);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m->title != NULL) {
|
if (m->title != NULL && !m->is_starting_up) {
|
||||||
size_t i = 32;
|
size_t i = 32;
|
||||||
tll_foreach(m->seats, it) {
|
tll_foreach(m->seats, it) {
|
||||||
const struct seat *seat = &it->item;
|
const struct seat *seat = &it->item;
|
||||||
|
@ -565,6 +566,10 @@ run(struct module *mod)
|
||||||
}
|
}
|
||||||
|
|
||||||
wl_display_roundtrip(display);
|
wl_display_roundtrip(display);
|
||||||
|
|
||||||
|
bool unlock_at_exit = true;
|
||||||
|
mtx_lock(&mod->lock);
|
||||||
|
|
||||||
m->is_starting_up = false;
|
m->is_starting_up = false;
|
||||||
|
|
||||||
tll_foreach(m->outputs, it)
|
tll_foreach(m->outputs, it)
|
||||||
|
@ -572,6 +577,9 @@ run(struct module *mod)
|
||||||
tll_foreach(m->seats, it)
|
tll_foreach(m->seats, it)
|
||||||
instantiate_seat(&it->item);
|
instantiate_seat(&it->item);
|
||||||
|
|
||||||
|
unlock_at_exit = false;
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
wl_display_flush(display);
|
wl_display_flush(display);
|
||||||
|
|
||||||
|
@ -618,6 +626,9 @@ out:
|
||||||
wl_registry_destroy(registry);
|
wl_registry_destroy(registry);
|
||||||
if (display != NULL)
|
if (display != NULL)
|
||||||
wl_display_disconnect(display);
|
wl_display_disconnect(display);
|
||||||
|
|
||||||
|
if (unlock_at_exit)
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
639
modules/script.c
Normal file
639
modules/script.c
Normal file
|
@ -0,0 +1,639 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <assert.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <sys/timerfd.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "script"
|
||||||
|
#define LOG_ENABLE_DBG 0
|
||||||
|
#include "../log.h"
|
||||||
|
#include "../config.h"
|
||||||
|
#include "../config-verify.h"
|
||||||
|
#include "../module.h"
|
||||||
|
#include "../plugin.h"
|
||||||
|
|
||||||
|
struct private {
|
||||||
|
char *path;
|
||||||
|
size_t argc;
|
||||||
|
char **argv;
|
||||||
|
|
||||||
|
struct particle *content;
|
||||||
|
|
||||||
|
struct tag_set tags;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
char *data;
|
||||||
|
size_t sz;
|
||||||
|
size_t idx;
|
||||||
|
} recv_buf;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
destroy(struct module *mod)
|
||||||
|
{
|
||||||
|
struct private *m = mod->private;
|
||||||
|
m->content->destroy(m->content);
|
||||||
|
|
||||||
|
struct tag **tag_array = m->tags.tags;
|
||||||
|
tag_set_destroy(&m->tags);
|
||||||
|
free(tag_array);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < m->argc; i++)
|
||||||
|
free(m->argv[i]);
|
||||||
|
free(m->argv);
|
||||||
|
free(m->recv_buf.data);
|
||||||
|
free(m->path);
|
||||||
|
free(m);
|
||||||
|
module_default_destroy(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct exposable *
|
||||||
|
content(struct module *mod)
|
||||||
|
{
|
||||||
|
const struct private *m = mod->private;
|
||||||
|
|
||||||
|
mtx_lock(&mod->lock);
|
||||||
|
struct exposable *e = m->content->instantiate(m->content, &m->tags);
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
|
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct tag *
|
||||||
|
process_line(struct module *mod, const char *line, size_t len)
|
||||||
|
{
|
||||||
|
char *name = NULL;
|
||||||
|
char *value = NULL;
|
||||||
|
|
||||||
|
const char *_name = line;
|
||||||
|
|
||||||
|
const char *type = memchr(line, '|', len);
|
||||||
|
if (type == NULL)
|
||||||
|
goto bad_tag;
|
||||||
|
|
||||||
|
size_t name_len = type - _name;
|
||||||
|
type++;
|
||||||
|
|
||||||
|
const char *_value = memchr(type, '|', len - name_len - 1);
|
||||||
|
if (_value == NULL)
|
||||||
|
goto bad_tag;
|
||||||
|
|
||||||
|
size_t type_len = _value - type;
|
||||||
|
_value++;
|
||||||
|
|
||||||
|
size_t value_len = line + len - _value;
|
||||||
|
|
||||||
|
LOG_DBG("%.*s: name=\"%.*s\", type=\"%.*s\", value=\"%.*s\"",
|
||||||
|
(int)len, line,
|
||||||
|
(int)name_len, _name, (int)type_len, type, (int)value_len, _value);
|
||||||
|
|
||||||
|
name = malloc(name_len + 1);
|
||||||
|
memcpy(name, _name, name_len);
|
||||||
|
name[name_len] = '\0';
|
||||||
|
|
||||||
|
value = malloc(value_len + 1);
|
||||||
|
memcpy(value, _value, value_len);
|
||||||
|
value[value_len] = '\0';
|
||||||
|
|
||||||
|
struct tag *tag = NULL;
|
||||||
|
|
||||||
|
if (type_len == 6 && memcmp(type, "string", 6) == 0)
|
||||||
|
tag = tag_new_string(mod, name, value);
|
||||||
|
|
||||||
|
else if (type_len == 3 && memcmp(type, "int", 3) == 0) {
|
||||||
|
errno = 0;
|
||||||
|
char *end;
|
||||||
|
long v = strtol(value, &end, 0);
|
||||||
|
|
||||||
|
if (errno != 0 || *end != '\0') {
|
||||||
|
LOG_ERR("tag value is not an integer: %s", value);
|
||||||
|
goto bad_tag;
|
||||||
|
}
|
||||||
|
tag = tag_new_int(mod, name, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (type_len == 4 && memcmp(type, "bool", 4) == 0) {
|
||||||
|
bool v;
|
||||||
|
if (strcmp(value, "true") == 0)
|
||||||
|
v = true;
|
||||||
|
else if (strcmp(value, "false") == 0)
|
||||||
|
v = false;
|
||||||
|
else {
|
||||||
|
LOG_ERR("tag value is not a boolean: %s", value);
|
||||||
|
goto bad_tag;
|
||||||
|
}
|
||||||
|
tag = tag_new_bool(mod, name, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (type_len == 5 && memcmp(type, "float", 5) == 0) {
|
||||||
|
errno = 0;
|
||||||
|
char *end;
|
||||||
|
double v = strtod(value, &end);
|
||||||
|
|
||||||
|
if (errno != 0 || *end != '\0') {
|
||||||
|
LOG_ERR("tag value is not a float: %s", value);
|
||||||
|
goto bad_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
tag = tag_new_float(mod, name, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if ((type_len > 6 && memcmp(type, "range:", 6) == 0) ||
|
||||||
|
(type_len > 9 && memcmp(type, "realtime:", 9 == 0)))
|
||||||
|
{
|
||||||
|
const char *_start = type + 6;
|
||||||
|
const char *split = memchr(_start, '-', type_len - 6);
|
||||||
|
|
||||||
|
if (split == NULL || split == _start || (split + 1) - type >= type_len) {
|
||||||
|
LOG_ERR(
|
||||||
|
"tag range delimiter ('-') not found in type: %.*s",
|
||||||
|
(int)type_len, type);
|
||||||
|
goto bad_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *_end = split + 1;
|
||||||
|
|
||||||
|
size_t start_len = split - _start;
|
||||||
|
size_t end_len = type + type_len - _end;
|
||||||
|
|
||||||
|
long start = 0;
|
||||||
|
for (size_t i = 0; i < start_len; i++) {
|
||||||
|
if (!(_start[i] >= '0' && _start[i] <= '9')) {
|
||||||
|
LOG_ERR(
|
||||||
|
"tag range start is not an integer: %.*s",
|
||||||
|
(int)start_len, _start);
|
||||||
|
goto bad_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
start *= 10;
|
||||||
|
start += _start[i] - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
long end = 0;
|
||||||
|
for (size_t i = 0; i < end_len; i++) {
|
||||||
|
if (!(_end[i] >= '0' && _end[i] < '9')) {
|
||||||
|
LOG_ERR(
|
||||||
|
"tag range end is not an integer: %.*s",
|
||||||
|
(int)end_len, _end);
|
||||||
|
goto bad_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
end *= 10;
|
||||||
|
end += _end[i] - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type_len > 9 && memcmp(type, "realtime:", 9) == 0) {
|
||||||
|
LOG_ERR("unimplemented: realtime tag");
|
||||||
|
goto bad_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
char *vend;
|
||||||
|
long v = strtol(value, &vend, 0);
|
||||||
|
if (errno != 0 || *vend != '\0') {
|
||||||
|
LOG_ERR("tag value is not an integer: %s", value);
|
||||||
|
goto bad_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (v < start || v > end) {
|
||||||
|
LOG_ERR("tag value is outside range: %ld <= %ld <= %ld",
|
||||||
|
start, v, end);
|
||||||
|
goto bad_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
tag = tag_new_int_range(mod, name, v, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
else {
|
||||||
|
goto bad_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(name);
|
||||||
|
free(value);
|
||||||
|
return tag;
|
||||||
|
|
||||||
|
bad_tag:
|
||||||
|
LOG_ERR("invalid tag: %.*s", (int)len, line);
|
||||||
|
free(name);
|
||||||
|
free(value);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
process_transaction(struct module *mod, size_t size)
|
||||||
|
{
|
||||||
|
struct private *m = mod->private;
|
||||||
|
mtx_lock(&mod->lock);
|
||||||
|
|
||||||
|
size_t left = size;
|
||||||
|
const char *line = m->recv_buf.data;
|
||||||
|
|
||||||
|
size_t line_count = 0;
|
||||||
|
{
|
||||||
|
const char *p = line;
|
||||||
|
while ((p = memchr(p, '\n', size - (p - line))) != NULL) {
|
||||||
|
p++;
|
||||||
|
line_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct tag **old_tag_array = m->tags.tags;
|
||||||
|
tag_set_destroy(&m->tags);
|
||||||
|
free(old_tag_array);
|
||||||
|
|
||||||
|
m->tags.tags = calloc(line_count, sizeof(m->tags.tags[0]));
|
||||||
|
m->tags.count = line_count;
|
||||||
|
|
||||||
|
size_t idx = 0;
|
||||||
|
|
||||||
|
while (left > 0) {
|
||||||
|
char *line_end = memchr(line, '\n', left);
|
||||||
|
assert(line_end != NULL);
|
||||||
|
|
||||||
|
size_t line_len = line_end - line;
|
||||||
|
|
||||||
|
struct tag *tag = process_line(mod, line, line_len);
|
||||||
|
if (tag != NULL)
|
||||||
|
m->tags.tags[idx++] = tag;
|
||||||
|
|
||||||
|
left -= line_len + 1;
|
||||||
|
line += line_len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
m->tags.count = idx;
|
||||||
|
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
|
mod->bar->refresh(mod->bar);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
data_received(struct module *mod, const char *data, size_t len)
|
||||||
|
{
|
||||||
|
struct private *m = mod->private;
|
||||||
|
|
||||||
|
if (len > m->recv_buf.sz - m->recv_buf.idx) {
|
||||||
|
size_t new_sz = m->recv_buf.sz == 0 ? 1024 : m->recv_buf.sz * 2;
|
||||||
|
char *new_buf = realloc(m->recv_buf.data, new_sz);
|
||||||
|
|
||||||
|
if (new_buf == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m->recv_buf.data = new_buf;
|
||||||
|
m->recv_buf.sz = new_sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(m->recv_buf.sz >= m->recv_buf.idx);
|
||||||
|
assert(m->recv_buf.sz - m->recv_buf.idx >= len);
|
||||||
|
|
||||||
|
memcpy(&m->recv_buf.data[m->recv_buf.idx], data, len);
|
||||||
|
m->recv_buf.idx += len;
|
||||||
|
|
||||||
|
const char *eot = memmem(m->recv_buf.data, m->recv_buf.idx, "\n\n", 2);
|
||||||
|
if (eot == NULL) {
|
||||||
|
/* End of transaction not yet available */
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t transaction_size = eot - m->recv_buf.data + 1;
|
||||||
|
process_transaction(mod, transaction_size);
|
||||||
|
|
||||||
|
assert(m->recv_buf.idx >= transaction_size + 1);
|
||||||
|
memmove(m->recv_buf.data,
|
||||||
|
&m->recv_buf.data[transaction_size + 1],
|
||||||
|
m->recv_buf.idx - (transaction_size + 1));
|
||||||
|
m->recv_buf.idx -= transaction_size + 1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_loop(struct module *mod, pid_t pid, int comm_fd)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
struct pollfd fds[] = {
|
||||||
|
{.fd = mod->abort_fd, .events = POLLIN},
|
||||||
|
{.fd = comm_fd, .events = POLLIN},
|
||||||
|
};
|
||||||
|
|
||||||
|
int r = poll(fds, sizeof(fds) / sizeof(fds[0]), -1);
|
||||||
|
if (r < 0) {
|
||||||
|
if (errno == EINTR)
|
||||||
|
continue;
|
||||||
|
LOG_ERRNO("failed to poll");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds[1].revents & POLLIN) {
|
||||||
|
char data[4096];
|
||||||
|
ssize_t amount = read(comm_fd, data, sizeof(data));
|
||||||
|
if (amount < 0) {
|
||||||
|
LOG_ERRNO("failed to read from script");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_DBG("recv: \"%.*s\"", (int)amount, data);
|
||||||
|
|
||||||
|
data_received(mod, data, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds[0].revents & POLLHUP) {
|
||||||
|
/* Aborted */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds[1].revents & POLLHUP) {
|
||||||
|
/* Child's stdout closed */
|
||||||
|
LOG_DBG("script pipe closed (script terminated?)");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds[0].revents & POLLIN) {
|
||||||
|
/* Aborted */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
run(struct module *mod)
|
||||||
|
{
|
||||||
|
struct private *m = mod->private;
|
||||||
|
|
||||||
|
/* Pipe to detect exec() failures */
|
||||||
|
int exec_pipe[2];
|
||||||
|
if (pipe2(exec_pipe, O_CLOEXEC) < 0) {
|
||||||
|
LOG_ERRNO("failed to create pipe");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stdout redirection pipe */
|
||||||
|
int comm_pipe[2];
|
||||||
|
if (pipe(comm_pipe) < 0) {
|
||||||
|
LOG_ERRNO("failed to create stdin/stdout redirection pipe");
|
||||||
|
close(exec_pipe[0]);
|
||||||
|
close(exec_pipe[1]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int pid = fork();
|
||||||
|
if (pid < 0) {
|
||||||
|
LOG_ERRNO("failed to fork");
|
||||||
|
close(comm_pipe[0]);
|
||||||
|
close(comm_pipe[1]);
|
||||||
|
close(exec_pipe[0]);
|
||||||
|
close(exec_pipe[1]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pid == 0) {
|
||||||
|
/* Child */
|
||||||
|
|
||||||
|
/* Construct argv for execvp() */
|
||||||
|
char *argv[1 + m->argc + 1];
|
||||||
|
argv[0] = m->path;
|
||||||
|
for (size_t i = 0; i < m->argc; i++)
|
||||||
|
argv[i + 1] = m->argv[i];
|
||||||
|
argv[1 + m->argc] = NULL;
|
||||||
|
|
||||||
|
/* Restore signal handlers and signal mask */
|
||||||
|
sigset_t mask;
|
||||||
|
sigemptyset(&mask);
|
||||||
|
|
||||||
|
const struct sigaction sa = {.sa_handler = SIG_DFL};
|
||||||
|
if (sigaction(SIGINT, &sa, NULL) < 0 ||
|
||||||
|
sigaction(SIGTERM, &sa, NULL) < 0 ||
|
||||||
|
sigaction(SIGCHLD, &sa, NULL) < 0 ||
|
||||||
|
sigprocmask(SIG_SETMASK, &mask, NULL) < 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* New process group, so that we can use killpg() */
|
||||||
|
setpgid(0, 0);
|
||||||
|
|
||||||
|
/* Close pipe read ends */
|
||||||
|
close(exec_pipe[0]);
|
||||||
|
close(comm_pipe[0]);
|
||||||
|
|
||||||
|
/* Re-direct stdin/stdout */
|
||||||
|
int dev_null = open("/dev/null", O_RDONLY);
|
||||||
|
if (dev_null < 0)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
if (dup2(dev_null, STDIN_FILENO) < 0 ||
|
||||||
|
dup2(comm_pipe[1], STDOUT_FILENO) < 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We're done with the redirection pipe */
|
||||||
|
close(comm_pipe[1]);
|
||||||
|
comm_pipe[1] = -1;
|
||||||
|
|
||||||
|
/* Close *all* other FDs */
|
||||||
|
for (int i = STDERR_FILENO + 1; i < 65536; i++) {
|
||||||
|
if (i == exec_pipe[1]) {
|
||||||
|
/* Needed for error reporting. Automatically closed
|
||||||
|
* when execvp() succeeds */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
close(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
execvp(m->path, argv);
|
||||||
|
|
||||||
|
fail:
|
||||||
|
(void)!write(exec_pipe[1], &errno, sizeof(errno));
|
||||||
|
close(exec_pipe[1]);
|
||||||
|
if (comm_pipe[1] >= 0)
|
||||||
|
close(comm_pipe[1]);
|
||||||
|
_exit(errno);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Close pipe write ends */
|
||||||
|
close(exec_pipe[1]);
|
||||||
|
close(comm_pipe[1]);
|
||||||
|
|
||||||
|
int _errno;
|
||||||
|
static_assert(sizeof(_errno) == sizeof(errno), "errno size mismatch");
|
||||||
|
|
||||||
|
/* Wait for errno from child, or FD being closed in execvp() */
|
||||||
|
int r = read(exec_pipe[0], &_errno, sizeof(_errno));
|
||||||
|
close(exec_pipe[0]);
|
||||||
|
|
||||||
|
if (r < 0) {
|
||||||
|
LOG_ERRNO("failed to read from pipe");
|
||||||
|
close(comm_pipe[0]);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r > 0) {
|
||||||
|
LOG_ERRNO_P("%s: failed to start", _errno, m->path);
|
||||||
|
close(comm_pipe[0]);
|
||||||
|
waitpid(pid, NULL, 0);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Pipe was closed. I.e. execvp() succeeded */
|
||||||
|
assert(r == 0);
|
||||||
|
LOG_DBG("script running under PID=%u", pid);
|
||||||
|
|
||||||
|
int ret = run_loop(mod, pid, comm_pipe[0]);
|
||||||
|
close(comm_pipe[0]);
|
||||||
|
|
||||||
|
if (waitpid(pid, NULL, WNOHANG) == 0) {
|
||||||
|
static const struct {
|
||||||
|
int signo;
|
||||||
|
int timeout;
|
||||||
|
const char *name;
|
||||||
|
} sig_info[] = {
|
||||||
|
{SIGINT, 2, "SIGINT"},
|
||||||
|
{SIGTERM, 5, "SIGTERM"},
|
||||||
|
{SIGKILL, 0, "SIGKILL"},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sizeof(sig_info) / sizeof(sig_info[0]); i++) {
|
||||||
|
struct timeval start;
|
||||||
|
gettimeofday(&start, NULL);
|
||||||
|
|
||||||
|
const int signo = sig_info[i].signo;
|
||||||
|
const int timeout = sig_info[i].timeout;
|
||||||
|
const char *const name __attribute__((unused)) = sig_info[i].name;
|
||||||
|
|
||||||
|
LOG_DBG("sending %s to PID=%u (timeout=%ds)", name, pid, timeout);
|
||||||
|
killpg(pid, signo);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Child is unlikely to terminate *immediately*. Wait a
|
||||||
|
* *short* period of time before checking waitpid() the
|
||||||
|
* first time
|
||||||
|
*/
|
||||||
|
usleep(10000);
|
||||||
|
|
||||||
|
pid_t waited_pid;
|
||||||
|
while ((waited_pid = waitpid(
|
||||||
|
pid, NULL, timeout > 0 ? WNOHANG : 0)) == 0)
|
||||||
|
{
|
||||||
|
struct timeval now;
|
||||||
|
gettimeofday(&now, NULL);
|
||||||
|
|
||||||
|
struct timeval elapsed;
|
||||||
|
timersub(&now, &start, &elapsed);
|
||||||
|
|
||||||
|
if (elapsed.tv_sec >= timeout)
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* Don't spinning */
|
||||||
|
thrd_yield();
|
||||||
|
usleep(100000); /* 100ms */
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waited_pid == pid) {
|
||||||
|
/* Child finally dead */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
LOG_DBG("PID=%u already terminated", pid);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct module *
|
||||||
|
script_new(const char *path, size_t argc, const char *const argv[static argc],
|
||||||
|
struct particle *_content)
|
||||||
|
{
|
||||||
|
struct private *m = calloc(1, sizeof(*m));
|
||||||
|
m->path = strdup(path);
|
||||||
|
m->content = _content;
|
||||||
|
m->argc = argc;
|
||||||
|
m->argv = malloc(argc * sizeof(m->argv[0]));
|
||||||
|
for (size_t i = 0; i < argc; i++)
|
||||||
|
m->argv[i] = strdup(argv[i]);
|
||||||
|
|
||||||
|
struct module *mod = module_common_new();
|
||||||
|
mod->private = m;
|
||||||
|
mod->run = &run;
|
||||||
|
mod->destroy = &destroy;
|
||||||
|
mod->content = &content;
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct module *
|
||||||
|
from_conf(const struct yml_node *node, struct conf_inherit inherited)
|
||||||
|
{
|
||||||
|
const struct yml_node *path = yml_get_value(node, "path");
|
||||||
|
const struct yml_node *args = yml_get_value(node, "args");
|
||||||
|
const struct yml_node *c = yml_get_value(node, "content");
|
||||||
|
|
||||||
|
size_t argc = args != NULL ? yml_list_length(args) : 0;
|
||||||
|
const char *argv[argc];
|
||||||
|
|
||||||
|
if (args != NULL) {
|
||||||
|
size_t i = 0;
|
||||||
|
for (struct yml_list_iter iter = yml_list_iter(args);
|
||||||
|
iter.node != NULL;
|
||||||
|
yml_list_next(&iter), i++)
|
||||||
|
{
|
||||||
|
argv[i] = yml_value_as_string(iter.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return script_new(
|
||||||
|
yml_value_as_string(path), argc, argv, conf_to_particle(c, inherited));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
conf_verify_path(keychain_t *chain, const struct yml_node *node)
|
||||||
|
{
|
||||||
|
if (!conf_verify_string(chain, node))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const char *path = yml_value_as_string(node);
|
||||||
|
if (strlen(path) < 1 || path[0] != '/') {
|
||||||
|
LOG_ERR("%s: path must be absolute", conf_err_prefix(chain, node));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
conf_verify_args(keychain_t *chain, const struct yml_node *node)
|
||||||
|
{
|
||||||
|
return conf_verify_list(chain, node, &conf_verify_string);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool
|
||||||
|
verify_conf(keychain_t *chain, const struct yml_node *node)
|
||||||
|
{
|
||||||
|
static const struct attr_info attrs[] = {
|
||||||
|
{"path", true, &conf_verify_path},
|
||||||
|
{"args", false, &conf_verify_args},
|
||||||
|
MODULE_COMMON_ATTRS,
|
||||||
|
};
|
||||||
|
|
||||||
|
return conf_verify_dict(chain, node, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const struct module_iface module_script_iface = {
|
||||||
|
.verify_conf = &verify_conf,
|
||||||
|
.from_conf = &from_conf,
|
||||||
|
};
|
||||||
|
|
||||||
|
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
|
||||||
|
extern const struct module_iface iface __attribute__((weak, alias("module_script_iface")));
|
||||||
|
#endif
|
26
particle.c
26
particle.c
|
@ -219,14 +219,25 @@ exposable_default_on_mouse(struct exposable *exposable, struct bar *bar,
|
||||||
|
|
||||||
LOG_DBG("executing on-click handler: %s", cmd);
|
LOG_DBG("executing on-click handler: %s", cmd);
|
||||||
|
|
||||||
|
sigset_t mask;
|
||||||
|
sigemptyset(&mask);
|
||||||
|
|
||||||
|
const struct sigaction sa = {.sa_handler = SIG_DFL};
|
||||||
|
if (sigaction(SIGINT, &sa, NULL) < 0 ||
|
||||||
|
sigaction(SIGTERM, &sa, NULL) < 0 ||
|
||||||
|
sigaction(SIGCHLD, &sa, NULL) < 0 ||
|
||||||
|
sigprocmask(SIG_SETMASK, &mask, NULL) < 0)
|
||||||
|
{
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
/* Redirect stdin/stdout/stderr to /dev/null */
|
/* Redirect stdin/stdout/stderr to /dev/null */
|
||||||
int dev_null_r = open("/dev/null", O_RDONLY | O_CLOEXEC);
|
int dev_null_r = open("/dev/null", O_RDONLY | O_CLOEXEC);
|
||||||
int dev_null_w = open("/dev/null", O_WRONLY | O_CLOEXEC);
|
int dev_null_w = open("/dev/null", O_WRONLY | O_CLOEXEC);
|
||||||
|
|
||||||
if (dev_null_r == -1 || dev_null_w == -1) {
|
if (dev_null_r == -1 || dev_null_w == -1) {
|
||||||
LOG_ERRNO("/dev/null: failed to open");
|
LOG_ERRNO("/dev/null: failed to open");
|
||||||
(void)!write(pipe_fds[1], &errno, sizeof(errno));
|
goto fail;
|
||||||
_exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dup2(dev_null_r, STDIN_FILENO) == -1 ||
|
if (dup2(dev_null_r, STDIN_FILENO) == -1 ||
|
||||||
|
@ -234,15 +245,20 @@ exposable_default_on_mouse(struct exposable *exposable, struct bar *bar,
|
||||||
dup2(dev_null_w, STDERR_FILENO) == -1)
|
dup2(dev_null_w, STDERR_FILENO) == -1)
|
||||||
{
|
{
|
||||||
LOG_ERRNO("failed to redirect stdin/stdout/stderr");
|
LOG_ERRNO("failed to redirect stdin/stdout/stderr");
|
||||||
(void)!write(pipe_fds[1], &errno, sizeof(errno));
|
goto fail;
|
||||||
_exit(1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Close *all* other FDs (e.g. script modules' FDs) */
|
||||||
|
for (int i = STDERR_FILENO + 1; i < 65536; i++)
|
||||||
|
close(i);
|
||||||
|
|
||||||
execvp(argv[0], argv);
|
execvp(argv[0], argv);
|
||||||
|
|
||||||
|
fail:
|
||||||
/* Signal failure to parent process */
|
/* Signal failure to parent process */
|
||||||
(void)!write(pipe_fds[1], &errno, sizeof(errno));
|
(void)!write(pipe_fds[1], &errno, sizeof(errno));
|
||||||
_exit(1);
|
close(pipe_fds[1]);
|
||||||
|
_exit(errno);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -118,6 +118,7 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
||||||
for (size_t i = 0; i < p->count; i++) {
|
for (size_t i = 0; i < p->count; i++) {
|
||||||
const struct particle *pp = p->particles[i];
|
const struct particle *pp = p->particles[i];
|
||||||
e->exposables[i] = pp->instantiate(pp, tags);
|
e->exposables[i] = pp->instantiate(pp, tags);
|
||||||
|
assert(e->exposables[i] != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
char *on_click = tags_expand_template(particle->on_click_template, tags);
|
char *on_click = tags_expand_template(particle->on_click_template, tags);
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "../config-verify.h"
|
#include "../config-verify.h"
|
||||||
#include "../particle.h"
|
#include "../particle.h"
|
||||||
#include "../plugin.h"
|
#include "../plugin.h"
|
||||||
|
#include "dynlist.h"
|
||||||
|
|
||||||
struct particle_map {
|
struct particle_map {
|
||||||
const char *tag_value;
|
const char *tag_value;
|
||||||
|
@ -87,11 +88,12 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
||||||
{
|
{
|
||||||
const struct private *p = particle->private;
|
const struct private *p = particle->private;
|
||||||
const struct tag *tag = tag_for_name(tags, p->tag);
|
const struct tag *tag = tag_for_name(tags, p->tag);
|
||||||
assert(tag != NULL || p->default_particle != NULL);
|
|
||||||
|
|
||||||
if (tag == NULL)
|
|
||||||
return p->default_particle->instantiate(p->default_particle, tags);
|
|
||||||
|
|
||||||
|
if (tag == NULL) {
|
||||||
|
return p->default_particle != NULL
|
||||||
|
? p->default_particle->instantiate(p->default_particle, tags)
|
||||||
|
: dynlist_exposable_new(NULL, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
const char *tag_value = tag->as_string(tag);
|
const char *tag_value = tag->as_string(tag);
|
||||||
struct particle *pp = NULL;
|
struct particle *pp = NULL;
|
||||||
|
@ -106,13 +108,16 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pp == NULL) {
|
|
||||||
assert(p->default_particle != NULL);
|
|
||||||
pp = p->default_particle;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct eprivate *e = calloc(1, sizeof(*e));
|
struct eprivate *e = calloc(1, sizeof(*e));
|
||||||
|
|
||||||
|
if (pp != NULL)
|
||||||
e->exposable = pp->instantiate(pp, tags);
|
e->exposable = pp->instantiate(pp, tags);
|
||||||
|
else if (p->default_particle != NULL)
|
||||||
|
e->exposable = p->default_particle->instantiate(p->default_particle, tags);
|
||||||
|
else
|
||||||
|
e->exposable = dynlist_exposable_new(NULL, 0, 0, 0);
|
||||||
|
|
||||||
|
assert(e->exposable != NULL);
|
||||||
|
|
||||||
char *on_click = tags_expand_template(particle->on_click_template, tags);
|
char *on_click = tags_expand_template(particle->on_click_template, tags);
|
||||||
struct exposable *exposable = exposable_common_new(particle, on_click);
|
struct exposable *exposable = exposable_common_new(particle, on_click);
|
||||||
|
|
|
@ -1,21 +1,5 @@
|
||||||
particle_sdk = declare_dependency(dependencies: [pixman, tllist, fcft])
|
particle_sdk = declare_dependency(dependencies: [pixman, tllist, fcft])
|
||||||
|
|
||||||
particles = []
|
|
||||||
foreach particle : ['empty', 'list', 'map', 'progress-bar', 'ramp', 'string']
|
|
||||||
if plugs_as_libs
|
|
||||||
shared_module('@0@'.format(particle), '@0@.c'.format(particle),
|
|
||||||
dependencies: particle_sdk,
|
|
||||||
name_prefix: 'particle_',
|
|
||||||
install: true,
|
|
||||||
install_dir: join_paths(get_option('libdir'), 'yambar'))
|
|
||||||
else
|
|
||||||
particles += [declare_dependency(
|
|
||||||
sources: '@0@.c'.format(particle),
|
|
||||||
dependencies: particle_sdk,
|
|
||||||
compile_args: '-DHAVE_PLUGIN_@0@'.format(particle.underscorify()))]
|
|
||||||
endif
|
|
||||||
endforeach
|
|
||||||
|
|
||||||
dynlist_lib = build_target(
|
dynlist_lib = build_target(
|
||||||
'dynlist', 'dynlist.c', 'dynlist.h', dependencies: particle_sdk,
|
'dynlist', 'dynlist.c', 'dynlist.h', dependencies: particle_sdk,
|
||||||
target_type: plugs_as_libs ? 'shared_library' : 'static_library',
|
target_type: plugs_as_libs ? 'shared_library' : 'static_library',
|
||||||
|
@ -25,3 +9,29 @@ dynlist_lib = build_target(
|
||||||
)
|
)
|
||||||
|
|
||||||
dynlist = declare_dependency(link_with: dynlist_lib)
|
dynlist = declare_dependency(link_with: dynlist_lib)
|
||||||
|
|
||||||
|
# Particle name -> dep-list
|
||||||
|
deps = {
|
||||||
|
'empty': [],
|
||||||
|
'list': [],
|
||||||
|
'map': [dynlist],
|
||||||
|
'progress-bar': [],
|
||||||
|
'ramp': [],
|
||||||
|
'string': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
particles = []
|
||||||
|
foreach particle, particle_deps : deps
|
||||||
|
if plugs_as_libs
|
||||||
|
shared_module('@0@'.format(particle), '@0@.c'.format(particle),
|
||||||
|
dependencies: [particle_sdk] + particle_deps,
|
||||||
|
name_prefix: 'particle_',
|
||||||
|
install: true,
|
||||||
|
install_dir: join_paths(get_option('libdir'), 'yambar'))
|
||||||
|
else
|
||||||
|
particles += [declare_dependency(
|
||||||
|
sources: '@0@.c'.format(particle),
|
||||||
|
dependencies: [particle_sdk] + particle_deps,
|
||||||
|
compile_args: '-DHAVE_PLUGIN_@0@'.format(particle.underscorify()))]
|
||||||
|
endif
|
||||||
|
endforeach
|
||||||
|
|
|
@ -179,13 +179,13 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
||||||
{
|
{
|
||||||
const struct private *p = particle->private;
|
const struct private *p = particle->private;
|
||||||
const struct tag *tag = tag_for_name(tags, p->tag);
|
const struct tag *tag = tag_for_name(tags, p->tag);
|
||||||
assert(tag != NULL);
|
|
||||||
|
|
||||||
long value = tag->as_int(tag);
|
long value = tag != NULL ? tag->as_int(tag) : 0;
|
||||||
long min = tag->min(tag);
|
long min = tag != NULL ? tag->min(tag) : 0;
|
||||||
long max = tag->max(tag);
|
long max = tag != NULL ? tag->max(tag) : 0;
|
||||||
|
|
||||||
LOG_DBG("%s: value=%ld, min=%ld, max=%ld", tag->name(tag), value, min, max);
|
LOG_DBG("%s: value=%ld, min=%ld, max=%ld",
|
||||||
|
tag != NULL ? tag->name(tag) : "<no tag>", value, min, max);
|
||||||
|
|
||||||
long fill_count = max == min ? 0 : p->width * value / (max - min);
|
long fill_count = max == min ? 0 : p->width * value / (max - min);
|
||||||
long empty_count = p->width - fill_count;
|
long empty_count = p->width - fill_count;
|
||||||
|
@ -210,6 +210,8 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
||||||
epriv->exposables[idx++] = p->end_marker->instantiate(p->end_marker, tags);
|
epriv->exposables[idx++] = p->end_marker->instantiate(p->end_marker, tags);
|
||||||
|
|
||||||
assert(idx == epriv->count);
|
assert(idx == epriv->count);
|
||||||
|
for (size_t i = 0; i < epriv->count; i++)
|
||||||
|
assert(epriv->exposables[i] != NULL);
|
||||||
|
|
||||||
char *on_click = tags_expand_template(particle->on_click_template, tags);
|
char *on_click = tags_expand_template(particle->on_click_template, tags);
|
||||||
|
|
||||||
|
@ -222,6 +224,9 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
||||||
exposable->expose = &expose;
|
exposable->expose = &expose;
|
||||||
exposable->on_mouse = &on_mouse;
|
exposable->on_mouse = &on_mouse;
|
||||||
|
|
||||||
|
if (tag == NULL)
|
||||||
|
return exposable;
|
||||||
|
|
||||||
enum tag_realtime_unit rt = tag->realtime(tag);
|
enum tag_realtime_unit rt = tag->realtime(tag);
|
||||||
|
|
||||||
if (rt == TAG_REALTIME_NONE)
|
if (rt == TAG_REALTIME_NONE)
|
||||||
|
|
|
@ -95,13 +95,12 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
||||||
{
|
{
|
||||||
const struct private *p = particle->private;
|
const struct private *p = particle->private;
|
||||||
const struct tag *tag = tag_for_name(tags, p->tag);
|
const struct tag *tag = tag_for_name(tags, p->tag);
|
||||||
assert(tag != NULL);
|
|
||||||
|
|
||||||
assert(p->count > 0);
|
assert(p->count > 0);
|
||||||
|
|
||||||
long value = tag->as_int(tag);
|
long value = tag != NULL ? tag->as_int(tag) : 0;
|
||||||
long min = tag->min(tag);
|
long min = tag != NULL ? tag->min(tag) : 0;
|
||||||
long max = tag->max(tag);
|
long max = tag != NULL ? tag->max(tag) : 0;
|
||||||
|
|
||||||
assert(value >= min && value <= max);
|
assert(value >= min && value <= max);
|
||||||
assert(max >= min);
|
assert(max >= min);
|
||||||
|
@ -122,6 +121,7 @@ instantiate(const struct particle *particle, const struct tag_set *tags)
|
||||||
|
|
||||||
struct eprivate *e = calloc(1, sizeof(*e));
|
struct eprivate *e = calloc(1, sizeof(*e));
|
||||||
e->exposable = pp->instantiate(pp, tags);
|
e->exposable = pp->instantiate(pp, tags);
|
||||||
|
assert(e->exposable != NULL);
|
||||||
|
|
||||||
char *on_click = tags_expand_template(particle->on_click_template, tags);
|
char *on_click = tags_expand_template(particle->on_click_template, tags);
|
||||||
struct exposable *exposable = exposable_common_new(particle, on_click);
|
struct exposable *exposable = exposable_common_new(particle, on_click);
|
||||||
|
|
2
plugin.c
2
plugin.c
|
@ -43,6 +43,7 @@ EXTERN_MODULE(network);
|
||||||
EXTERN_MODULE(removables);
|
EXTERN_MODULE(removables);
|
||||||
EXTERN_MODULE(river);
|
EXTERN_MODULE(river);
|
||||||
EXTERN_MODULE(sway_xkb);
|
EXTERN_MODULE(sway_xkb);
|
||||||
|
EXTERN_MODULE(script);
|
||||||
EXTERN_MODULE(xkb);
|
EXTERN_MODULE(xkb);
|
||||||
EXTERN_MODULE(xwindow);
|
EXTERN_MODULE(xwindow);
|
||||||
|
|
||||||
|
@ -119,6 +120,7 @@ init(void)
|
||||||
REGISTER_CORE_MODULE(river, river);
|
REGISTER_CORE_MODULE(river, river);
|
||||||
#endif
|
#endif
|
||||||
REGISTER_CORE_MODULE(sway-xkb, sway_xkb);
|
REGISTER_CORE_MODULE(sway-xkb, sway_xkb);
|
||||||
|
REGISTER_CORE_MODULE(script, script);
|
||||||
#if defined(HAVE_PLUGIN_xkb)
|
#if defined(HAVE_PLUGIN_xkb)
|
||||||
REGISTER_CORE_MODULE(xkb, xkb);
|
REGISTER_CORE_MODULE(xkb, xkb);
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Reference in a new issue