aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.clang-format32
-rw-r--r--.gitignore2
-rw-r--r--CMakeLists.txt98
-rw-r--r--LICENSE2
-rw-r--r--README.adoc11
-rw-r--r--gdm-switch-user.c2
-rw-r--r--iexec.c115
-rw-r--r--input-switch.c129
m---------liberty0
-rw-r--r--paswitch.c13
-rw-r--r--poller-pa.c361
-rwxr-xr-xwmstatus-weather.pl69
-rw-r--r--wmstatus.c198
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"]
diff --git a/.gitignore b/.gitignore
index 90b12a7..e5731aa 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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")
diff --git a/LICENSE b/LICENSE
index 7eda545..43ac88f 100644
--- a/LICENSE
+++ b/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[])
diff --git a/iexec.c b/iexec.c
index cd9dc97..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" },
@@ -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
diff --git a/paswitch.c b/paswitch.c
index b43fc00..66cec1d 100644
--- a/paswitch.c
+++ b/paswitch.c
@@ -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; }
diff --git a/wmstatus.c b/wmstatus.c
index a02a0d1..0885d97 100644
--- a/wmstatus.c
+++ b/wmstatus.c
@@ -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);