aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt55
-rw-r--r--LICENSE2
-rw-r--r--README.adoc3
-rw-r--r--ddc-ci.c2
-rw-r--r--fancontrol-ng.c10
-rw-r--r--gdm-switch-user.c2
-rw-r--r--genpass.c151
-rw-r--r--iexec.c113
-rw-r--r--input-switch.c88
m---------liberty0
-rw-r--r--paswitch.c2
-rwxr-xr-xwmstatus-weather.pl3
-rw-r--r--wmstatus.c1051
-rw-r--r--wmstatus.conf.example63
14 files changed, 1209 insertions, 336 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0e18b66..233ebde 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -25,7 +25,7 @@ include_directories (
link_directories (
${x_LIBRARY_DIRS} ${pulse_LIBRARY_DIRS} ${dbus_LIBRARY_DIRS})
-option (WITH_GDM "Compile with GDM support" ${gdm_FOUND})
+option (WITH_GDM "Compile with GDM utilities" ${gdm_FOUND})
# Generate a configuration file
configure_file (${PROJECT_SOURCE_DIR}/config.h.in
@@ -33,7 +33,7 @@ configure_file (${PROJECT_SOURCE_DIR}/config.h.in
include_directories (${PROJECT_BINARY_DIR})
# Build
-set (targets wmstatus paswitch siprandom big-brother)
+set (targets wmstatus paswitch siprandom genpass)
if ("${CMAKE_SYSTEM_NAME}" STREQUAL Linux)
# These use Linux i2c APIs, but can be made to work on macOS
list (APPEND targets brightness input-switch)
@@ -47,7 +47,7 @@ elseif (APPLE)
add_definitions (-D_DARWIN_C_SOURCE)
endif ()
-foreach (name ${targets})
+foreach (name big-brother ${targets})
add_executable (${name} ${name}.c)
endforeach ()
@@ -58,9 +58,10 @@ target_link_libraries (wmstatus
add_threads (wmstatus)
if (WITH_GDM)
- include_directories (${gdm_INCLUDE_DIRS})
- link_directories (${gdm_LIBRARY_DIRS})
+ list (APPEND targets gdm-switch-user)
add_executable (gdm-switch-user gdm-switch-user.c)
+ target_include_directories (gdm-switch-user PUBLIC ${gdm_INCLUDE_DIRS})
+ target_link_directories (gdm-switch-user PUBLIC ${gdm_LIBRARY_DIRS})
target_link_libraries (gdm-switch-user ${gdm_LIBRARIES})
endif ()
@@ -71,27 +72,43 @@ include (GNUInstallDirs)
set (SYSTEMD_UNITDIR /lib/systemd/system
CACHE PATH "Base directory for systemd unit files")
-configure_file (${PROJECT_SOURCE_DIR}/fancontrol-ng.service.in
- ${PROJECT_BINARY_DIR}/fancontrol-ng.service @ONLY)
-install (FILES fancontrol-ng.conf.example
- DESTINATION ${CMAKE_INSTALL_DATADIR}/fancontrol-ng)
+if ("${CMAKE_SYSTEM_NAME}" STREQUAL Linux)
+ configure_file (${PROJECT_SOURCE_DIR}/fancontrol-ng.service.in
+ ${PROJECT_BINARY_DIR}/fancontrol-ng.service @ONLY)
+ install (FILES fancontrol-ng.conf.example
+ DESTINATION ${CMAKE_INSTALL_DATADIR}/fancontrol-ng)
-configure_file (${PROJECT_SOURCE_DIR}/priod.service.in
- ${PROJECT_BINARY_DIR}/priod.service @ONLY)
-install (FILES priod.conf.example
- DESTINATION ${CMAKE_INSTALL_DATADIR}/priod)
+ configure_file (${PROJECT_SOURCE_DIR}/priod.service.in
+ ${PROJECT_BINARY_DIR}/priod.service @ONLY)
+ install (FILES priod.conf.example
+ DESTINATION ${CMAKE_INSTALL_DATADIR}/priod)
-# System-wide unit files should be installed under /lib and not /usr/lib
-install (FILES
- ${PROJECT_BINARY_DIR}/fancontrol-ng.service
- ${PROJECT_BINARY_DIR}/priod.service
- DESTINATION "${SYSTEMD_UNITDIR}")
+ # System-wide unit files should be installed under /lib and not /usr/lib
+ install (FILES
+ ${PROJECT_BINARY_DIR}/fancontrol-ng.service
+ ${PROJECT_BINARY_DIR}/priod.service
+ DESTINATION "${SYSTEMD_UNITDIR}")
+endif ()
if (WITH_GDM)
install (TARGETS gdm-switch-user DESTINATION ${CMAKE_INSTALL_BINDIR})
endif ()
-list (REMOVE_ITEM targets big-brother)
+# These should be accessible by users, but need to touch system devices.
+# Use the setuid bit, for simplicity.
+set (SETUID "SETUID" CACHE STRING "Set this empty on permission issues")
+foreach (target brightness input-switch)
+ if (${target} IN_LIST targets)
+ list (REMOVE_ITEM targets ${target})
+ install (TARGETS ${target} DESTINATION ${CMAKE_INSTALL_BINDIR}
+ PERMISSIONS
+ OWNER_WRITE OWNER_READ OWNER_EXECUTE
+ GROUP_READ GROUP_EXECUTE
+ WORLD_READ WORLD_EXECUTE
+ ${SETUID})
+ endif ()
+endforeach ()
+
install (TARGETS ${targets} DESTINATION ${CMAKE_INSTALL_BINDIR})
install (PROGRAMS shellify DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
diff --git a/LICENSE b/LICENSE
index 7eda545..fe9bba1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2015 - 2021, Přemysl Eric Janouch <p@janouch.name>
+Copyright (c) 2015 - 2025, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
diff --git a/README.adoc b/README.adoc
index 0bd0de4..9f2b5f4 100644
--- a/README.adoc
+++ b/README.adoc
@@ -37,7 +37,8 @@ Regular releases are sporadic. git master should be stable enough.
Building
--------
Build dependencies: CMake, pkg-config, liberty (included) +
-Runtime dependencies: libpulse, libx11, dbus-1, libgdm (optional)
+Runtime dependencies: libpulse, libx11, dbus-1 +
+Optional runtime dependencies: libgdm (gdm-switch-user)
$ git clone --recursive https://git.janouch.name/p/desktop-tools.git
$ mkdir desktop-tools/build
diff --git a/ddc-ci.c b/ddc-ci.c
index c9148dc..7701152 100644
--- a/ddc-ci.c
+++ b/ddc-ci.c
@@ -46,7 +46,7 @@ log_message_custom (void *user_data, const char *quote, const char *fmt,
static void
wait_ms (long ms)
{
- struct timespec ts = { 0, ms * 1000 * 1000 };
+ struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
nanosleep (&ts, NULL);
}
diff --git a/fancontrol-ng.c b/fancontrol-ng.c
index ec06dfb..e87dce1 100644
--- a/fancontrol-ng.c
+++ b/fancontrol-ng.c
@@ -124,7 +124,7 @@ config_validate_nonnegative (const struct config_item *item, struct error **e)
return error_set (e, "must be non-negative");
}
-static struct config_schema g_config_device[] =
+static const struct config_schema g_config_device[] =
{
{ .name = "name",
.comment = "Device identifier",
@@ -137,7 +137,7 @@ static struct config_schema g_config_device[] =
{}
};
-static struct config_schema g_config_pwm[] =
+static const struct config_schema g_config_pwm[] =
{
{ .name = "temp",
.comment = "Path to temperature sensor output",
@@ -415,7 +415,7 @@ device_create (struct app_context *ctx, const char *path,
// There is no room for errors in the configuration, everything must be valid.
// Thus the reset to defaults on invalid values is effectively disabled here.
static bool
-apply_schema (struct config_schema *schema, struct config_item *object,
+apply_schema (const struct config_schema *schema, struct config_item *object,
struct error **e)
{
struct error *warning = NULL, *error = NULL;
@@ -445,7 +445,7 @@ static bool
check_device_configuration (struct config_item *subtree, struct error **e)
{
// Check regular fields in the device object
- for (struct config_schema *s = g_config_device; s->name; s++)
+ for (const struct config_schema *s = g_config_device; s->name; s++)
if (!apply_schema (s, subtree, e))
return false;
@@ -465,7 +465,7 @@ check_device_configuration (struct config_item *subtree, struct error **e)
while ((pwm = str_map_iter_next (&iter)))
{
const char *subpath = iter.link->key;
- for (struct config_schema *s = g_config_pwm; s->name; s++)
+ for (const struct config_schema *s = g_config_pwm; s->name; s++)
if (!apply_schema (s, pwm, &error))
{
error_set (e, "PWM `%s': %s", subpath, error->message);
diff --git a/gdm-switch-user.c b/gdm-switch-user.c
index 5718c31..55d8ba3 100644
--- a/gdm-switch-user.c
+++ b/gdm-switch-user.c
@@ -1,5 +1,5 @@
// Public domain
-#include <gdm-user-switching.h>
+#include <gdm/gdm-user-switching.h>
int
main (int argc, char *argv[])
diff --git a/genpass.c b/genpass.c
new file mode 100644
index 0000000..623eecb
--- /dev/null
+++ b/genpass.c
@@ -0,0 +1,151 @@
+/*
+ * genpass.c: password generator
+ *
+ * Copyright (c) 2025, Přemysl Eric Janouch <p@janouch.name>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ */
+
+#include "config.h"
+#undef PROGRAM_NAME
+#define PROGRAM_NAME "genpass"
+#include "liberty/liberty.c"
+
+static struct str
+parse_group (const char *group)
+{
+ bool present[0x100] = {};
+ for (size_t i = 0; group[i]; i++)
+ {
+ unsigned char c = group[i];
+ if (!i || c != '-' || !group[i + 1])
+ present[c] = true;
+ else if (group[i + 1] < group[i - 1])
+ exit_fatal ("character ranges must be increasing");
+ else
+ for (c = group[i - 1]; ++c <= group[i + 1]; )
+ present[c] = true;
+ }
+
+ struct str alphabet = str_make ();
+ for (size_t i = 1; i < N_ELEMENTS (present); i++)
+ if (present[i])
+ str_append_c (&alphabet, i);
+ if (!alphabet.len)
+ exit_fatal ("empty group");
+ return alphabet;
+}
+
+static void
+parse_program_arguments (int argc, char **argv,
+ unsigned long *length, struct strv *groups, struct str *alphabet)
+{
+ static const struct opt opts[] =
+ {
+ { 'l', "length", "CHARACTERS", 0, "set password length" },
+ { 'd', "debug", NULL, 0, "run in debug mode" },
+ { 'h', "help", NULL, 0, "display this help and exit" },
+ { 'V', "version", NULL, 0, "output version information and exit" },
+ { 0, NULL, NULL, 0, NULL }
+ };
+
+ struct opt_handler oh =
+ opt_handler_make (argc, argv, opts, "GROUP...", "Password generator.");
+
+ int c;
+ while ((c = opt_handler_get (&oh)) != -1)
+ switch (c)
+ {
+ case 'l':
+ if (!xstrtoul (length, optarg, 10) || *length <= 0)
+ print_fatal ("invalid length argument");
+ break;
+ case 'd':
+ g_debug_mode = true;
+ break;
+ case 'h':
+ opt_handler_usage (&oh, stdout);
+ exit (EXIT_SUCCESS);
+ case 'V':
+ printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
+ exit (EXIT_SUCCESS);
+ default:
+ print_error ("wrong options");
+ opt_handler_usage (&oh, stderr);
+ exit (EXIT_FAILURE);
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ for (int i = 0; i < argc; i++)
+ {
+ struct str alphabet = parse_group (argv[i]);
+ strv_append_owned (groups, str_steal (&alphabet));
+ }
+
+ bool present[0x100] = {};
+ for (size_t i = 0; i < groups->len; i++)
+ for (size_t k = 0; groups->vector[i][k]; k++)
+ {
+ unsigned char c = groups->vector[i][k];
+ if (present[c])
+ exit_fatal ("groups are not disjunct");
+ present[c] = true;
+ }
+ for (size_t i = 1; i < N_ELEMENTS (present); i++)
+ if (present[i])
+ str_append_c (alphabet, i);
+
+ if (groups->len > *length)
+ exit_fatal ("the requested length is less than the number of groups");
+ if (!groups->len)
+ {
+ opt_handler_usage (&oh, stderr);
+ exit (EXIT_FAILURE);
+ }
+
+ opt_handler_free (&oh);
+}
+
+int
+main (int argc, char *argv[])
+{
+ unsigned long length = 8;
+ struct strv groups = strv_make ();
+ struct str alphabet = str_make ();
+ parse_program_arguments (argc, argv, &length, &groups, &alphabet);
+
+ unsigned seed = 0;
+ if (!random_bytes (&seed, sizeof seed, NULL))
+ exit_fatal ("failed to initialize random numbers");
+ srand (seed);
+
+ // Select from a joined alphabet, but make sure all groups are represented.
+ struct str candidate = str_make ();
+ while (true)
+ {
+restart:
+ for (size_t i = length; i--; )
+ str_append_c (&candidate, alphabet.str[rand () % alphabet.len]);
+ for (size_t i = 0; i < groups.len; i++)
+ if (!strpbrk (candidate.str, groups.vector[i]))
+ {
+ str_reset (&candidate);
+ goto restart;
+ }
+
+ printf ("%s\n", candidate.str);
+ return 0;
+ }
+}
diff --git a/iexec.c b/iexec.c
index 04e8e91..562d49d 100644
--- a/iexec.c
+++ b/iexec.c
@@ -1,7 +1,7 @@
/*
* iexec.c: run a program and restart on file change
*
- * Copyright (c) 2017, Přemysl Eric Janouch <p@janouch.name>
+ * Copyright (c) 2017 - 2023, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -24,34 +24,54 @@
// This can also work on BSD if someone puts in the effort to support kqueue
#include <sys/inotify.h>
-static pid_t g_child;
-static bool g_restarting = false;
-static int g_inotify_fd, g_inotify_wd;
+static struct
+{
+ pid_t child; ///< Watched child or 0
+ bool exits; ///< Don't restart child when it exits
+ bool respawn; ///< Respawn child ASAP
+ bool killing; ///< Waiting for child to die
+ int inotify_fd, inotify_wd;
+}
+g;
+// Note that this program doesn't queue up file-based restarts
static void
-handle_file_change (const char *base)
+handle_inotify_event (const struct inotify_event *e, const char *base)
{
- char buf[4096]; ssize_t len; const struct inotify_event *e;
- while ((len = read (g_inotify_fd, buf, sizeof buf)) > 0)
- for (char *ptr = buf; ptr < buf + len; ptr += sizeof *e + e->len)
- {
- e = (const struct inotify_event *) buf;
- if (e->wd != g_inotify_wd || strcmp (e->name, base))
- continue;
+ if (e->wd != g.inotify_wd || strcmp (e->name, base))
+ return;
+ if (g.child)
+ {
print_debug ("file changed, killing child");
- g_restarting = true;
- if (kill (g_child, SIGINT))
+ if (kill (g.child, SIGINT))
print_error ("kill: %s", strerror (errno));
+ g.killing = true;
}
+ else
+ {
+ print_debug ("file changed, respawning");
+ g.respawn = true;
+ }
+}
+
+static void
+handle_file_change (const char *base)
+{
+ char buf[4096];
+ ssize_t len = 0;
+ struct inotify_event *e = NULL;
+ while ((len = read (g.inotify_fd, buf, sizeof buf)) > 0)
+ for (char *ptr = buf; ptr < buf + len; ptr += sizeof *e + e->len)
+ handle_inotify_event ((e = (struct inotify_event *) buf), base);
}
static void
spawn (char *argv[])
{
- if ((g_child = fork ()) == -1)
+ if ((g.child = fork ()) == -1)
exit_fatal ("fork: %s", strerror (errno));
- else if (g_child)
+ else if (g.child)
return;
// A linker can create spurious CLOSE_WRITEs, wait until it's executable
@@ -64,23 +84,22 @@ spawn (char *argv[])
}
static bool
-check_child_death (char *argv[])
+check_child_death (void)
{
- if (waitpid (g_child, NULL, WNOHANG) != g_child)
+ int status = 0;
+ if (waitpid (g.child, &status, WNOHANG) != g.child)
return true;
- if (!g_restarting)
+ g.child = 0;
+ if (!g.killing)
{
print_debug ("child died on its own, not respawning");
- return false;
- }
- else
- {
- print_debug ("child died on request, respawning");
- spawn (argv);
- g_restarting = false;
- return true;
+ return g.exits;
}
+
+ g.killing = false;
+ print_debug ("child died on request, respawning");
+ return g.respawn = true;
}
static void
@@ -93,8 +112,11 @@ sigchld_handler (int signum)
int
main (int argc, char *argv[])
{
+ const char *target = NULL;
static const struct opt opts[] =
{
+ { 'f', "file", "PATH", 0, "watch this path rather than the program" },
+ { 'e', "exits", NULL, 0, "allow the program to exit on its own" },
{ 'd', "debug", NULL, 0, "run in debug mode" },
{ 'h', "help", NULL, 0, "display this help and exit" },
{ 'V', "version", NULL, 0, "output version information and exit" },
@@ -111,6 +133,12 @@ main (int argc, char *argv[])
while ((c = opt_handler_get (&oh)) != -1)
switch (c)
{
+ case 'f':
+ target = optarg;
+ break;
+ case 'e':
+ g.exits = true;
+ break;
case 'd':
g_debug_mode = true;
break;
@@ -136,6 +164,9 @@ main (int argc, char *argv[])
argc -= optind;
argv += optind;
+ if (!target)
+ target = argv[0];
+
(void) signal (SIGPIPE, SIG_IGN);
struct sigaction sa = { .sa_handler = sigchld_handler };
sigemptyset (&sa.sa_mask);
@@ -148,27 +179,33 @@ main (int argc, char *argv[])
if (sigprocmask (SIG_BLOCK, &chld, &orig))
exit_fatal ("sigprocmask: %s", strerror (errno));
- char *path = xstrdup (argv[0]);
+ char *path = NULL;
+ char *dir = dirname ((path = xstrdup (target)));
- if ((g_inotify_fd = inotify_init1 (IN_NONBLOCK)) < 0)
+ if ((g.inotify_fd = inotify_init1 (IN_NONBLOCK)) < 0)
exit_fatal ("inotify_init1: %s", strerror (errno));
- if ((g_inotify_wd = inotify_add_watch (g_inotify_fd,
- dirname (path), IN_MOVED_TO | IN_CLOSE_WRITE)) < 0)
+ if ((g.inotify_wd = inotify_add_watch (g.inotify_fd,
+ dir, IN_MOVED_TO | IN_CLOSE_WRITE)) < 0)
exit_fatal ("inotify_add_watch: %s", strerror (errno));
free (path);
- char *base = basename ((path = xstrdup (argv[0])));
- spawn (argv);
-
+ char *base = basename ((path = xstrdup (target)));
+ g.respawn = true;
do
{
- fd_set r; FD_SET (g_inotify_fd, &r);
- (void) pselect (g_inotify_fd + 1, &r, NULL, NULL, NULL, &orig);
+ if (g.respawn)
+ {
+ spawn (argv);
+ g.respawn = false;
+ }
+
+ fd_set r; FD_SET (g.inotify_fd, &r);
+ (void) pselect (g.inotify_fd + 1, &r, NULL, NULL, NULL, &orig);
handle_file_change (base);
}
- while (check_child_death (argv));
+ while (check_child_death ());
free (path);
- close (g_inotify_fd);
+ xclose (g.inotify_fd);
return EXIT_SUCCESS;
}
diff --git a/input-switch.c b/input-switch.c
index da93670..5788f4a 100644
--- a/input-switch.c
+++ b/input-switch.c
@@ -1,7 +1,7 @@
/*
* input-switch.c: switches display input via DDC/CI
*
- * Copyright (c) 2017, Přemysl Eric Janouch <p@janouch.name>
+ * Copyright (c) 2017 - 2022, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -28,11 +28,60 @@
#include "ddc-ci.c"
#include <dirent.h>
+// This list is from the MCCS 2.2a specification
+struct
+{
+ int code; ///< Input code
+ const char *name; ///< Input name
+ int index; ///< Input index
+}
+g_inputs[] =
+{
+ { 0x01, "VGA", 1, }, // Analog video (R/G/B) 1
+ { 0x02, "VGA", 2, }, // Analog video (R/G/B) 2
+ { 0x03, "DVI", 1, }, // Digital video (TMDS) 1 DVI 1
+ { 0x04, "DVI", 2, }, // Digital video (TMDS) 2 DVI 2
+ { 0x05, "composite", 1, }, // Composite video 1
+ { 0x06, "composite", 2, }, // Composite video 2
+ { 0x07, "S-Video", 1, }, // S-video 1
+ { 0x08, "S-Video", 2, }, // S-video 2
+ { 0x09, "tuner", 1, }, // Tuner 1
+ { 0x0A, "tuner", 2, }, // Tuner 2
+ { 0x0B, "tuner", 3, }, // Tuner 3
+ { 0x0C, "component", 1, }, // Component video (YPbPr/YCbCr) 1
+ { 0x0D, "component", 2, }, // Component video (YPbPr/YCbCr) 2
+ { 0x0E, "component", 3, }, // Component video (YPbPr/YCbCr) 3
+ { 0x0F, "DP", 1, }, // DisplayPort 1
+ { 0x10, "DP", 2, }, // DisplayPort 2
+ { 0x11, "HDMI", 1, }, // Digital Video (TMDS) 3 HDMI 1
+ { 0x12, "HDMI", 2, }, // Digital Video (TMDS) 4 HDMI 2
+ { 0x15, "bnq-tb", 1, }, // Thunderbolt on BenQ PD3220U (no spec)
+};
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
typedef bool (*ActionFunc) (int fd, int param, struct error **);
static bool
+get_input_source (int fd, int input, struct error **e)
+{
+ struct vcp_feature_readout readout = {};
+ if (!vcp_get_feature (fd, VCP_INPUT_SOURCE, &readout, e))
+ return false;
+
+ (void) input;
+ for (size_t i = 0; i < N_ELEMENTS (g_inputs); i++)
+ if (g_inputs[i].code == readout.cur)
+ {
+ printf ("input is %s %d\n", g_inputs[i].name, g_inputs[i].index);
+ return true;
+ }
+
+ printf ("input is %d\n", readout.cur);
+ return true;
+}
+
+static bool
set_input_source (int fd, int input, struct error **e)
{
struct vcp_feature_readout readout = {};
@@ -114,36 +163,6 @@ i2c (ActionFunc action, int param)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-// This list is from the MCCS 2.2a specification
-struct
-{
- int code; ///< Input code
- const char *name; ///< Input name
- int index; ///< Input index
-}
-g_inputs[] =
-{
- { 0x01, "vga", 1, }, // Analog video (R/G/B) 1
- { 0x02, "vga", 2, }, // Analog video (R/G/B) 2
- { 0x03, "dvi", 1, }, // Digital video (TMDS) 1 DVI 1
- { 0x04, "dvi", 2, }, // Digital video (TMDS) 2 DVI 2
- { 0x05, "composite", 1, }, // Composite video 1
- { 0x06, "composite", 2, }, // Composite video 2
- { 0x07, "s-video", 1, }, // S-video 1
- { 0x08, "s-video", 2, }, // S-video 2
- { 0x09, "tuner", 1, }, // Tuner 1
- { 0x0A, "tuner", 2, }, // Tuner 2
- { 0x0B, "tuner", 3, }, // Tuner 3
- { 0x0C, "component", 1, }, // Component video (YPbPr/YCbCr) 1
- { 0x0D, "component", 2, }, // Component video (YPbPr/YCbCr) 2
- { 0x0E, "component", 3, }, // Component video (YPbPr/YCbCr) 3
- { 0x0F, "dp", 1, }, // DisplayPort 1
- { 0x10, "dp", 2, }, // DisplayPort 2
- { 0x11, "hdmi", 1, }, // Digital Video (TMDS) 3 HDMI 1
- { 0x12, "hdmi", 2, }, // Digital Video (TMDS) 4 HDMI 2
- { 0x15, "bnq-tb", 1, }, // Thunderbolt on BenQ PD3220U (no spec)
-};
-
int
main (int argc, char *argv[])
{
@@ -151,9 +170,14 @@ main (int argc, char *argv[])
if (argc <= 1)
{
- printf ("Usage: %s <input> [<index>]\n", argv[0]);
+ printf ("Usage: %s {? | INPUT [INDEX]}\n", argv[0]);
exit (EXIT_FAILURE);
}
+ if (!strcmp (argv[1], "?"))
+ {
+ i2c (get_input_source, -1);
+ exit (EXIT_SUCCESS);
+ }
unsigned long input_source = 0;
if (xstrtoul (&input_source, argv[1], 10))
diff --git a/liberty b/liberty
-Subproject 782a9a5977bd5f2101e8808b94d659fe52e2490
+Subproject 75fc6f1c374796f9e794297c3893089009b8772
diff --git a/paswitch.c b/paswitch.c
index 66cec1d..3d329f4 100644
--- a/paswitch.c
+++ b/paswitch.c
@@ -290,7 +290,7 @@ on_sink_info (pa_context *context, const pa_sink_info *info, int eol,
sink->ports_len++;
struct port *port = sink->ports =
- xcalloc (sizeof *sink->ports, sink->ports_len);
+ xcalloc (sink->ports_len, sizeof *sink->ports);
for (struct pa_sink_port_info **iter = info->ports; *iter; iter++)
{
port->name = xstrdup ((*iter)->name);
diff --git a/wmstatus-weather.pl b/wmstatus-weather.pl
index d5252f2..2d60bc4 100755
--- a/wmstatus-weather.pl
+++ b/wmstatus-weather.pl
@@ -20,7 +20,8 @@ my %legends;
sub retrieve_legends {
# HTTP/Tiny supports TLS, but with non-core IO::Socket::SSL, so use cURL
open(my $sock, '-|', 'curl', '-sSA', $agent,
- "$base/weathericon/2.0/legends.txt") or return $!;
+ 'https://raw.githubusercontent.com/' .
+ 'metno/weathericons/main/weather/legend.csv') or return $!;
while (local $_ = <$sock>) { $legends{$1} = $2 if /^(.+?),(.+?),/ }
close($sock);
}
diff --git a/wmstatus.c b/wmstatus.c
index a35ebc6..0c0136e 100644
--- a/wmstatus.c
+++ b/wmstatus.c
@@ -1,7 +1,7 @@
/*
* wmstatus.c: simple PulseAudio-enabled status setter for dwm and i3/sway
*
- * Copyright (c) 2015 - 2021, Přemysl Eric Janouch <p@janouch.name>
+ * Copyright (c) 2015 - 2024, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
@@ -20,7 +20,10 @@
#define LIBERTY_WANT_ASYNC
#define LIBERTY_WANT_PROTO_MPD
-#define _GNU_SOURCE // openat
+// openat, dirfd
+#define _XOPEN_SOURCE 700
+#define _ATFILE_SOURCE
+#define _GNU_SOURCE
#include "config.h"
#undef PROGRAM_NAME
@@ -67,6 +70,20 @@ log_message_custom (void *user_data, const char *quote, const char *fmt,
fputs ("\n", stream);
}
+static void
+shell_quote (const char *str, struct str *output)
+{
+ // See SUSv3 Shell and Utilities, 2.2.3 Double-Quotes
+ str_append_c (output, '"');
+ for (const char *p = str; *p; p++)
+ {
+ if (strchr ("`$\"\\", *p))
+ str_append_c (output, '\\');
+ str_append_c (output, *p);
+ }
+ str_append_c (output, '"');
+}
+
// --- NUT ---------------------------------------------------------------------
// More or less copied and pasted from the MPD client. This code doesn't even
@@ -779,41 +796,197 @@ backend_i3_new (void)
// --- Configuration -----------------------------------------------------------
-static struct simple_config_item g_config_table[] =
+static const struct config_schema g_config_general[] =
{
- { "mpd_address", "localhost", "MPD host or socket" },
- { "mpd_service", "6600", "MPD service name or port" },
- { "mpd_password", NULL, "MPD password" },
+ { .name = "command",
+ .comment = "Command to run for more info",
+ .type = CONFIG_ITEM_STRING },
+ { .name = "sleep_timer",
+ .comment = "Idle seconds to suspend after",
+ .type = CONFIG_ITEM_INTEGER },
+ {}
+};
- { "nut_enabled", "off", "NUT UPS status reading enabled" },
- { "nut_load_thld", "50", "NUT threshold for load display" },
+static const struct config_schema g_config_mpd[] =
+{
+ { .name = "address",
+ .comment = "MPD host or socket",
+ .type = CONFIG_ITEM_STRING,
+ .default_ = "\"localhost\"" },
+ { .name = "service",
+ .comment = "MPD service name or port",
+ .type = CONFIG_ITEM_STRING,
+ .default_ = "\"6600\"" },
+ { .name = "password",
+ .comment = "MPD password",
+ .type = CONFIG_ITEM_STRING },
+ {}
+};
+
+static const struct config_schema g_config_nut[] =
+{
+ { .name = "enabled",
+ .comment = "NUT UPS status reading enabled",
+ .type = CONFIG_ITEM_BOOLEAN,
+ .default_ = "off" },
+ { .name = "load_thld",
+ .comment = "NUT threshold for load display",
+ .type = CONFIG_ITEM_INTEGER,
+ .default_ = "50" },
// This is just a hack because my UPS doesn't report that value; a more
// proper way of providing this information would be by making use of the
// enhanced configuration format and allowing arbitrary per-UPS overrides
- { "nut_load_power", NULL, "ups.realpower.nominal override" },
-
- { "command", NULL, "command to run for more info" },
- { "sleep_timer", NULL, "idle seconds to suspend after" },
- { NULL, NULL, NULL }
+ { .name = "load_power",
+ .comment = "ups.realpower.nominal fallback",
+ .type = CONFIG_ITEM_INTEGER },
+ {}
};
+static void
+app_load_config_general (struct config_item *subtree, void *user_data)
+{
+ config_schema_apply_to_object (g_config_general, subtree, user_data);
+}
+
+static void
+app_load_config_mpd (struct config_item *subtree, void *user_data)
+{
+ config_schema_apply_to_object (g_config_mpd, subtree, user_data);
+}
+
+static void
+app_load_config_nut (struct config_item *subtree, void *user_data)
+{
+ config_schema_apply_to_object (g_config_nut, subtree, user_data);
+}
+
+static struct config
+app_make_config (void)
+{
+ struct config config = config_make ();
+ config_register_module (&config, "general", app_load_config_general, NULL);
+ config_register_module (&config, "keys", NULL, NULL);
+ config_register_module (&config, "mpd", app_load_config_mpd, NULL);
+ config_register_module (&config, "nut", app_load_config_nut, NULL);
+
+ // Bootstrap configuration, so that we can access schema items at all
+ config_load (&config, config_item_object ());
+ return config;
+}
+
+static const char *
+get_config_string (struct config_item *root, const char *key)
+{
+ struct config_item *item = config_item_get (root, key, NULL);
+ hard_assert (item);
+ if (item->type == CONFIG_ITEM_NULL)
+ return NULL;
+ hard_assert (config_item_type_is_string (item->type));
+ return item->value.string.str;
+}
+
+static const int64_t *
+get_config_integer (struct config_item *root, const char *key)
+{
+ struct config_item *item = config_item_get (root, key, NULL);
+ hard_assert (item);
+ if (item->type == CONFIG_ITEM_NULL)
+ return NULL;
+ hard_assert (item->type == CONFIG_ITEM_INTEGER);
+ return &item->value.integer;
+}
+
+static const bool *
+get_config_boolean (struct config_item *root, const char *key)
+{
+ struct config_item *item = config_item_get (root, key, NULL);
+ hard_assert (item);
+ if (item->type == CONFIG_ITEM_NULL)
+ return NULL;
+ hard_assert (item->type == CONFIG_ITEM_BOOLEAN);
+ return &item->value.boolean;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+// This is essentially simplified shell command language syntax,
+// without comments or double quotes, and line feeds are whitespace.
+static bool
+parse_binding (const char *line, struct strv *out)
+{
+ enum { STA, DEF, ESC, WOR, QUO, STATES };
+ enum { TAKE = 1 << 3, PUSH = 1 << 4, STOP = 1 << 5, ERROR = 1 << 6 };
+ enum { TWOR = TAKE | WOR };
+
+ // We never transition back to the start state, so it can stay as a no-op
+ static char table[STATES][7] =
+ {
+ // state NUL SP, TAB, LF ' \ default
+ /* STA */ { STOP, DEF, QUO, ESC, TWOR },
+ /* DEF */ { STOP, 0, QUO, ESC, TWOR },
+ /* ESC */ { ERROR, TWOR, TWOR, TWOR, TWOR },
+ /* WOR */ { STOP | PUSH, DEF | PUSH, QUO, ESC, TAKE },
+ /* QUO */ { ERROR, TAKE, WOR, TAKE, TAKE },
+ };
+
+ strv_reset (out);
+ struct str token = str_make ();
+ int state = STA, edge = 0, ch = 0;
+ while (true)
+ {
+ switch ((ch = (unsigned char) *line++))
+ {
+ case 0: edge = table[state][0]; break;
+ case '\t':
+ case '\n':
+ case ' ': edge = table[state][1]; break;
+ case '\'': edge = table[state][2]; break;
+ case '\\': edge = table[state][3]; break;
+ default: edge = table[state][4]; break;
+ }
+ if (edge & TAKE)
+ str_append_c (&token, ch);
+ if (edge & PUSH)
+ {
+ strv_append_owned (out, str_steal (&token));
+ token = str_make ();
+ }
+ if (edge & STOP)
+ {
+ str_free (&token);
+ return true;
+ }
+ if (edge & ERROR)
+ {
+ str_free (&token);
+ return false;
+ }
+ if (edge &= 7)
+ state = edge;
+ }
+}
+
// --- Application -------------------------------------------------------------
struct app_context
{
- struct str_map config; ///< Program configuration
+ struct config config; ///< Program configuration
struct backend *backend; ///< WM backend
Display *dpy; ///< X display handle
struct poller_fd x_event; ///< X11 event
- const char *prefix; ///< User-defined prefix
struct poller poller; ///< Poller
struct poller_timer time_changed; ///< Time change timer
struct poller_timer make_context; ///< Start PulseAudio communication
struct poller_timer refresh_rest; ///< Refresh unpollable information
+ // IPC:
+
+ int ipc_fd; ///< The IPC datagram socket (file)
+ struct poller_fd ipc_event; ///< IPC event
+
// Sleep timer:
int xsync_base_event_code; ///< XSync base event code
@@ -834,6 +1007,7 @@ struct app_context
// Hotkeys:
+ struct binding *bindings; ///< Global bindings
int xkb_base_event_code; ///< Xkb base event code
char *layout; ///< Keyboard layout
@@ -849,7 +1023,7 @@ struct app_context
struct mpd_client mpd_client; ///< MPD client
char *mpd_song; ///< MPD current song
- char *mpd_status; ///< MPD status (overrides song)
+ bool mpd_stopped; ///< MPD stopped (overrides song)
// NUT:
@@ -920,8 +1094,7 @@ app_context_init (struct app_context *self)
{
memset (self, 0, sizeof *self);
- self->config = str_map_make (free);
- simple_config_load_defaults (&self->config, g_config_table);
+ self->config = app_make_config ();
if (!(self->dpy = XkbOpenDisplay
(NULL, &self->xkb_base_event_code, NULL, NULL, NULL, NULL)))
@@ -930,10 +1103,13 @@ app_context_init (struct app_context *self)
poller_init (&self->poller);
self->api = poller_pa_new (&self->poller);
+ self->ipc_fd = -1;
+ self->ipc_event = poller_fd_make (&self->poller, self->ipc_fd);
+
self->command_current = strv_make ();
self->command_pid = -1;
self->command_fd = -1;
- self->command_event = poller_fd_make (&self->poller, -1);
+ self->command_event = poller_fd_make (&self->poller, self->command_fd);
self->command_buffer = str_make ();
set_cloexec (ConnectionNumber (self->dpy));
@@ -963,8 +1139,8 @@ app_context_init (struct app_context *self)
static void
app_context_free (struct app_context *self)
{
- str_map_free (&self->config);
- if (self->backend) self->backend->destroy (self->backend);
+ config_free (&self->config);
+ if (self->backend) self->backend->destroy (self->backend);
poller_fd_reset (&self->x_event);
cstr_set (&self->layout, NULL);
@@ -973,6 +1149,12 @@ app_context_free (struct app_context *self)
if (self->context) pa_context_unref (self->context);
if (self->dpy) XCloseDisplay (self->dpy);
+ if (self->ipc_fd != -1)
+ {
+ poller_fd_reset (&self->ipc_event);
+ xclose (self->ipc_fd);
+ }
+
strv_free (&self->command_current);
if (self->command_pid != -1)
(void) kill (self->command_pid, SIGTERM);
@@ -989,7 +1171,6 @@ app_context_free (struct app_context *self)
mpd_client_free (&self->mpd_client);
cstr_set (&self->mpd_song, NULL);
- cstr_set (&self->mpd_status, NULL);
nut_client_free (&self->nut_client);
str_map_free (&self->nut_ups_info);
@@ -1112,6 +1293,15 @@ try_power_supply (int dir, struct error **e)
return NULL;
}
+ bool offline = !read_number (dir, "online", &error);
+ if (error)
+ {
+ error_free (error);
+ error = NULL;
+ }
+ else if (offline)
+ return NULL;
+
bool is_relevant =
!strcmp (type, "Battery") ||
!strcmp (type, "USB") ||
@@ -1226,9 +1416,7 @@ make_noise_status (struct app_context *ctx)
static void
refresh_status (struct app_context *ctx)
{
- if (ctx->prefix) ctx->backend->add (ctx->backend, ctx->prefix);
-
- if (ctx->mpd_status) ctx->backend->add (ctx->backend, ctx->mpd_status);
+ if (ctx->mpd_stopped) ctx->backend->add (ctx->backend, "MPD stopped");
else if (ctx->mpd_song) ctx->backend->add (ctx->backend, ctx->mpd_song);
if (ctx->noise_end_time)
@@ -1415,7 +1603,8 @@ static void
on_command_start (void *user_data)
{
struct app_context *ctx = user_data;
- char *command = str_map_find (&ctx->config, "command");
+ const char *command =
+ get_config_string (ctx->config.root, "general.command");
if (!command)
return;
@@ -1435,7 +1624,7 @@ on_command_start (void *user_data)
posix_spawn_file_actions_addclose (&actions, output_pipe[PIPE_WRITE]);
pid_t pid = -1;
- char *argv[] = { "sh", "-c", command, NULL };
+ char *argv[] = { "sh", "-c", (char *) command, NULL };
int result = posix_spawnp (&pid, argv[0], &actions, NULL, argv, environ);
posix_spawn_file_actions_destroy (&actions);
@@ -1495,9 +1684,8 @@ mpd_on_info_response (const struct mpd_response *response,
struct str_map map;
mpd_vector_to_map (data, &map);
- cstr_set (&ctx->mpd_status, NULL);
-
struct str s = str_make ();
+ ctx->mpd_stopped = false;
const char *value;
if ((value = str_map_find (&map, "state")))
@@ -1505,7 +1693,7 @@ mpd_on_info_response (const struct mpd_response *response,
// Unicode approximates since in proportional fonts ASCII looks ugly
// and I don't want to depend on a particular font with player chars
if (!strcmp (value, "stop"))
- ctx->mpd_status = xstrdup ("MPD stopped");
+ ctx->mpd_stopped = true;
else if (!strcmp (value, "pause"))
str_append (&s, "▯▯ " /* "|| " */);
else
@@ -1585,7 +1773,7 @@ mpd_on_connected (void *user_data)
struct app_context *ctx = user_data;
struct mpd_client *c = &ctx->mpd_client;
- const char *password = str_map_find (&ctx->config, "mpd_password");
+ const char *password = get_config_string (ctx->config.root, "mpd.password");
if (password)
{
mpd_client_send_command (c, "password", password, NULL);
@@ -1602,6 +1790,10 @@ mpd_on_failure (void *user_data)
struct app_context *ctx = user_data;
print_error ("connection to MPD failed");
mpd_queue_reconnect (ctx);
+
+ cstr_set (&ctx->mpd_song, NULL);
+ ctx->mpd_stopped = false;
+ refresh_status (ctx);
}
static void
@@ -1628,9 +1820,10 @@ on_mpd_reconnect (void *user_data)
c->on_io_hook = mpd_on_io_hook;
struct error *e = NULL;
+ struct config_item *root = ctx->config.root;
if (!mpd_client_connect (&ctx->mpd_client,
- str_map_find (&ctx->config, "mpd_address"),
- str_map_find (&ctx->config, "mpd_service"), &e))
+ get_config_string (root, "mpd.address"),
+ get_config_string (root, "mpd.service"), &e))
{
print_error ("%s: %s", "cannot connect to MPD", e->message);
error_free (e);
@@ -1688,6 +1881,7 @@ nut_process_ups (struct app_context *ctx, struct strv *ups_list,
const char *charge = str_map_find (dict, "battery.charge");
const char *runtime = str_map_find (dict, "battery.runtime");
const char *load = str_map_find (dict, "ups.load");
+ const char *power = str_map_find (dict, "ups.realpower.nominal");
if (!soft_assert (status && charge && runtime))
return;
@@ -1717,24 +1911,25 @@ nut_process_ups (struct app_context *ctx, struct strv *ups_list,
strv_append_owned (&items, interval_string (runtime_sec));
// Only show load if it's higher than the threshold so as to not distract
- const char *threshold = str_map_find (&ctx->config, "nut_load_thld");
- unsigned long load_n, threshold_n;
+ struct config_item *root = ctx->config.root;
+ const int64_t *threshold = get_config_integer (root, "nut.load_thld");
+ const int64_t *fallback = get_config_integer (root, "nut.load_power");
+ unsigned long load_n, power_n;
if (load
- && xstrtoul (&load_n, load, 10)
- && xstrtoul (&threshold_n, threshold, 10)
- && load_n >= threshold_n)
+ && xstrtoul (&load_n, load, 10)
+ && load_n >= (unsigned long) *threshold)
{
struct str item = str_make ();
str_append_printf (&item, "load %s%%", load);
- const char *power = str_map_find (dict, "ups.realpower.nominal");
- // Override if NUT cannot tell it correctly for whatever reason
- if (!power) power = str_map_find (&ctx->config, "nut_load_power");
-
- // Approximation of how much electricity the perpihery actually uses
- unsigned long power_n;
+ // Approximation of how much electricity the perpihery actually uses.
+ // Use fallback if NUT cannot tell it correctly for whatever reason.
if (power && xstrtoul (&power_n, power, 10))
- str_append_printf (&item, " (~%luW)", power_n * load_n / 100);
+ str_append_printf (&item,
+ " (~%luW)", power_n * load_n / 100);
+ else if (fallback && *fallback >= 0)
+ str_append_printf (&item,
+ " (~%luW)", (unsigned long) *fallback * load_n / 100);
strv_append_owned (&items, str_steal (&item));
}
@@ -1877,12 +2072,7 @@ static void
on_nut_reconnect (void *user_data)
{
struct app_context *ctx = user_data;
-
- bool want_nut = false;
- if (!set_boolean_if_valid (&want_nut,
- str_map_find (&ctx->config, "nut_enabled")))
- print_error ("invalid configuration value for `%s'", "nut_enabled");
- if (!want_nut)
+ if (!*get_config_boolean (ctx->config.root, "nut.enabled"))
return;
struct nut_client *c = &ctx->nut_client;
@@ -2048,15 +2238,33 @@ on_noise_timer (void *user_data)
}
static void
-on_noise_adjust (struct app_context *ctx, int arg)
+action_noise_adjust (struct app_context *ctx, const struct strv *args)
{
+ if (args->len != 1)
+ {
+ print_error ("usage: noise-adjust +/-HOURS");
+ return;
+ }
+
+ long arg = strtol (args->vector[0], NULL, 10);
ctx->noise_fadeout_samples = 0;
ctx->noise_fadeout_iterator = 0;
- if (!ctx->noise_end_time && (arg < 0 || !noise_start (ctx)))
+ if (!ctx->noise_end_time && (arg <= 0 || !noise_start (ctx)))
return;
- // The granularity of noise playback is whole minutes
- ctx->noise_end_time += arg * 60;
+ time_t now = time (NULL);
+ int diff = difftime (ctx->noise_end_time, now);
+
+ // The granularity of noise playback setting is whole hours.
+ enum { SECOND = 1, MINUTE = 60, HOUR = 3600 };
+ if (arg > 0)
+ // Add a minute to enable stepping up from 0:59 to 2:00.
+ diff = (diff + arg * HOUR + MINUTE) / HOUR * HOUR;
+ else if (arg++ < 0)
+ // Remove a second to enable stepping down from 2:00 to 1:00.
+ diff = (diff + arg * HOUR - SECOND) / HOUR * HOUR;
+
+ ctx->noise_end_time = now + diff;
on_noise_timer (ctx);
}
@@ -2198,27 +2406,35 @@ spawn (char *argv[])
posix_spawn_file_actions_destroy (&actions);
}
-#define MPD_SIMPLE(name, ...) \
- static void \
- on_mpd_ ## name (struct app_context *ctx, int arg) \
- { \
- (void) arg; \
- struct mpd_client *c = &ctx->mpd_client; \
- if (c->state != MPD_CONNECTED) \
- return; \
- mpd_client_send_command (c, __VA_ARGS__); \
- mpd_client_add_task (c, NULL, NULL); \
- mpd_client_idle (c, 0); \
- }
+static void
+action_exec (struct app_context *ctx, const struct strv *args)
+{
+ (void) ctx;
+ spawn (args->vector);
+}
-// XXX: pause without argument is deprecated, we can watch play state
-// if we want to have the toggle pause/play functionality
-MPD_SIMPLE (play, "pause", NULL)
-MPD_SIMPLE (stop, "stop", NULL)
-MPD_SIMPLE (prev, "previous", NULL)
-MPD_SIMPLE (next, "next", NULL)
-MPD_SIMPLE (forward, "seekcur", "+10", NULL)
-MPD_SIMPLE (backward, "seekcur", "-10", NULL)
+static void
+action_mpd (struct app_context *ctx, const struct strv *args)
+{
+ struct mpd_client *c = &ctx->mpd_client;
+ if (c->state != MPD_CONNECTED)
+ return;
+ mpd_client_send_commandv (c, args->vector);
+ mpd_client_add_task (c, NULL, NULL);
+ mpd_client_idle (c, 0);
+}
+
+static void
+action_mpd_play_toggle (struct app_context *ctx, const struct strv *args)
+{
+ (void) args;
+ struct mpd_client *c = &ctx->mpd_client;
+ if (c->state != MPD_CONNECTED)
+ return;
+ mpd_client_send_command (c, ctx->mpd_stopped ? "play" : "pause", NULL);
+ mpd_client_add_task (c, NULL, NULL);
+ mpd_client_idle (c, 0);
+}
static void
on_volume_finish (pa_context *context, int success, void *userdata)
@@ -2231,9 +2447,9 @@ on_volume_finish (pa_context *context, int success, void *userdata)
}
static void
-on_volume_mic_mute (struct app_context *ctx, int arg)
+action_audio_mic_mute (struct app_context *ctx, const struct strv *args)
{
- (void) arg;
+ (void) args;
if (!ctx->context)
return;
@@ -2243,9 +2459,9 @@ on_volume_mic_mute (struct app_context *ctx, int arg)
}
static void
-on_volume_switch (struct app_context *ctx, int arg)
+action_audio_switch (struct app_context *ctx, const struct strv *args)
{
- (void) arg;
+ (void) args;
if (!ctx->context || !ctx->sink_port_active || !ctx->sink_ports.len)
return;
@@ -2262,9 +2478,9 @@ on_volume_switch (struct app_context *ctx, int arg)
}
static void
-on_volume_mute (struct app_context *ctx, int arg)
+action_audio_mute (struct app_context *ctx, const struct strv *args)
{
- (void) arg;
+ (void) args;
if (!ctx->context)
return;
@@ -2274,14 +2490,20 @@ on_volume_mute (struct app_context *ctx, int arg)
}
static void
-on_volume_set (struct app_context *ctx, int arg)
+action_audio_volume (struct app_context *ctx, const struct strv *args)
{
+ if (args->len != 1)
+ {
+ print_error ("usage: audio-volume +/-PERCENT");
+ return;
+ }
if (!ctx->context)
return;
+ long arg = strtol (args->vector[0], NULL, 10);
pa_cvolume volume = ctx->sink_volume;
if (arg > 0)
- pa_cvolume_inc (&volume, (pa_volume_t) arg * PA_VOLUME_NORM / 100);
+ pa_cvolume_inc (&volume, (pa_volume_t) +arg * PA_VOLUME_NORM / 100);
else
pa_cvolume_dec (&volume, (pa_volume_t) -arg * PA_VOLUME_NORM / 100);
pa_operation_unref (pa_context_set_sink_volume_by_name (ctx->context,
@@ -2289,51 +2511,6 @@ on_volume_set (struct app_context *ctx, int arg)
}
static void
-on_lock (struct app_context *ctx, int arg)
-{
- (void) ctx;
- (void) arg;
-
- // One of these will work
- char *argv_gdm[] = { "gdm-switch-user", NULL };
- spawn (argv_gdm);
- char *argv_ldm[] = { "dm-tool", "lock", NULL };
- spawn (argv_ldm);
-}
-
-static void
-on_input_switch (struct app_context *ctx, int arg)
-{
- (void) ctx;
-
- char *values[] = { "vga", "dvi", "hdmi", "dp" },
- *numbers[] = { "1", "2" };
- char *argv[] = { "input-switch",
- values[arg & 0xf], numbers[arg >> 4], NULL };
- spawn (argv);
-}
-
-static void
-on_brightness (struct app_context *ctx, int arg)
-{
- (void) ctx;
- char *value = xstrdup_printf ("%d", arg);
- char *argv[] = { "brightness", value, NULL };
- spawn (argv);
- free (value);
-}
-
-static void
-on_standby (struct app_context *ctx, int arg)
-{
- (void) ctx;
- (void) arg;
-
- // We need to wait a little while until user releases the key
- spawn ((char *[]) { "sh", "-c", "sleep 1; xset dpms force standby", NULL });
-}
-
-static void
go_insomniac (struct app_context *ctx)
{
static const char *what = "sleep:idle";
@@ -2378,9 +2555,9 @@ go_insomniac (struct app_context *ctx)
}
static void
-on_insomnia (struct app_context *ctx, int arg)
+action_insomnia (struct app_context *ctx, const struct strv *args)
{
- (void) arg;
+ (void) args;
cstr_set (&ctx->insomnia_info, NULL);
// Get rid of the lock if we hold one, establish it otherwise
@@ -2396,102 +2573,70 @@ on_insomnia (struct app_context *ctx, int arg)
}
static void
-on_lock_group (struct app_context *ctx, int arg)
+action_xkb_lock_group (struct app_context *ctx, const struct strv *args)
{
- XkbLockGroup (ctx->dpy, XkbUseCoreKbd, arg);
+ if (args->len != 1)
+ {
+ print_error ("usage: xkb-lock-group GROUP");
+ return;
+ }
+
+ long group = strtol (args->vector[0], NULL, 10);
+ if (group < XkbGroup1Index || group > XkbGroup4Index)
+ print_warning ("invalid XKB group index: %s", args->vector[0]);
+ else
+ XkbLockGroup (ctx->dpy, XkbUseCoreKbd, group);
}
-struct
+static const struct action
{
- unsigned mod;
- KeySym keysym;
- void (*handler) (struct app_context *ctx, int arg);
- int arg;
+ const char *name;
+ void (*handler) (struct app_context *ctx, const struct strv *args);
}
-g_keys[] =
+g_handlers[] =
{
- // This key should be labeled L on normal Qwert[yz] layouts
- { Mod4Mask, XK_n, on_lock, 0 },
-
- // xmodmap | grep -e Alt_R -e Meta_R -e ISO_Level3_Shift -e Mode_switch
- // can be used to figure out which modifier is AltGr
-
- // MPD
- { Mod4Mask, XK_Up, on_mpd_play, 0 },
- { Mod4Mask, XK_Down, on_mpd_stop, 0 },
- { Mod4Mask, XK_Left, on_mpd_prev, 0 },
- { Mod4Mask, XK_Right, on_mpd_next, 0 },
- { Mod4Mask | ShiftMask, XK_Left, on_mpd_backward, 0 },
- { Mod4Mask | ShiftMask, XK_Right, on_mpd_forward, 0 },
- { 0, XF86XK_AudioPlay, on_mpd_play, 0 },
- { 0, XF86XK_AudioPrev, on_mpd_prev, 0 },
- { 0, XF86XK_AudioNext, on_mpd_next, 0 },
-
- // Keyboard groups
- { Mod4Mask, XK_F1, on_lock_group, 0 },
- { Mod4Mask, XK_F2, on_lock_group, 1 },
- { Mod4Mask, XK_F3, on_lock_group, 2 },
- { Mod4Mask, XK_F4, on_lock_group, 3 },
-
-#define CSMask (ControlMask | ShiftMask)
-
- // Display input sources
- { Mod4Mask | ControlMask, XK_F1, on_input_switch, 0 },
- { Mod4Mask | CSMask, XK_F1, on_input_switch, 16 | 0 },
- { Mod4Mask | ControlMask, XK_F2, on_input_switch, 1 },
- { Mod4Mask | CSMask, XK_F2, on_input_switch, 16 | 1 },
- { Mod4Mask | ControlMask, XK_F3, on_input_switch, 2 },
- { Mod4Mask | CSMask, XK_F3, on_input_switch, 16 | 2 },
- { Mod4Mask | ControlMask, XK_F4, on_input_switch, 3 },
- { Mod4Mask | CSMask, XK_F4, on_input_switch, 16 | 3 },
-
- // Brightness
- { Mod4Mask, XK_Home, on_brightness, 10 },
- { Mod4Mask, XK_End, on_brightness, -10 },
- { 0, XF86XK_MonBrightnessUp, on_brightness, 10 },
- { 0, XF86XK_MonBrightnessDown, on_brightness, -10 },
-
- { Mod4Mask, XK_F5, on_standby, 0 },
- { Mod4Mask | ShiftMask, XK_F5, on_insomnia, 0 },
- { Mod4Mask, XK_Pause, on_standby, 0 },
- { Mod4Mask | ShiftMask, XK_Pause, on_insomnia, 0 },
+ { "exec", action_exec },
+ { "mpd", action_mpd },
+ { "mpd-play-toggle", action_mpd_play_toggle },
+ { "xkb-lock-group", action_xkb_lock_group },
+ { "insomnia", action_insomnia },
+ { "audio-switch", action_audio_switch },
+ { "audio-mute", action_audio_mute },
+ { "audio-mic-mute", action_audio_mic_mute },
+ { "audio-volume", action_audio_volume },
+ { "noise-adjust", action_noise_adjust },
+};
- // Volume
- { Mod4Mask, XK_Insert, on_volume_switch, 0 },
- { Mod4Mask, XK_Delete, on_volume_mute, 0 },
- { Mod4Mask | ShiftMask, XK_Delete, on_volume_mic_mute, 0 },
- { Mod4Mask, XK_Page_Up, on_volume_set, 5 },
- { Mod4Mask | ShiftMask, XK_Page_Up, on_volume_set, 1 },
- { Mod4Mask, XK_Page_Down, on_volume_set, -5 },
- { Mod4Mask | ShiftMask, XK_Page_Down, on_volume_set, -1 },
- { 0, XF86XK_AudioRaiseVolume, on_volume_set, 5 },
- { ShiftMask, XF86XK_AudioRaiseVolume, on_volume_set, 1 },
- { 0, XF86XK_AudioLowerVolume, on_volume_set, -5 },
- { ShiftMask, XF86XK_AudioLowerVolume, on_volume_set, -1 },
- { 0, XF86XK_AudioMute, on_volume_mute, 0 },
- { 0, XF86XK_AudioMicMute, on_volume_mic_mute, 0 },
+struct binding
+{
+ LIST_HEADER (struct binding)
- // Noise playback
- { ControlMask, XF86XK_AudioRaiseVolume, on_noise_adjust, 60 },
- { ControlMask, XF86XK_AudioLowerVolume, on_noise_adjust, -60 },
+ unsigned mods; ///< Modifiers
+ KeyCode keycode; ///< Key code
+ struct action handler; ///< Handling procedure
+ struct strv args; ///< Arguments to the handler
};
+static struct action
+action_by_name (const char *name)
+{
+ for (size_t i = 0; i < N_ELEMENTS (g_handlers); i++)
+ if (!strcmp (g_handlers[i].name, name))
+ return g_handlers[i];
+ return (struct action) {};
+}
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
static void
on_x_keypress (struct app_context *ctx, XEvent *e)
{
XKeyEvent *ev = &e->xkey;
- unsigned unconsumed_mods;
- KeySym keysym;
- if (!XkbLookupKeySym (ctx->dpy,
- (KeyCode) ev->keycode, ev->state, &unconsumed_mods, &keysym))
- return;
- for (size_t i = 0; i < N_ELEMENTS (g_keys); i++)
- if (g_keys[i].keysym == keysym
- && g_keys[i].mod == ev->state
- && g_keys[i].handler)
- g_keys[i].handler (ctx, g_keys[i].arg);
+ LIST_FOR_EACH (struct binding, iter, ctx->bindings)
+ if (iter->keycode == ev->keycode
+ && iter->mods == ev->state
+ && iter->handler.handler)
+ iter->handler.handler (ctx, &iter->args);
}
static void
@@ -2546,33 +2691,143 @@ on_x_ready (const struct pollfd *pfd, void *user_data)
}
}
-static void
-init_xlib_events (struct app_context *ctx)
+static bool
+parse_key_modifier (const char *modifier, unsigned *mods)
{
- unsigned long n;
- const char *sleep_timer = str_map_find (&ctx->config, "sleep_timer");
- if (sleep_timer && ctx->idle_counter)
+ static const struct
{
- if (!xstrtoul (&n, sleep_timer, 10) || !n || n > INT_MAX / 1000)
- exit_fatal ("invalid value for the sleep timer");
- XSyncIntToValue (&ctx->idle_timeout, n * 1000);
- set_idle_alarm (ctx, &ctx->idle_alarm_inactive,
- XSyncPositiveComparison, ctx->idle_timeout);
+ const char *name;
+ unsigned mask;
+ }
+ modifiers[] =
+ {
+ { "Shift", ShiftMask },
+ { "Lock", LockMask },
+ { "Control", ControlMask },
+ { "Mod1", Mod1Mask },
+ { "Mod2", Mod2Mask },
+ { "Mod3", Mod3Mask },
+ { "Mod4", Mod4Mask },
+ { "Mod5", Mod5Mask },
+ };
+
+ for (size_t k = 0; k < N_ELEMENTS (modifiers); k++)
+ if (!strcasecmp_ascii (modifiers[k].name, modifier))
+ {
+ *mods |= modifiers[k].mask;
+ return true;
+ }
+ return false;
+}
+
+static bool
+parse_key_vector (const struct strv *keys, unsigned *mods, KeySym *keysym)
+{
+ for (size_t i = 0; i < keys->len; i++)
+ {
+ if (parse_key_modifier (keys->vector[i], mods))
+ continue;
+ if (*keysym)
+ return false;
+ *keysym = XStringToKeysym (keys->vector[i]);
+ }
+ return *keysym != 0;
+}
+
+static bool
+parse_key_combination (const char *combination, unsigned *mods, KeySym *keysym)
+{
+ struct strv keys = strv_make ();
+ bool result = parse_binding (combination, &keys)
+ && parse_key_vector (&keys, mods, keysym);
+ strv_free (&keys);
+ return result;
+}
+
+static const char *
+init_grab (struct app_context *ctx, const char *combination, const char *action)
+{
+ unsigned mods = 0;
+ KeySym keysym = 0;
+ if (!parse_key_combination (combination, &mods, &keysym))
+ return "parsing key combination failed";
+
+ KeyCode keycode = XKeysymToKeycode (ctx->dpy, keysym);
+ if (!keycode)
+ return "no keycode found";
+
+ struct strv args = strv_make ();
+ if (!parse_binding (action, &args) || !args.len)
+ {
+ strv_free (&args);
+ return "parsing the binding failed";
}
+ struct action handler = action_by_name (args.vector[0]);
+ free (strv_steal (&args, 0));
+ if (!handler.name)
+ {
+ strv_free (&args);
+ return "unknown action";
+ }
+
+ XGrabKey (ctx->dpy, keycode, mods, DefaultRootWindow (ctx->dpy),
+ False /* ? */, GrabModeAsync, GrabModeAsync);
+
+ struct binding *binding = xcalloc (1, sizeof *binding);
+ binding->mods = mods;
+ binding->keycode = keycode;
+ binding->handler = handler;
+ binding->args = args;
+ LIST_PREPEND (ctx->bindings, binding);
+ return NULL;
+}
+
+static void
+init_bindings (struct app_context *ctx)
+{
unsigned ignored_locks =
LockMask | XkbKeysymToModifiers (ctx->dpy, XK_Num_Lock);
hard_assert (XkbSetIgnoreLockMods
(ctx->dpy, XkbUseCoreKbd, ignored_locks, ignored_locks, 0, 0));
- KeyCode code;
- Window root = DefaultRootWindow (ctx->dpy);
- for (size_t i = 0; i < N_ELEMENTS (g_keys); i++)
- if ((code = XKeysymToKeycode (ctx->dpy, g_keys[i].keysym)))
- XGrabKey (ctx->dpy, code, g_keys[i].mod, root,
- False /* ? */, GrabModeAsync, GrabModeAsync);
+ struct str_map *keys =
+ &config_item_get (ctx->config.root, "keys", NULL)->value.object;
+ struct str_map_iter iter = str_map_iter_make (keys);
- XSelectInput (ctx->dpy, root, KeyPressMask);
+ struct config_item *action;
+ while ((action = str_map_iter_next (&iter)))
+ {
+ const char *combination = iter.link->key, *err = NULL;
+ if (action->type != CONFIG_ITEM_NULL)
+ {
+ if (action->type != CONFIG_ITEM_STRING)
+ err = "expected a string";
+ else
+ err = init_grab (ctx, combination, action->value.string.str);
+ }
+ if (err)
+ print_warning ("configuration: key `%s': %s", combination, err);
+ }
+
+ XSelectInput (ctx->dpy, DefaultRootWindow (ctx->dpy), KeyPressMask);
+}
+
+static void
+init_xlib_events (struct app_context *ctx)
+{
+ const int64_t *sleep_timer =
+ get_config_integer (ctx->config.root, "general.sleep_timer");
+ if (sleep_timer && ctx->idle_counter)
+ {
+ if (*sleep_timer <= 0 || *sleep_timer > INT_MAX / 1000)
+ exit_fatal ("invalid value for the sleep timer");
+ XSyncIntToValue (&ctx->idle_timeout, *sleep_timer * 1000);
+ set_idle_alarm (ctx, &ctx->idle_alarm_inactive,
+ XSyncPositiveComparison, ctx->idle_timeout);
+ }
+
+ init_bindings (ctx);
XSync (ctx->dpy, False);
ctx->x_event.dispatcher = on_x_ready;
@@ -2586,8 +2841,323 @@ init_xlib_events (struct app_context *ctx)
XkbAllStateComponentsMask, XkbGroupStateMask);
}
+// --- IPC ---------------------------------------------------------------------
+
+#define IPC_SOCKET "ipc.socket"
+
+static void
+on_ipc_message (struct app_context *ctx, const char *message, size_t len)
+{
+ struct action handler = action_by_name (message);
+ if (!handler.handler)
+ {
+ print_error ("ipc: %s: %s", "unknown action", message);
+ return;
+ }
+
+ struct strv args = strv_make ();
+ const char *p = memchr (message, 0, len);
+ while (p)
+ {
+ strv_append (&args, ++p);
+ p = memchr (p, 0, len - (p - message));
+ }
+
+ handler.handler (ctx, &args);
+ strv_free (&args);
+}
+
+static void
+on_ipc_ready (const struct pollfd *pfd, void *user_data)
+{
+ struct app_context *ctx = user_data;
+ char buf[65536] = {};
+
+ while (true)
+ {
+ ssize_t len = read (pfd->fd, buf, sizeof buf - 1 /* NUL-terminated */);
+ if (len >= 0)
+ {
+ buf[len] = 0;
+ on_ipc_message (ctx, buf, len);
+ }
+ else if (errno == EAGAIN)
+ return;
+ else if (errno != EINTR)
+ print_warning ("ipc: %s: %s", "read", strerror (errno));
+
+ }
+}
+
+static void
+app_setup_ipc (struct app_context *ctx)
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ if (fd == -1)
+ {
+ print_error ("ipc: %s: %s", "socket", strerror (errno));
+ return;
+ }
+
+ set_cloexec (fd);
+ char *path = resolve_relative_runtime_filename (IPC_SOCKET);
+
+ // This is unfortunately the only way to prevent EADDRINUSE.
+ unlink (path);
+
+ struct sockaddr_un sa = { .sun_family = AF_UNIX };
+ strncpy (sa.sun_path, path, sizeof sa.sun_path - 1);
+ if (bind (fd, (struct sockaddr *) &sa, sizeof sa))
+ {
+ print_error ("ipc: %s: %s", path, strerror (errno));
+ xclose (fd);
+ goto out;
+ }
+
+ set_blocking (fd, false);
+ ctx->ipc_fd = fd;
+ ctx->ipc_event = poller_fd_make (&ctx->poller, fd);
+ ctx->ipc_event.dispatcher = on_ipc_ready;
+ ctx->ipc_event.user_data = ctx;
+ poller_fd_set (&ctx->ipc_event, POLLIN);
+out:
+ free (path);
+}
+
+static int
+ipc_send (int argc, char *argv[])
+{
+ int fd = socket (AF_UNIX, SOCK_DGRAM, 0);
+ if (fd == -1)
+ print_fatal ("ipc: %s: %s", "socket", strerror (errno));
+
+ struct str message = str_make ();
+ for (int i = 0; i < argc; i++)
+ {
+ if (i > 0)
+ str_append_c (&message, 0);
+ str_append (&message, argv[i]);
+ }
+
+ char *path = resolve_relative_runtime_filename (IPC_SOCKET);
+ struct sockaddr_un sa = { .sun_family = AF_UNIX };
+ strncpy (sa.sun_path, path, sizeof sa.sun_path - 1);
+
+ int result = EXIT_FAILURE;
+ ssize_t sent = sendto (fd, message.str, message.len, 0,
+ (struct sockaddr *) &sa, sizeof sa);
+ if (sent < 0)
+ print_error ("ipc: %s: %s", path, strerror (errno));
+ else if (sent != (ssize_t) message.len)
+ print_error ("ipc: %s: %s", path, "incomplete message sent");
+ else
+ result = 0;
+
+ free (path);
+ str_free (&message);
+ return result;
+}
+
+// --- Configuration -----------------------------------------------------------
+
+static void
+app_load_configuration (struct app_context *ctx)
+{
+ char *filename = resolve_filename
+ (PROGRAM_NAME ".conf", resolve_relative_config_filename);
+ if (!filename)
+ return;
+
+ struct error *e = NULL;
+ struct config_item *root = config_read_from_file (filename, &e);
+ free (filename);
+
+ if (e)
+ exit_fatal ("error loading configuration: %s", e->message);
+
+ if (root)
+ {
+ config_load (&ctx->config, root);
+ config_schema_call_changed (ctx->config.root);
+ }
+}
+
+static void
+app_save_configuration (struct app_context *ctx, const char *path_hint)
+{
+ static const char *prolog =
+ "# " PROGRAM_NAME " " PROGRAM_VERSION " configuration file\n\n";
+
+ struct str data = str_make ();
+ str_append (&data, prolog);
+ config_item_write (ctx->config.root, true, &data);
+
+ struct error *e = NULL;
+ char *filename = write_configuration_file (path_hint, &data, &e);
+ str_free (&data);
+
+ if (!filename)
+ {
+ print_error ("%s", e->message);
+ error_free (e);
+ exit (EXIT_FAILURE);
+ }
+ print_status ("configuration written to `%s'", filename);
+ free (filename);
+}
+
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+static bool
+sway_command_argument_needs_quoting (const char *word)
+{
+ while (*word)
+ if (!isalnum_ascii (*word++))
+ return true;
+ return false;
+}
+
+static void
+sway_append_command_argument (struct str *out, const char *word)
+{
+ if (out->len)
+ str_append_c (out, ' ');
+
+ if (!sway_command_argument_needs_quoting (word))
+ {
+ str_append (out, word);
+ return;
+ }
+
+ str_append_c (out, '\'');
+ for (const char *p = word; *p; p++)
+ {
+ if (*p == '\'' || *p == '\\')
+ str_append_c (out, '\\');
+ str_append_c (out, *p);
+ }
+ str_append_c (out, '\'');
+}
+
+static char *
+sway_shell_command_for_action (const struct strv *args)
+{
+ // The i3/Sway quoting is properly fucked up,
+ // and its exec command forwards to `sh -c`.
+ struct str shell_command = str_make ();
+ if (strcmp (args->vector[0], "exec"))
+ {
+ // argv[0] would need realpath() applied on it.
+ shell_quote (PROGRAM_NAME, &shell_command);
+ str_append (&shell_command, " -- ");
+ shell_quote (args->vector[0], &shell_command);
+ str_append_c (&shell_command, ' ');
+ }
+ for (size_t i = 1; i < args->len; i++)
+ {
+ shell_quote (args->vector[i], &shell_command);
+ str_append_c (&shell_command, ' ');
+ }
+ if (shell_command.len)
+ shell_command.str[--shell_command.len] = 0;
+ return str_steal (&shell_command);
+}
+
+static const char *
+sway_bindsym (const char *combination, const char *action)
+{
+ const char *error = NULL;
+ struct strv keys = strv_make ();
+ struct strv args = strv_make ();
+ if (!parse_binding (combination, &keys))
+ {
+ error = "parsing key combination failed";
+ goto out;
+ }
+ if (!parse_binding (action, &args) || !args.len)
+ {
+ error = "parsing the binding failed";
+ goto out;
+ }
+
+ struct action handler = action_by_name (args.vector[0]);
+ if (!handler.name)
+ {
+ error = "unknown action";
+ goto out;
+ }
+
+ // This command name may not be quoted.
+ // Note that i3-msg doesn't accept bindsym at all, only swaymsg does.
+ struct str sway_command = str_make ();
+ sway_append_command_argument (&sway_command, "bindsym");
+ char *recombined = strv_join (&keys, "+");
+ sway_append_command_argument (&sway_command, recombined);
+ free (recombined);
+
+ if (handler.handler == action_xkb_lock_group)
+ {
+ // This should also switch the XWayland layout,
+ // though it has been observed to not take effect immediately.
+ sway_append_command_argument (&sway_command, "input");
+ sway_append_command_argument (&sway_command, "type:keyboard");
+ sway_append_command_argument (&sway_command, "xkb_switch_layout");
+ for (size_t i = 1; i < args.len; i++)
+ sway_append_command_argument (&sway_command, args.vector[i]);
+ }
+ else
+ {
+ sway_append_command_argument (&sway_command, "exec");
+ char *shell_command = sway_shell_command_for_action (&args);
+ sway_append_command_argument (&sway_command, shell_command);
+ free (shell_command);
+ }
+
+ struct strv argv = strv_make ();
+ strv_append (&argv, "swaymsg");
+ strv_append_owned (&argv, str_steal (&sway_command));
+
+ posix_spawn_file_actions_t actions;
+ posix_spawn_file_actions_init (&actions);
+ posix_spawnp (NULL, argv.vector[0], &actions, NULL, argv.vector, environ);
+ posix_spawn_file_actions_destroy (&actions);
+
+ strv_free (&argv);
+out:
+ strv_free (&keys);
+ strv_free (&args);
+ return error;
+}
+
+static void
+sway_forward_bindings (void)
+{
+ // app_context_init() has side-effects.
+ struct app_context ctx = { .config = app_make_config () };
+ app_load_configuration (&ctx);
+
+ struct str_map *keys =
+ &config_item_get (ctx.config.root, "keys", NULL)->value.object;
+ struct str_map_iter iter = str_map_iter_make (keys);
+
+ struct config_item *action;
+ while ((action = str_map_iter_next (&iter)))
+ {
+ const char *combination = iter.link->key, *err = NULL;
+ if (action->type != CONFIG_ITEM_NULL)
+ {
+ if (action->type != CONFIG_ITEM_STRING)
+ err = "expected a string";
+ else
+ err = sway_bindsym (combination, action->value.string.str);
+ }
+ if (err)
+ print_warning ("configuration: key `%s': %s", combination, err);
+ }
+}
+
+// --- Signals -----------------------------------------------------------------
+
static int g_signal_pipe[2]; ///< A pipe used to signal... signals
static struct poller_fd g_signal_event; ///< Signal pipe is readable
@@ -2650,6 +3220,8 @@ setup_signal_handlers (struct app_context *ctx)
poller_fd_set (&g_signal_event, POLLIN);
}
+// --- Initialisation, event handling ------------------------------------------
+
static void
poller_timer_init_and_set (struct poller_timer *self, struct poller *poller,
poller_timer_fn cb, void *user_data)
@@ -2671,15 +3243,16 @@ main (int argc, char *argv[])
{ 'd', "debug", NULL, 0, "run in debug mode" },
{ 'h', "help", NULL, 0, "display this help and exit" },
{ 'V', "version", NULL, 0, "output version information and exit" },
- { '3', "i3bar", NULL, 0, "print output for i3bar/sway-bar instead" },
+ { '3', "i3bar", NULL, 0, "print output for i3-bar/swaybar instead" },
+ { 's', "bind-sway", NULL, 0, "import bindings over swaymsg" },
{ 'w', "write-default-cfg", "FILENAME",
OPT_OPTIONAL_ARG | OPT_LONG_ONLY,
"write a default configuration file and exit" },
{ 0, NULL, NULL, 0, NULL }
};
- struct opt_handler oh =
- opt_handler_make (argc, argv, opts, NULL, "Set root window name.");
+ struct opt_handler oh = opt_handler_make (argc, argv, opts, "[ACTION...]",
+ "Set root window name.");
bool i3bar = false;
int c;
@@ -2698,9 +3271,16 @@ main (int argc, char *argv[])
case '3':
i3bar = true;
break;
+ case 's':
+ sway_forward_bindings ();
+ exit (EXIT_SUCCESS);
case 'w':
- call_simple_config_write_default (optarg, g_config_table);
+ {
+ // app_context_init() has side-effects.
+ struct app_context ctx = { .config = app_make_config () };
+ app_save_configuration (&ctx, optarg);
exit (EXIT_SUCCESS);
+ }
default:
print_error ("wrong options");
opt_handler_usage (&oh, stderr);
@@ -2711,16 +3291,15 @@ main (int argc, char *argv[])
argv += optind;
opt_handler_free (&oh);
+ if (argc > 0)
+ return ipc_send (argc, argv);
struct app_context ctx;
app_context_init (&ctx);
- ctx.prefix = argc > 1 ? argv[1] : NULL;
+ app_load_configuration (&ctx);
+ app_setup_ipc (&ctx);
setup_signal_handlers (&ctx);
- struct error *e = NULL;
- if (!simple_config_update_from_file (&ctx.config, &e))
- exit_fatal ("%s", e->message);
-
poller_timer_init_and_set (&ctx.time_changed, &ctx.poller,
on_time_changed, &ctx);
poller_timer_init_and_set (&ctx.make_context, &ctx.poller,
diff --git a/wmstatus.conf.example b/wmstatus.conf.example
new file mode 100644
index 0000000..8d6a7fb
--- /dev/null
+++ b/wmstatus.conf.example
@@ -0,0 +1,63 @@
+# vim: set ft=libertyconf:
+keys = {
+ # This key should be labeled L on normal Qwert[yz] layouts
+ "Mod4 n" = "exec dm-tool lock" # gdm-switch-user
+
+ # xmodmap grep -e Alt_R -e Meta_R -e ISO_Level3_Shift -e Mode_switch
+ # can be used to figure out which modifier is AltGr
+
+ "Mod4 Up" = "mpd-play-toggle"
+ "Mod4 Down" = "mpd stop"
+ "Mod4 Left" = "mpd previous"
+ "Mod4 Right" = "mpd next"
+ "Mod4 Shift Left" = "mpd seekcur -10"
+ "Mod4 Shift Right" = "mpd seekcur +10"
+ "XF86AudioPlay" = "mpd-play-toggle"
+ "XF86AudioPrev" = "mpd previous"
+ "XF86AudioNext" = "mpd next"
+
+ "Mod4 F1" = "xkb-lock-group 0"
+ "Mod4 F2" = "xkb-lock-group 1"
+ "Mod4 F3" = "xkb-lock-group 2"
+ "Mod4 F4" = "xkb-lock-group 3"
+
+ "Mod4 Control F1" = "exec input-switch vga 1"
+ "Mod4 Control Shift F1" = "exec input-switch vga 2"
+ "Mod4 Control F2" = "exec input-switch dvi 1"
+ "Mod4 Control Shift F2" = "exec input-switch dvi 2"
+ "Mod4 Control F3" = "exec input-switch hdmi 1"
+ "Mod4 Control Shift F3" = "exec input-switch hdmi 2"
+ "Mod4 Control F4" = "exec input-switch dp 1"
+ "Mod4 Control Shift F4" = "exec input-switch dp 2"
+
+ "Mod4 Home" = "exec brightness +10"
+ "Mod4 End" = "exec brightness -10"
+ "XF86MonBrightnessUp" = "exec brightness +10"
+ "XF86MonBrightnessDown" = "exec brightness -10"
+
+ # We need to wait a little while until user releases the key
+ "Mod4 F5" = "exec sh -c 'sleep 1; xset dpms force standby'"
+ "Mod4 Shift F5" = "insomnia"
+ "Mod4 Pause" = "exec sh -c 'sleep 1; xset dpms force standby'"
+ "Mod4 Shift Pause" = "insomnia"
+
+ "Mod4 Insert" = "audio-switch"
+ "Mod4 Delete" = "audio-mute"
+ "Mod4 Shift Delete" = "audio-mic-mute"
+ "Mod4 Page_Up" = "audio-volume +5"
+ "Mod4 Shift Page_Up" = "audio-volume +1"
+ "Mod4 Page_Down" = "audio-volume -5"
+ "Mod4 Shift Page_Down" = "audio-volume -1"
+ " XF86AudioRaiseVolume" = "audio-volume +5"
+ "Shift XF86AudioRaiseVolume" = "audio-volume +1"
+ " XF86AudioLowerVolume" = "audio-volume -5"
+ "Shift XF86AudioLowerVolume" = "audio-volume -1"
+ " XF86AudioMute" = "audio-mute"
+ " XF86AudioMicMute" = "audio-mic-mute"
+
+ "Control XF86AudioRaiseVolume" = "noise-adjust +1"
+ "Control XF86AudioLowerVolume" = "noise-adjust -1"
+
+ # Turns on or off Pioneer integrated amplifiers
+ "Mod4 Control Delete" = "exec elksmart-comm --nec A538"
+}