diff options
-rw-r--r-- | .clang-format | 32 | ||||
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | CMakeLists.txt | 98 | ||||
-rw-r--r-- | LICENSE | 2 | ||||
-rw-r--r-- | README.adoc | 11 | ||||
-rw-r--r-- | gdm-switch-user.c | 2 | ||||
-rw-r--r-- | iexec.c | 115 | ||||
-rw-r--r-- | input-switch.c | 129 | ||||
m--------- | liberty | 0 | ||||
-rw-r--r-- | paswitch.c | 13 | ||||
-rw-r--r-- | poller-pa.c | 361 | ||||
-rwxr-xr-x | wmstatus-weather.pl | 69 | ||||
-rw-r--r-- | wmstatus.c | 198 |
13 files changed, 458 insertions, 574 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..27838ac --- /dev/null +++ b/.clang-format @@ -0,0 +1,32 @@ +# clang-format is fairly limited, and these rules are approximate: +# - array initializers can get terribly mangled with clang-format 12.0, +# - sometimes it still aligns with space characters, +# - struct name NL { NL ... NL } NL name; is unachievable. +BasedOnStyle: GNU +ColumnLimit: 80 +IndentWidth: 4 +TabWidth: 4 +UseTab: ForContinuationAndIndentation +BreakBeforeBraces: Allman +SpaceAfterCStyleCast: true +AlignAfterOpenBracket: DontAlign +AlignOperands: DontAlign +AlignConsecutiveMacros: Consecutive +AllowAllArgumentsOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +IndentGotoLabels: false + +# IncludeCategories has some potential, but it may also break the build. +# Note that the documentation says the value should be "Never". +SortIncludes: false + +# This is a compromise, it generally works out aesthetically better. +BinPackArguments: false + +# Unfortunately, this can't be told to align to column 40 or so. +SpacesBeforeTrailingComments: 2 + +# liberty-specific macro body wrappers. +MacroBlockBegin: "BLOCK_START" +MacroBlockEnd: "BLOCK_END" +ForEachMacros: ["LIST_FOR_EACH"] @@ -7,3 +7,5 @@ /desktop-tools.files /desktop-tools.creator* /desktop-tools.includes +/desktop-tools.cflags +/desktop-tools.cxxflags diff --git a/CMakeLists.txt b/CMakeLists.txt index 1bf55c5..f1d4292 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,11 +1,14 @@ -cmake_minimum_required (VERSION 3.0) -project (desktop-tools VERSION 0.1.0 LANGUAGES C) +cmake_minimum_required (VERSION 3.10) +project (desktop-tools VERSION 0.1.0 DESCRIPTION "Desktop tools" LANGUAGES C) # Moar warnings +set (CMAKE_C_STANDARD 99) +set (CMAKE_C_STANDARD_REQUIRED ON) +set (CMAKE_C_EXTENSIONS OFF) + if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC) # -Wunused-function is pretty annoying here, as everything is static - set (wdisabled "-Wno-unused-function") - set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c99 -Wall -Wextra ${wdisabled}") + set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-function") endif () # Dependencies @@ -13,29 +16,50 @@ set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/liberty/cmake) include (AddThreads) find_package (PkgConfig REQUIRED) -pkg_check_modules (dependencies REQUIRED libpulse x11 xext xextproto dbus-1) +pkg_check_modules (x REQUIRED x11 xext xextproto) +pkg_check_modules (pulse REQUIRED libpulse) +pkg_check_modules (dbus REQUIRED dbus-1) pkg_check_modules (gdm gdm glib-2.0 gio-2.0) -include_directories (${dependencies_INCLUDE_DIRS}) +include_directories ( + ${x_INCLUDE_DIRS} ${pulse_INCLUDE_DIRS} ${dbus_INCLUDE_DIRS}) +link_directories ( + ${x_LIBRARY_DIRS} ${pulse_LIBRARY_DIRS} ${dbus_LIBRARY_DIRS}) option (WITH_GDM "Compile with GDM support" ${gdm_FOUND}) # Generate a configuration file -configure_file (${PROJECT_SOURCE_DIR}/config.h.in ${PROJECT_BINARY_DIR}/config.h) +configure_file (${PROJECT_SOURCE_DIR}/config.h.in + ${PROJECT_BINARY_DIR}/config.h) include_directories (${PROJECT_BINARY_DIR}) # Build -foreach (name brightness fancontrol-ng iexec input-switch priod siprandom) - add_executable (${name} ${name}.c) -endforeach () +set (targets wmstatus paswitch siprandom) +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) + # Only iexec could be made to use kqueue + list (APPEND targets fancontrol-ng priod iexec) +elseif ("${CMAKE_SYSTEM_NAME}" MATCHES BSD) + # Need this for SIGWINCH in FreeBSD and OpenBSD respectively; + # our POSIX version macros make it undefined + add_definitions (-D__BSD_VISIBLE=1 -D_BSD_SOURCE=1) +elseif (APPLE) + add_definitions (-D_DARWIN_C_SOURCE) +endif () -foreach (name big-brother paswitch wmstatus) +foreach (name big-brother ${targets}) add_executable (${name} ${name}.c) - target_link_libraries (${name} ${dependencies_LIBRARIES}) endforeach () + +target_link_libraries (big-brother ${x_LIBRARIES}) +target_link_libraries (paswitch ${pulse_LIBRARIES}) +target_link_libraries (wmstatus + ${x_LIBRARIES} ${pulse_LIBRARIES} ${dbus_LIBRARIES}) add_threads (wmstatus) if (WITH_GDM) include_directories (${gdm_INCLUDE_DIRS}) + link_directories (${gdm_LIBRARY_DIRS}) add_executable (gdm-switch-user gdm-switch-user.c) target_link_libraries (gdm-switch-user ${gdm_LIBRARIES}) endif () @@ -47,33 +71,47 @@ 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) - -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}") +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) + + # 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 () -install (TARGETS wmstatus paswitch brightness input-switch fancontrol-ng priod - iexec siprandom DESTINATION ${CMAKE_INSTALL_BINDIR}) +# These should be accessible by users, but need to touch system devices. +# Use the setuid bit, for simplicity. +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}) # CPack -set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Desktop tools") set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch") set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>") set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") @@ -1,4 +1,4 @@ -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. diff --git a/README.adoc b/README.adoc index 3c3d4d3..0bd0de4 100644 --- a/README.adoc +++ b/README.adoc @@ -5,9 +5,9 @@ desktop-tools 'desktop-tools' is a collection of tools to run my desktop that might be useful to other people as well: - - 'wmstatus' does literally everything my i3 doesn't but I'd like it to. It - includes PulseAudio volume management and hand-written NUT and MPD clients, - all in the name of liberation from GPL-licensed software of course + - 'wmstatus' does literally everything i3/sway don't but I'd like them to. + It includes PulseAudio volume management and custom-made NUT and MPD clients, + all in the name of liberation from GPL-licensed software, of course - 'paswitch' displays a list of all PulseAudio sinks and ports and allows switching between them, moving all playing inputs - 'brightness' allows me to change the brightness of w/e display device I have @@ -28,12 +28,11 @@ to other people as well: - 'big-brother' tracks the title of the active window and the idle state of the user and writes these events to standard output. -Don't expect them to work under any OS that isn't Linux. +Few of them are useful outside of Linux. Packages -------- -Regular releases are sporadic. git master should be stable enough. You can get -a package with the latest development version from Archlinux's AUR. +Regular releases are sporadic. git master should be stable enough. Building -------- 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[]) @@ -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" }, @@ -102,7 +124,7 @@ main (int argc, char *argv[]) }; struct opt_handler oh = opt_handler_make (argc, argv, opts, - "PROGRAM [ARG...]", "Run a program and restart on file change."); + "PROGRAM [ARG...]", "Run a program and restart it when it changes."); // We have to turn that off as it causes more trouble than what it's worth cstr_set (&oh.opt_string, xstrdup_printf ("+%s", oh.opt_string)); @@ -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 abf81d9..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,8 +28,59 @@ #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) { @@ -49,8 +100,31 @@ set_input_source (int fd, int input, struct error **e) return true; } + +static bool +set_bnq_kvm (int fd, int kvm, struct error **e) +{ + // This function does a leap of faith, should check the actual manufacturer + enum { VCP_BNQ_KVM = 0xE4 }; + + struct vcp_feature_readout readout = {}; + if (!vcp_get_feature (fd, VCP_BNQ_KVM, &readout, e)) + return false; + if (kvm < 0 || kvm > readout.max) + return error_set (e, "KVM index out of range"); + + uint8_t set_req[] = { VCP_BNQ_KVM, kvm >> 8, kvm }; + if (!ddc_send (fd, DDC_SET_VCP_FEATURE, set_req, sizeof set_req, e)) + return false; + + wait_ms (50); + + printf ("KVM set from %d to %d of %d\n", readout.cur, kvm, readout.max); + return true; +} + static void -i2c (int input) +i2c (ActionFunc action, int param) { DIR *dev = opendir ("/dev"); if (!dev) @@ -76,7 +150,7 @@ i2c (int input) struct error *e = NULL; if (!is_a_display (fd, &e) - || !set_input_source (fd, input, &e)) + || !action (fd, param, &e)) { printf ("%s\n", e->message); error_free (e); @@ -89,35 +163,6 @@ i2c (int input) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// 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 -}; - int main (int argc, char *argv[]) { @@ -125,20 +170,33 @@ 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)) { - i2c (input_source); + i2c (set_input_source, input_source); exit (EXIT_SUCCESS); } unsigned long index = 1; if (argc > 2 && !xstrtoul (&index, argv[2], 10)) exit_fatal ("given index is not a number: %s", argv[2]); + + // Manufacturer-specific, argument currently necessary, but we could rotate + if (argc > 2 && !strcasecmp (argv[1], "bnq-kvm")) + { + i2c (set_bnq_kvm, index); + exit (EXIT_SUCCESS); + } + for (size_t i = 0; i < N_ELEMENTS (g_inputs); i++) if (!strcasecmp_ascii (g_inputs[i].name, argv[1]) && g_inputs[i].index == (int) index) @@ -146,7 +204,6 @@ main (int argc, char *argv[]) if (!input_source) exit_fatal ("unknown input source: %s %lu", argv[1], index); - i2c (input_source); + i2c (set_input_source, input_source); return 0; } - diff --git a/liberty b/liberty -Subproject 69101eb1554ad2fca6de30cdbaccac076210d7e +Subproject ad5b2fb8cd4915de9c6d97c362839de02d4970a @@ -26,7 +26,7 @@ #undef PROGRAM_NAME #define PROGRAM_NAME "paswitch" #include "liberty/liberty.c" -#include "poller-pa.c" +#include "liberty/liberty-pulse.c" #include <locale.h> #include <wchar.h> @@ -143,6 +143,7 @@ struct app_context struct poller_timer tty_timer; ///< Terminal input timeout struct str tty_input_buffer; ///< Buffered terminal input + bool quitting; ///< Quitting requested pa_mainloop_api *api; ///< PulseAudio event loop proxy pa_context *context; ///< PulseAudio connection context @@ -683,7 +684,7 @@ on_action (struct app_context *ctx, enum action action) break; case ACTION_QUIT: - poller_pa_quit (ctx->api, 0); + ctx->quitting = true; case ACTION_NONE: break; } @@ -910,7 +911,7 @@ on_signal_pipe_readable (const struct pollfd *pfd, struct app_context *ctx) (void) read (pfd->fd, &id, 1); if (id == SIGINT || id == SIGTERM || id == SIGHUP) - poller_pa_quit (ctx->api, 0); + ctx->quitting = true; else if (id == SIGWINCH) poller_idle_set (&ctx->redraw_event); else @@ -1068,7 +1069,9 @@ main (int argc, char *argv[]) poller_timer_init_and_set (&ctx.make_context, &ctx.poller, on_make_context, &ctx); - int status = poller_pa_run (ctx.api); + while (!ctx.quitting) + poller_run (&ctx.poller); + app_context_free (&ctx); - return status; + return 0; } diff --git a/poller-pa.c b/poller-pa.c deleted file mode 100644 index 24d70a4..0000000 --- a/poller-pa.c +++ /dev/null @@ -1,361 +0,0 @@ -/* - * pa.c: PulseAudio mainloop abstraction - * - * Copyright (c) 2016 - 2017, 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 <pulse/mainloop.h> - -// --- PulseAudio mainloop abstraction ----------------------------------------- - -struct pa_io_event -{ - LIST_HEADER (pa_io_event) - - pa_mainloop_api *api; ///< Parent structure - struct poller_fd fd; ///< Underlying FD event - - pa_io_event_cb_t dispatch; ///< Dispatcher - pa_io_event_destroy_cb_t free; ///< Destroyer - void *user_data; ///< User data -}; - -struct pa_time_event -{ - LIST_HEADER (pa_time_event) - - pa_mainloop_api *api; ///< Parent structure - struct poller_timer timer; ///< Underlying timer event - - pa_time_event_cb_t dispatch; ///< Dispatcher - pa_time_event_destroy_cb_t free; ///< Destroyer - void *user_data; ///< User data -}; - -struct pa_defer_event -{ - LIST_HEADER (pa_defer_event) - - pa_mainloop_api *api; ///< Parent structure - struct poller_idle idle; ///< Underlying idle event - - pa_defer_event_cb_t dispatch; ///< Dispatcher - pa_defer_event_destroy_cb_t free; ///< Destroyer - void *user_data; ///< User data -}; - -struct poller_pa -{ - struct poller *poller; ///< The underlying event loop - int result; ///< Result on quit - bool running; ///< Not quitting - - pa_io_event *io_list; ///< I/O events - pa_time_event *time_list; ///< Timer events - pa_defer_event *defer_list; ///< Deferred events -}; - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static short -poller_pa_flags_to_events (pa_io_event_flags_t flags) -{ - short result = 0; - if (flags & PA_IO_EVENT_ERROR) result |= POLLERR; - if (flags & PA_IO_EVENT_HANGUP) result |= POLLHUP; - if (flags & PA_IO_EVENT_INPUT) result |= POLLIN; - if (flags & PA_IO_EVENT_OUTPUT) result |= POLLOUT; - return result; -} - -static pa_io_event_flags_t -poller_pa_events_to_flags (short events) -{ - pa_io_event_flags_t result = 0; - if (events & POLLERR) result |= PA_IO_EVENT_ERROR; - if (events & POLLHUP) result |= PA_IO_EVENT_HANGUP; - if (events & POLLIN) result |= PA_IO_EVENT_INPUT; - if (events & POLLOUT) result |= PA_IO_EVENT_OUTPUT; - return result; -} - -static struct timeval -poller_pa_get_current_time (void) -{ - struct timeval tv; -#ifdef _POSIX_TIMERS - struct timespec tp; - hard_assert (clock_gettime (CLOCK_REALTIME, &tp) != -1); - tv.tv_sec = tp.tv_sec; - tv.tv_usec = tp.tv_nsec / 1000; -#else - gettimeofday (&tv, NULL); -#endif - return tv; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -poller_pa_io_dispatcher (const struct pollfd *pfd, void *user_data) -{ - pa_io_event *self = user_data; - self->dispatch (self->api, self, - pfd->fd, poller_pa_events_to_flags (pfd->revents), self->user_data); -} - -static void -poller_pa_io_enable (pa_io_event *self, pa_io_event_flags_t events) -{ - struct poller_fd *fd = &self->fd; - if (events) - poller_fd_set (fd, poller_pa_flags_to_events (events)); - else - poller_fd_reset (fd); -} - -static pa_io_event * -poller_pa_io_new (pa_mainloop_api *api, int fd_, pa_io_event_flags_t events, - pa_io_event_cb_t cb, void *userdata) -{ - pa_io_event *self = xcalloc (1, sizeof *self); - self->api = api; - self->dispatch = cb; - self->user_data = userdata; - - struct poller_pa *data = api->userdata; - self->fd = poller_fd_make (data->poller, fd_); - self->fd.user_data = self; - self->fd.dispatcher = poller_pa_io_dispatcher; - - // FIXME: under x2go PA tries to register twice for the same FD, - // which fails with our curent poller implementation; - // we could maintain a list of { poller_fd, listeners } structures; - // or maybe we're doing something wrong, which is yet to be determined - poller_pa_io_enable (self, events); - LIST_PREPEND (data->io_list, self); - return self; -} - -static void -poller_pa_io_free (pa_io_event *self) -{ - if (self->free) - self->free (self->api, self, self->user_data); - - struct poller_pa *data = self->api->userdata; - poller_fd_reset (&self->fd); - LIST_UNLINK (data->io_list, self); - free (self); -} - -static void -poller_pa_io_set_destroy (pa_io_event *self, pa_io_event_destroy_cb_t cb) -{ - self->free = cb; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -poller_pa_time_dispatcher (void *user_data) -{ - pa_time_event *self = user_data; - // XXX: the meaning of the time argument is undocumented, - // so let's just put current Unix time in there - struct timeval now = poller_pa_get_current_time (); - self->dispatch (self->api, self, &now, self->user_data); -} - -static void -poller_pa_time_restart (pa_time_event *self, const struct timeval *tv) -{ - struct poller_timer *timer = &self->timer; - if (tv) - { - struct timeval now = poller_pa_get_current_time (); - poller_timer_set (timer, - (tv->tv_sec - now.tv_sec) * 1000 + - (tv->tv_usec - now.tv_usec) / 1000); - } - else - poller_timer_reset (timer); -} - -static pa_time_event * -poller_pa_time_new (pa_mainloop_api *api, const struct timeval *tv, - pa_time_event_cb_t cb, void *userdata) -{ - pa_time_event *self = xcalloc (1, sizeof *self); - self->api = api; - self->dispatch = cb; - self->user_data = userdata; - - struct poller_pa *data = api->userdata; - self->timer = poller_timer_make (data->poller); - self->timer.user_data = self; - self->timer.dispatcher = poller_pa_time_dispatcher; - - poller_pa_time_restart (self, tv); - LIST_PREPEND (data->time_list, self); - return self; -} - -static void -poller_pa_time_free (pa_time_event *self) -{ - if (self->free) - self->free (self->api, self, self->user_data); - - struct poller_pa *data = self->api->userdata; - poller_timer_reset (&self->timer); - LIST_UNLINK (data->time_list, self); - free (self); -} - -static void -poller_pa_time_set_destroy (pa_time_event *self, pa_time_event_destroy_cb_t cb) -{ - self->free = cb; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -poller_pa_defer_dispatcher (void *user_data) -{ - pa_defer_event *self = user_data; - self->dispatch (self->api, self, self->user_data); -} - -static pa_defer_event * -poller_pa_defer_new (pa_mainloop_api *api, - pa_defer_event_cb_t cb, void *userdata) -{ - pa_defer_event *self = xcalloc (1, sizeof *self); - self->api = api; - self->dispatch = cb; - self->user_data = userdata; - - struct poller_pa *data = api->userdata; - self->idle = poller_idle_make (data->poller); - self->idle.user_data = self; - self->idle.dispatcher = poller_pa_defer_dispatcher; - - poller_idle_set (&self->idle); - LIST_PREPEND (data->defer_list, self); - return self; -} - -static void -poller_pa_defer_enable (pa_defer_event *self, int enable) -{ - struct poller_idle *idle = &self->idle; - if (enable) - poller_idle_set (idle); - else - poller_idle_reset (idle); -} - -static void -poller_pa_defer_free (pa_defer_event *self) -{ - if (self->free) - self->free (self->api, self, self->user_data); - - struct poller_pa *data = self->api->userdata; - poller_idle_reset (&self->idle); - LIST_UNLINK (data->defer_list, self); - free (self); -} - -static void -poller_pa_defer_set_destroy (pa_defer_event *self, - pa_defer_event_destroy_cb_t cb) -{ - self->free = cb; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -poller_pa_quit (pa_mainloop_api *api, int retval) -{ - struct poller_pa *data = api->userdata; - data->result = retval; - data->running = false; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static struct pa_mainloop_api g_poller_pa_template = -{ - .io_new = poller_pa_io_new, - .io_enable = poller_pa_io_enable, - .io_free = poller_pa_io_free, - .io_set_destroy = poller_pa_io_set_destroy, - - .time_new = poller_pa_time_new, - .time_restart = poller_pa_time_restart, - .time_free = poller_pa_time_free, - .time_set_destroy = poller_pa_time_set_destroy, - - .defer_new = poller_pa_defer_new, - .defer_enable = poller_pa_defer_enable, - .defer_free = poller_pa_defer_free, - .defer_set_destroy = poller_pa_defer_set_destroy, - - .quit = poller_pa_quit, -}; - -static struct pa_mainloop_api * -poller_pa_new (struct poller *self) -{ - struct poller_pa *data = xcalloc (1, sizeof *data); - data->poller = self; - - struct pa_mainloop_api *api = xmalloc (sizeof *api); - *api = g_poller_pa_template; - api->userdata = data; - return api; -} - -static void -poller_pa_destroy (struct pa_mainloop_api *api) -{ - struct poller_pa *data = api->userdata; - - LIST_FOR_EACH (pa_io_event, iter, data->io_list) - poller_pa_io_free (iter); - LIST_FOR_EACH (pa_time_event, iter, data->time_list) - poller_pa_time_free (iter); - LIST_FOR_EACH (pa_defer_event, iter, data->defer_list) - poller_pa_defer_free (iter); - - free (data); - free (api); -} - -/// Since our poller API doesn't care much about continuous operation, -/// we need to provide that in the PulseAudio abstraction itself -static int -poller_pa_run (struct pa_mainloop_api *api) -{ - struct poller_pa *data = api->userdata; - data->running = true; - while (data->running) - poller_run (data->poller); - return data->result; -} diff --git a/wmstatus-weather.pl b/wmstatus-weather.pl index bce6c3d..2d60bc4 100755 --- a/wmstatus-weather.pl +++ b/wmstatus-weather.pl @@ -6,45 +6,56 @@ use strict; use warnings; use Time::Piece; -use IO::Socket::INET; +use File::Basename; -my $host = 'www.yr.no'; -my $path = '/place/Czech_Republic/Prague/Prague/forecast.xml'; +# Retrieve current weather information from the Norwegian weather service, +# see https://api.met.no/doc/ for its documentation +my $base = 'https://api.met.no/weatherapi'; +my $agent = basename($0) =~ s/[^-!#$%&'*+.^_`|~[:alnum:]]//gr; -# Retrieve current weather information from the Norwegian weather service -sub weather { - # There are no redirects and it's not exactly confidential either - my $sock = IO::Socket::INET->new( - PeerAddr => $host, - PeerPort => 'http(80)', - Proto => 'tcp' - ) or return '?'; +# https://www.yr.no/storage/lookup/English.csv.zip +my $where = 'lat=50.08804&lon=14.42076&altitude=202'; # Prague +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, + 'https://raw.githubusercontent.com/' . + 'metno/weathericons/main/weather/legend.csv') or return $!; + while (local $_ = <$sock>) { $legends{$1} = $2 if /^(.+?),(.+?),/ } + close($sock); +} - print $sock "GET $path HTTP/1.1\r\n" - . "Host: $host\r\n" - . "Connection: close\r\n\r\n"; +sub weather { + # We might want to rewrite this to use the JSON API (/compact), + # see https://developer.yr.no/doc/guides/getting-started-from-forecast-xml + open(my $sock, '-|', 'curl', '-sA', $agent, + "$base/locationforecast/2.0/classic?$where") or return $!; # Quick and dirty XML parsing is more than fine for our purpose - my ($offset, $acceptable, $temp, $symbol) = (0, 0); + my ($acceptable, $temp, $symbol) = (0, undef, undef); while (<$sock>) { - $offset = $1 * 60 if /utcoffsetMinutes="(.+?)"/; - next unless /<time/ .. /<\/time/; + next unless m|<time| .. m|</time|; # It gives forecast, so it doesn't necessarily contain the present; - # just pick the first thing that's no longer invalid - if (/from="(.+?)" to="(.+?)"/) { - $acceptable = Time::Piece->strptime($2, '%Y-%m-%dT%H:%M:%S') - - $offset >= gmtime; - } - if ($acceptable) { - $symbol = $1 if /<symbol .* name="(.+?)"/; - $temp = "$2 °${\uc $1}" - if /<temperature unit="(.).+?" value="(.+?)"/; + # just process the earliest entries that aren't yet invalid + $acceptable = Time::Piece->strptime($2, '%Y-%m-%dT%H:%M:%SZ') >= gmtime + if /from="(.+?)" to="(.+?)"/; + next unless $acceptable; + + # Temperature comes from a zero-length time interval, separately + $symbol = $1 if /<symbol.*? code="([^_"]+)/; + $temp = "$2 °" . uc $1 if /<temperature.*? unit="(.).+?" value="(.+?)"/; + if ($temp && $symbol) { + retrieve_legends if !%legends; + + close($sock); + return "$temp (" . ($legends{$symbol} || $symbol) . ")"; } - return "$temp ($symbol)" if $temp && $symbol; } - return 'Weather error'; + close($sock); + return "No weather ($?)"; } -# We need to be careful not to overload the service so that they don't ban us +# Be careful not to overload the service so that they don't ban us binmode STDOUT; $| = 1; while (1) { print weather() . "\n\n"; sleep 3600; } @@ -1,7 +1,7 @@ /* - * wmstatus.c: simple PulseAudio-enabled status setter for dwm and i3 + * 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,13 +20,16 @@ #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 #define PROGRAM_NAME "wmstatus" #include "liberty/liberty.c" -#include "poller-pa.c" +#include "liberty/liberty-pulse.c" #include <dirent.h> #include <spawn.h> @@ -849,7 +852,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: @@ -989,7 +992,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); @@ -1022,13 +1024,14 @@ read_value (int dir, const char *filename, struct error **e) return NULL; } + errno = 0; struct str s = str_make (); - bool success = read_line (fp, &s); + bool success = read_line (fp, &s) && !ferror (fp); fclose (fp); if (!success) { - error_set (e, "%s: %s", filename, "read failed"); + error_set (e, "%s: %s", filename, errno ? strerror (errno) : "EOF"); return NULL; } return str_steal (&s); @@ -1043,42 +1046,61 @@ read_number (int dir, const char *filename, struct error **e) unsigned long number = 0; if (!xstrtoul (&number, value, 10)) - error_set (e, "%s: %s", filename, "doesn't contain an unsigned number"); + error_set (e, "%s: %s", filename, "doesn't contain a valid number"); free (value); return number; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static int +read_battery_charge (int dir) +{ + struct error *error = NULL; + double capacity, now, full; + if ((capacity = read_number (dir, "capacity", &error), !error)) + return capacity; + + error_free (error); + if ((now = read_number (dir, "charge_now", &error), !error) + && (full = read_number (dir, "charge_full", &error), !error)) + return now / full * 100 + 0.5; + + error_free (error); + return -1; +} + static char * -read_battery_status (int dir, struct error **e) +read_battery_status (int dir, char **type) { - char *result = NULL; + // We present errors to the user, don't fill up the session's log. struct error *error = NULL; + struct str s = str_make (); - char *status; - double charge_now; - double charge_full; + // Dell is being unreasonable and seems to set charge_now + // to charge_full_design when the battery is fully charged + int charge = read_battery_charge (dir); + if (charge >= 0 && charge <= 100) + str_append_printf (&s, "%u%%", charge); - if ((status = read_value (dir, "status", &error), error) - || (charge_now = read_number (dir, "charge_now", &error), error) - || (charge_full = read_number (dir, "charge_full", &error), error)) - error_propagate (e, error); + char *status = NULL; + char *model_name = read_value (dir, "model_name", NULL); + if (model_name) + { + model_name[strcspn (model_name, " ")] = 0; + cstr_set (type, model_name); + } + else if ((status = read_value (dir, "status", &error), !error)) + { + str_append_printf (&s, " (%s)", status); + free (status); + } else { - struct str s = str_make (); - str_append (&s, status); - - // Dell is being unreasonable and seems to set charge_now - // to charge_full_design when the battery is fully charged - unsigned percentage = charge_now / charge_full * 100 + 0.5; - if (percentage < 100) - str_append_printf (&s, " (%u%%)", percentage); - result = str_steal (&s); + str_append_printf (&s, " (%s)", strerror (errno)); + error_free (error); } - - free (status); - return result; + return str_steal (&s); } static char * @@ -1092,16 +1114,24 @@ 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") || !strcmp (type, "UPS"); char *result = NULL; if (is_relevant) { - char *status = read_battery_status (dir, &error); - if (error) - error_propagate (e, error); + char *status = read_battery_status (dir, &type); if (status) result = xstrdup_printf ("%s %s", type, status); free (status); @@ -1122,8 +1152,8 @@ make_battery_status (void) } struct dirent *entry; - char *status = NULL; - while (!status && (entry = readdir (power_supply))) + struct strv batteries = strv_make (); + while ((entry = readdir (power_supply))) { const char *device_name = entry->d_name; if (device_name[0] == '.') @@ -1137,8 +1167,11 @@ make_battery_status (void) } struct error *error = NULL; - status = try_power_supply (dir, &error); + char *status = try_power_supply (dir, &error); close (dir); + + if (status) + strv_append_owned (&batteries, status); if (error) { print_error ("%s: %s", device_name, error->message); @@ -1146,7 +1179,10 @@ make_battery_status (void) } } closedir (power_supply); - return status; + + char *result = batteries.len ? strv_join (&batteries, " ") : NULL; + strv_free (&batteries); + return result; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -1203,7 +1239,7 @@ 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) @@ -1470,9 +1506,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"))) @@ -1480,7 +1515,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 @@ -1577,6 +1612,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 @@ -2030,8 +2069,19 @@ on_noise_adjust (struct app_context *ctx, int arg) 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); } @@ -2186,9 +2236,9 @@ spawn (char *argv[]) mpd_client_idle (c, 0); \ } -// 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 (play, "play", NULL) +MPD_SIMPLE (toggle, "pause", NULL) MPD_SIMPLE (stop, "stop", NULL) MPD_SIMPLE (prev, "previous", NULL) MPD_SIMPLE (next, "next", NULL) @@ -2196,6 +2246,12 @@ MPD_SIMPLE (forward, "seekcur", "+10", NULL) MPD_SIMPLE (backward, "seekcur", "-10", NULL) static void +on_mpd_play_toggle (struct app_context *ctx, int arg) +{ + (ctx->mpd_stopped ? on_mpd_play : on_mpd_toggle) (ctx, arg); +} + +static void on_volume_finish (pa_context *context, int success, void *userdata) { (void) context; @@ -2280,8 +2336,11 @@ static void on_input_switch (struct app_context *ctx, int arg) { (void) ctx; - char *values[] = { "vga", "dvi", "dp", "hdmi" }; - char *argv[] = { "input-switch", values[arg], NULL }; + + char *values[] = { "vga", "dvi", "hdmi", "dp" }, + *numbers[] = { "1", "2" }; + char *argv[] = { "input-switch", + values[arg & 0xf], numbers[arg >> 4], NULL }; spawn (argv); } @@ -2389,27 +2448,33 @@ g_keys[] = // can be used to figure out which modifier is AltGr // MPD - { Mod4Mask, XK_Up, on_mpd_play, 0 }, + { Mod4Mask, XK_Up, on_mpd_play_toggle, 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_AudioPlay, on_mpd_play_toggle, 0 }, { 0, XF86XK_AudioPrev, on_mpd_prev, 0 }, { 0, XF86XK_AudioNext, on_mpd_next, 0 }, - // Display input sources - { Mod4Mask, XK_F5, on_input_switch, 0 }, - { Mod4Mask, XK_F6, on_input_switch, 1 }, - { Mod4Mask, XK_F7, on_input_switch, 2 }, - { Mod4Mask, XK_F8, on_input_switch, 3 }, - // Keyboard groups - { Mod4Mask, XK_F9, on_lock_group, 0 }, - { Mod4Mask, XK_F10, on_lock_group, 1 }, - { Mod4Mask, XK_F11, on_lock_group, 2 }, - { Mod4Mask, XK_F12, on_lock_group, 3 }, + { 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 }, @@ -2417,8 +2482,8 @@ g_keys[] = { 0, XF86XK_MonBrightnessUp, on_brightness, 10 }, { 0, XF86XK_MonBrightnessDown, on_brightness, -10 }, - { Mod4Mask, XK_F4, on_standby, 0 }, - { Mod4Mask | ShiftMask, XK_F4, on_insomnia, 0 }, + { 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 }, @@ -2438,8 +2503,8 @@ g_keys[] = { 0, XF86XK_AudioMicMute, on_volume_mic_mute, 0 }, // Noise playback - { ControlMask, XF86XK_AudioRaiseVolume, on_noise_adjust, 60 }, - { ControlMask, XF86XK_AudioLowerVolume, on_noise_adjust, -60 }, + { ControlMask, XF86XK_AudioRaiseVolume, on_noise_adjust, 1 }, + { ControlMask, XF86XK_AudioLowerVolume, on_noise_adjust, -1 }, }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2637,7 +2702,7 @@ 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 instead" }, + { '3', "i3bar", NULL, 0, "print output for i3bar/sway-bar instead" }, { 'w', "write-default-cfg", "FILENAME", OPT_OPTIONAL_ARG | OPT_LONG_ONLY, "write a default configuration file and exit" }, @@ -2711,7 +2776,8 @@ main (int argc, char *argv[]) if (ctx.backend->start) ctx.backend->start (ctx.backend); - poller_pa_run (ctx.api); + while (true) + poller_run (&ctx.poller); if (ctx.backend->stop) ctx.backend->stop (ctx.backend); |