Compare commits

..

58 Commits

Author SHA1 Message Date
f26cfd3bb5 wmstatus: don't spam X session logs without MPD
All checks were successful
Alpine 3.21 Success
OpenBSD 7.6 Success
Allow and default to setting the MPD address to null.
2025-08-10 00:22:22 +02:00
f2ec611c26 Add genpass: a tool to generate passwords
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2025-03-31 21:06:21 +02:00
5b64c639ac CMakeLists.txt: don't enforce setuid bit
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2024-11-25 06:12:20 +01:00
8096a1b2c9 Move elksmart-comm to another repository 2024-11-25 03:31:07 +01:00
bb4fdcd936 wmstatus: fix noise adjustment logic
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
It was possible to trigger an untracked playback stream.
2024-10-12 15:36:34 +02:00
d06beedcaa CMakeLists.txt: install optional targets
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2024-10-12 14:20:31 +02:00
dc3f0d6d05 elksmart-comm: add support for EKX5S-T
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
This device seems to be very picky about USB ports,
but at least learning is reliable,
and it uses the same protocol as EKX4S.
2024-10-12 14:02:30 +02:00
9e91058ed9 Add elksmart-comm for transceiving infrared codes
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
The receive functionality is quite unstable,
however useful enough for something that is officially unsupported.

The gadget is picky about cables,
but it has ridiculous reach when it works.
2024-08-30 02:55:35 +02:00
fbc7454647 wmstatus: cleanup 2024-08-10 08:51:43 +02:00
94bc8c251c wmstatus: improve Sway forwards of xkb-lock-group
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2024-08-08 14:58:02 +02:00
e83cfa3c15 Fix calloc argument order, add some consts 2024-08-08 14:39:28 +02:00
29c89942ce Bump liberty
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2024-08-08 08:58:36 +02:00
9042aeaa93 wmstatus: fix the binding parser
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
2024-08-07 18:30:46 +02:00
180b16faee wmstatus: add an option to import bindings to Sway
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
We still want to retain the ability to bind them on our own under X11.

With this, the Wayland situation has considerably improved,
but the activity watch and keyboard layout switching are still broken.
2024-08-07 17:01:35 +02:00
674ea6d9a6 wmstatus: add IPC capabilities
All checks were successful
Alpine 3.20 Success
OpenBSD 7.5 Success
And remove the odd prefix functionality.
2024-08-07 12:32:33 +02:00
9ccdc3430c wmstatus: make bindings configurable 2024-08-07 11:51:14 +02:00
4a22708f52 wmstatus: move to libertyconf 2024-08-07 11:51:14 +02:00
128ef14c39 wmstatus: round in noise playback setting
All checks were successful
Alpine 3.19 Success
OpenBSD 7.3 Success
2024-04-17 00:42:55 +02:00
9def673a2b Install some binaries with the setuid bit
All checks were successful
Alpine 3.19 Success
OpenBSD 7.3 Success
2024-03-27 08:26:49 +01:00
36df9cc6c9 Fix gdm-switch-user build 2024-02-25 01:56:09 +01:00
d97a6e3f16 Bump liberty 2024-02-24 00:39:50 +01:00
e073fc400e wmstatus: MPD play/toggle 2024-02-24 00:39:50 +01:00
fefeb242ae wmstatus-weather.pl: fix weather icons
The API seems to have been removed entirely.
2024-01-19 04:31:52 +01:00
37b6ce3560 wmstatus: try a bit harder to get openat() 2023-07-24 09:56:07 +02:00
5c32057c42 wmstatus: fix an OpenBSD build warning
Note that _GNU_SOURCE is there to imply _DEFAULT_SOURCE, for BYTE_ORDER.
2023-07-04 07:24:24 +02:00
87e5285622 Don't install orphan supplementary files 2023-07-04 02:46:33 +02:00
7e30dfb6f0 iexec: enable not exitting together with the child 2023-06-19 19:06:37 +02:00
4cc1baf429 iexec: enable watching a different path 2023-06-19 19:06:16 +02:00
957aed63a8 iexec: cleanup 2023-06-19 19:06:08 +02:00
59b78ebc5c Bump liberty 2023-06-19 17:10:31 +02:00
c291e4b6ac input-switch: enable requesting current values 2022-07-25 22:41:03 +02:00
81c3c9ec3f wmstatus: skip offline power supplies 2022-02-01 22:37:34 +01:00
fbc1f18393 Improve iexec's self-description 2021-12-30 03:21:01 +01:00
4cad7806ab CMakeLists.txt: improve portability
Also, stop lying in the README that this is present in AUR.
2021-11-07 17:21:27 +01:00
6148d62ba2 Punt poller-pa.c to liberty
Now it also has tests in its new home.
2021-11-07 15:42:21 +01:00
67bd22c154 poller-pa.c: abandon the idea of quitting the loop
There are no users of this API in practice,
and it prevents making the libpulse dependency optional.
2021-11-07 14:45:56 +01:00
931ae4f82f CMakeLists.txt: slightly modernize
If someone really wants this to work on ancient systems,
the fix should be easy.
2021-11-07 14:44:16 +01:00
d057e903b7 Re-evaluate BenQ input switching
The manufacturer-specific KVM feature is necessary,
because Input Source alone won't let me wake up
the particular computer's video output.
2021-11-06 03:32:07 +01:00
c6a93b5d9e Bump liberty 2021-11-05 14:29:23 +01:00
4a0e756235 wmstatus: rebind function keys
They sucked on OLKBs, now we're appropriating F1-F5 with modifiers.
2021-11-05 14:15:31 +01:00
9dc1187b1c input-switch: add a Thunderbolt magic constant 2021-11-05 13:38:46 +01:00
135e279ca1 Update .gitignore 2021-10-30 03:33:46 +02:00
34a0cedb1f Add clang-format configuration 2021-10-30 02:58:28 +02:00
2ea58abdf0 wmstatus-weather.pl: update to use a newer API
The old one has been obsoleted, and sometimes refuses to work.

The "classic" endpoint is, sadly, not fully backwards-compatible.
2021-10-08 23:09:19 +02:00
83b4d96b15 wmstatus: rework battery reporting
Report "USB" and USB devices as well (SpaceMouse, Intuos),
make use of the "capacity" field everywhere.

Present read errors to the user, rather than spam the log.
2021-10-03 13:16:30 +02:00
7dcad3424d It turns out wmstatus works with sway as well
When Xwayland is running, that is.

There are a few issues, though, e.g. with the DPMS setting.
2021-07-24 23:18:06 +02:00
8e0e84825f wmstatus: add brown noise generation capabilities 2021-06-21 00:53:52 +02:00
32a28fcaa3 wmstatus: make Win-S-Delete mute the microphone 2021-06-20 19:19:14 +02:00
5f1b504d7f wmstatus: don't use AltGr for bindings
Unreachable key combinations for my Planck's layout.
2020-11-07 20:41:36 +01:00
aec905b291 CMakeLists.txt: omit end{if,foreach} expressions
Their usefulness was almost negative.
2020-10-29 16:11:29 +01:00
9e09ef39b7 Bump minimum CMake version to 3.0
A nice, round number.  This allows us to remove some boilerplate.
2020-10-26 18:44:08 +01:00
68dd99bc63 CMakeLists.txt: cleanup
Many executables had completely unnecessary dependencies.
2020-10-26 18:39:42 +01:00
a48c2cf4e5 Bump liberty
The bugfixes in the config parser are worth it.

I might have slightly overused cstr_set().
2020-10-19 19:54:14 +02:00
f79a8d38fb Name change 2020-09-28 05:02:35 +02:00
ab135c58c4 Bump liberty 2020-09-28 05:02:08 +02:00
1e7857dfdd wmstatus: bind standby/insomnia also to F4 2019-02-13 12:55:16 +01:00
510a53b845 paswitch: fix M-Esc 2019-02-13 12:53:25 +01:00
6e67469e3f paswitch: actually exit the program on error 2018-11-10 07:06:03 +01:00
22 changed files with 1684 additions and 878 deletions

32
.clang-format Normal file
View File

@@ -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"]

2
.gitignore vendored
View File

@@ -7,3 +7,5 @@
/desktop-tools.files
/desktop-tools.creator*
/desktop-tools.includes
/desktop-tools.cflags
/desktop-tools.cxxflags

View File

@@ -1,73 +1,69 @@
project (desktop-tools C)
cmake_minimum_required (VERSION 2.8.11)
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}")
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
# Version
set (project_VERSION_MAJOR "0")
set (project_VERSION_MINOR "1")
set (project_VERSION_PATCH "0")
set (project_VERSION "${project_VERSION_MAJOR}")
set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}")
set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wextra -Wno-unused-function")
endif ()
# Dependencies
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 (
${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})
set (project_libraries ${dependencies_LIBRARIES})
include_directories (${dependencies_INCLUDE_DIRS})
option (WITH_GDM "Compile with GDM utilities" ${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
add_executable (wmstatus wmstatus.c)
target_link_libraries (wmstatus ${project_libraries})
set (targets wmstatus paswitch siprandom genpass)
if ("${CMAKE_SYSTEM_NAME}" STREQUAL Linux)
# These use Linux i2c APIs, but can be made to work on macOS
list (APPEND targets brightness input-switch)
# 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 ${targets})
add_executable (${name} ${name}.c)
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)
add_executable (paswitch paswitch.c)
target_link_libraries (paswitch ${project_libraries})
add_executable (brightness brightness.c)
target_link_libraries (brightness ${project_libraries})
add_executable (input-switch input-switch.c)
target_link_libraries (input-switch ${project_libraries})
add_executable (fancontrol-ng fancontrol-ng.c)
target_link_libraries (fancontrol-ng ${project_libraries})
add_executable (priod priod.c)
target_link_libraries (priod ${project_libraries})
add_executable (iexec iexec.c)
target_link_libraries (iexec)
if (WITH_GDM)
include_directories (${gdm_INCLUDE_DIRS})
list (APPEND targets gdm-switch-user)
add_executable (gdm-switch-user gdm-switch-user.c)
target_include_directories (gdm-switch-user PUBLIC ${gdm_INCLUDE_DIRS})
target_link_directories (gdm-switch-user PUBLIC ${gdm_LIBRARY_DIRS})
target_link_libraries (gdm-switch-user ${gdm_LIBRARIES})
endif (WITH_GDM)
add_executable (siprandom siprandom.c)
target_link_libraries (siprandom ${project_libraries})
add_executable (big-brother big-brother.c)
target_link_libraries (big-brother ${project_libraries})
endif ()
# The files to be installed
include (GNUInstallDirs)
@@ -76,46 +72,58 @@ 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
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
install (FILES fancontrol-ng.conf.example
DESTINATION ${CMAKE_INSTALL_DATADIR}/fancontrol-ng)
configure_file (${PROJECT_SOURCE_DIR}/priod.service.in
configure_file (${PROJECT_SOURCE_DIR}/priod.service.in
${PROJECT_BINARY_DIR}/priod.service @ONLY)
install (FILES priod.conf.example
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
# 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 (WITH_GDM)
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.
set (SETUID "SETUID" CACHE STRING "Set this empty on permission issues")
foreach (target brightness input-switch)
if (${target} IN_LIST targets)
list (REMOVE_ITEM targets ${target})
install (TARGETS ${target} DESTINATION ${CMAKE_INSTALL_BINDIR}
PERMISSIONS
OWNER_WRITE OWNER_READ OWNER_EXECUTE
GROUP_READ GROUP_EXECUTE
WORLD_READ WORLD_EXECUTE
${SETUID})
endif ()
endforeach ()
install (TARGETS ${targets} DESTINATION ${CMAKE_INSTALL_BINDIR})
install (PROGRAMS shellify DESTINATION ${CMAKE_INSTALL_BINDIR})
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
# CPack
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Desktop tools")
set (CPACK_PACKAGE_VENDOR "Premysl Janouch")
set (CPACK_PACKAGE_CONTACT "Přemysl Janouch <p@janouch.name>")
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")
set (CPACK_PACKAGE_VERSION_MAJOR ${project_VERSION_MAJOR})
set (CPACK_PACKAGE_VERSION_MINOR ${project_VERSION_MINOR})
set (CPACK_PACKAGE_VERSION_PATCH ${project_VERSION_PATCH})
set (CPACK_GENERATOR "TGZ;ZIP")
set (CPACK_PACKAGE_FILE_NAME
"${PROJECT_NAME}-${project_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${project_VERSION}")
"${PROJECT_NAME}-${PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${PROJECT_VERSION}")
set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user")
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${project_VERSION}")
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
set (CPACK_SET_DESTDIR TRUE)
include (CPack)

View File

@@ -1,4 +1,4 @@
Copyright (c) 2015 - 2018, Přemysl Janouch <p@janouch.name>
Copyright (c) 2015 - 2025, Přemysl Eric Janouch <p@janouch.name>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.

View File

@@ -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,17 +28,17 @@ 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
--------
Build dependencies: CMake, pkg-config, liberty (included) +
Runtime dependencies: libpulse, libx11, dbus-1, libgdm (optional)
Runtime dependencies: libpulse, libx11, dbus-1 +
Optional runtime dependencies: libgdm (gdm-switch-user)
$ git clone --recursive https://git.janouch.name/p/desktop-tools.git
$ mkdir desktop-tools/build

View File

@@ -1,7 +1,7 @@
/*
* big-brother.c: activity tracker
*
* Copyright (c) 2016, Přemysl Janouch <p@janouch.name>
* Copyright (c) 2016, 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.
@@ -149,7 +149,7 @@ static void
app_context_free (struct app_context *self)
{
str_map_free (&self->config);
free (self->current_title);
cstr_set (&self->current_title, NULL);
poller_fd_reset (&self->x_event);
XCloseDisplay (self->dpy);
poller_free (&self->poller);
@@ -207,8 +207,7 @@ update_window_title (struct app_context *ctx, char *new_title)
{
bool changed = !ctx->current_title != !new_title
|| (new_title && strcmp (ctx->current_title, new_title));
free (ctx->current_title);
ctx->current_title = new_title;
cstr_set (&ctx->current_title, new_title);
return changed;
}

View File

@@ -1,7 +1,7 @@
/*
* brightness.c: set display brightness via DDC/CI - Linux only
*
* Copyright (c) 2015, Přemysl Janouch <p@janouch.name>
* Copyright (c) 2015, 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.

View File

@@ -2,7 +2,7 @@
#define CONFIG_H
#define PROGRAM_NAME "${PROJECT_NAME}"
#define PROGRAM_VERSION "${project_VERSION}"
#define PROGRAM_VERSION "${PROJECT_VERSION}"
#endif // ! CONFIG_H

View File

@@ -1,7 +1,7 @@
/*
* ddc-ci.c: DDC-CI utilities, Linux-only
*
* Copyright (c) 2015 - 2017, Přemysl Janouch <p@janouch.name>
* Copyright (c) 2015 - 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.
@@ -46,7 +46,7 @@ log_message_custom (void *user_data, const char *quote, const char *fmt,
static void
wait_ms (long ms)
{
struct timespec ts = { 0, ms * 1000 * 1000 };
struct timespec ts = { ms / 1000, (ms % 1000) * 1000 * 1000 };
nanosleep (&ts, NULL);
}

View File

@@ -1,7 +1,7 @@
/*
* fancontrol-ng.c: clone of fancontrol from lm_sensors
*
* Copyright (c) 2016, Přemysl Janouch <p@janouch.name>
* Copyright (c) 2016, 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.
@@ -124,7 +124,7 @@ config_validate_nonnegative (const struct config_item *item, struct error **e)
return error_set (e, "must be non-negative");
}
static struct config_schema g_config_device[] =
static const struct config_schema g_config_device[] =
{
{ .name = "name",
.comment = "Device identifier",
@@ -137,7 +137,7 @@ static struct config_schema g_config_device[] =
{}
};
static struct config_schema g_config_pwm[] =
static const struct config_schema g_config_pwm[] =
{
{ .name = "temp",
.comment = "Path to temperature sensor output",
@@ -225,12 +225,12 @@ paths_new (const char *device_path, const char *path, struct config_item *pwm)
static void
paths_destroy (struct paths *self)
{
free (self->temp);
cstr_set (&self->temp, NULL);
free (self->pwm);
free (self->pwm_enable);
free (self->pwm_min);
free (self->pwm_max);
cstr_set (&self->pwm, NULL);
cstr_set (&self->pwm_enable, NULL);
cstr_set (&self->pwm_min, NULL);
cstr_set (&self->pwm_max, NULL);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -415,7 +415,7 @@ device_create (struct app_context *ctx, const char *path,
// There is no room for errors in the configuration, everything must be valid.
// Thus the reset to defaults on invalid values is effectively disabled here.
static bool
apply_schema (struct config_schema *schema, struct config_item *object,
apply_schema (const struct config_schema *schema, struct config_item *object,
struct error **e)
{
struct error *warning = NULL, *error = NULL;
@@ -445,7 +445,7 @@ static bool
check_device_configuration (struct config_item *subtree, struct error **e)
{
// Check regular fields in the device object
for (struct config_schema *s = g_config_device; s->name; s++)
for (const struct config_schema *s = g_config_device; s->name; s++)
if (!apply_schema (s, subtree, e))
return false;
@@ -465,7 +465,7 @@ check_device_configuration (struct config_item *subtree, struct error **e)
while ((pwm = str_map_iter_next (&iter)))
{
const char *subpath = iter.link->key;
for (struct config_schema *s = g_config_pwm; s->name; s++)
for (const struct config_schema *s = g_config_pwm; s->name; s++)
if (!apply_schema (s, pwm, &error))
{
error_set (e, "PWM `%s': %s", subpath, error->message);

View File

@@ -1,5 +1,5 @@
// Public domain
#include <gdm-user-switching.h>
#include <gdm/gdm-user-switching.h>
int
main (int argc, char *argv[])

151
genpass.c Normal file
View File

@@ -0,0 +1,151 @@
/*
* genpass.c: password generator
*
* Copyright (c) 2025, Přemysl Eric Janouch <p@janouch.name>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "config.h"
#undef PROGRAM_NAME
#define PROGRAM_NAME "genpass"
#include "liberty/liberty.c"
static struct str
parse_group (const char *group)
{
bool present[0x100] = {};
for (size_t i = 0; group[i]; i++)
{
unsigned char c = group[i];
if (!i || c != '-' || !group[i + 1])
present[c] = true;
else if (group[i + 1] < group[i - 1])
exit_fatal ("character ranges must be increasing");
else
for (c = group[i - 1]; ++c <= group[i + 1]; )
present[c] = true;
}
struct str alphabet = str_make ();
for (size_t i = 1; i < N_ELEMENTS (present); i++)
if (present[i])
str_append_c (&alphabet, i);
if (!alphabet.len)
exit_fatal ("empty group");
return alphabet;
}
static void
parse_program_arguments (int argc, char **argv,
unsigned long *length, struct strv *groups, struct str *alphabet)
{
static const struct opt opts[] =
{
{ 'l', "length", "CHARACTERS", 0, "set password length" },
{ 'd', "debug", NULL, 0, "run in debug mode" },
{ 'h', "help", NULL, 0, "display this help and exit" },
{ 'V', "version", NULL, 0, "output version information and exit" },
{ 0, NULL, NULL, 0, NULL }
};
struct opt_handler oh =
opt_handler_make (argc, argv, opts, "GROUP...", "Password generator.");
int c;
while ((c = opt_handler_get (&oh)) != -1)
switch (c)
{
case 'l':
if (!xstrtoul (length, optarg, 10) || *length <= 0)
print_fatal ("invalid length argument");
break;
case 'd':
g_debug_mode = true;
break;
case 'h':
opt_handler_usage (&oh, stdout);
exit (EXIT_SUCCESS);
case 'V':
printf (PROGRAM_NAME " " PROGRAM_VERSION "\n");
exit (EXIT_SUCCESS);
default:
print_error ("wrong options");
opt_handler_usage (&oh, stderr);
exit (EXIT_FAILURE);
}
argc -= optind;
argv += optind;
for (int i = 0; i < argc; i++)
{
struct str alphabet = parse_group (argv[i]);
strv_append_owned (groups, str_steal (&alphabet));
}
bool present[0x100] = {};
for (size_t i = 0; i < groups->len; i++)
for (size_t k = 0; groups->vector[i][k]; k++)
{
unsigned char c = groups->vector[i][k];
if (present[c])
exit_fatal ("groups are not disjunct");
present[c] = true;
}
for (size_t i = 1; i < N_ELEMENTS (present); i++)
if (present[i])
str_append_c (alphabet, i);
if (groups->len > *length)
exit_fatal ("the requested length is less than the number of groups");
if (!groups->len)
{
opt_handler_usage (&oh, stderr);
exit (EXIT_FAILURE);
}
opt_handler_free (&oh);
}
int
main (int argc, char *argv[])
{
unsigned long length = 8;
struct strv groups = strv_make ();
struct str alphabet = str_make ();
parse_program_arguments (argc, argv, &length, &groups, &alphabet);
unsigned seed = 0;
if (!random_bytes (&seed, sizeof seed, NULL))
exit_fatal ("failed to initialize random numbers");
srand (seed);
// Select from a joined alphabet, but make sure all groups are represented.
struct str candidate = str_make ();
while (true)
{
restart:
for (size_t i = length; i--; )
str_append_c (&candidate, alphabet.str[rand () % alphabet.len]);
for (size_t i = 0; i < groups.len; i++)
if (!strpbrk (candidate.str, groups.vector[i]))
{
str_reset (&candidate);
goto restart;
}
printf ("%s\n", candidate.str);
return 0;
}
}

121
iexec.c
View File

@@ -1,7 +1,7 @@
/*
* iexec.c: run a program and restart on file change
*
* Copyright (c) 2017, Přemysl 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_inotify_event (const struct inotify_event *e, const char *base)
{
if (e->wd != g.inotify_wd || strcmp (e->name, base))
return;
if (g.child)
{
print_debug ("file changed, killing child");
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; const struct inotify_event *e;
while ((len = read (g_inotify_fd, buf, sizeof buf)) > 0)
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)
{
e = (const struct inotify_event *) buf;
if (e->wd != g_inotify_wd || strcmp (e->name, base))
continue;
print_debug ("file changed, killing child");
g_restarting = true;
if (kill (g_child, SIGINT))
print_error ("kill: %s", strerror (errno));
}
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;
return g.exits;
}
else
{
g.killing = false;
print_debug ("child died on request, respawning");
spawn (argv);
g_restarting = false;
return true;
}
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,17 +124,21 @@ 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
char *nonpermuting = xstrdup_printf ("+%s", oh.opt_string);
free (oh.opt_string);
oh.opt_string = nonpermuting;
cstr_set (&oh.opt_string, xstrdup_printf ("+%s", oh.opt_string));
int c;
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;
@@ -138,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);
@@ -150,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;
}

View File

@@ -1,7 +1,7 @@
/*
* input-switch.c: switches display input via DDC/CI
*
* Copyright (c) 2017, Přemysl 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;
}

Submodule liberty updated: bca7167d03...75fc6f1c37

View File

@@ -3,7 +3,7 @@
*
* module-switch-on-connect functionality without the on-connect part.
*
* Copyright (c) 2015 - 2018, Přemysl Janouch <p@janouch.name>
* Copyright (c) 2015 - 2018, 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.
@@ -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>
@@ -45,13 +45,6 @@
enum { PIPE_READ, PIPE_WRITE };
static void
cstr_set (char **s, char *new)
{
free (*s);
*s = new;
}
static void
log_message_custom (void *user_data, const char *quote, const char *fmt,
va_list ap)
@@ -79,8 +72,8 @@ struct port
static void
port_free (struct port *self)
{
free (self->name);
free (self->description);
cstr_set (&self->name, NULL);
cstr_set (&self->description, NULL);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -109,14 +102,14 @@ sink_new (void)
static void
sink_destroy (struct sink *self)
{
free (self->name);
free (self->description);
cstr_set (&self->name, NULL);
cstr_set (&self->description, NULL);
for (size_t i = 0; i < self->ports_len; i++)
port_free (self->ports + i);
free (self->ports);
free (self->port_active);
cstr_set (&self->port_active, NULL);
free (self);
}
@@ -150,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
@@ -185,7 +179,7 @@ app_context_free (struct app_context *self)
if (self->context)
pa_context_unref (self->context);
free (self->default_sink);
cstr_set (&self->default_sink, NULL);
LIST_FOR_EACH (struct sink, iter, self->sinks)
sink_destroy (iter);
LIST_FOR_EACH (struct sink_input, iter, self->inputs)
@@ -296,7 +290,7 @@ on_sink_info (pa_context *context, const pa_sink_info *info, int eol,
sink->ports_len++;
struct port *port = sink->ports =
xcalloc (sizeof *sink->ports, sink->ports_len);
xcalloc (sink->ports_len, sizeof *sink->ports);
for (struct pa_sink_port_info **iter = info->ports; *iter; iter++)
{
port->name = xstrdup ((*iter)->name);
@@ -690,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;
}
@@ -791,8 +785,9 @@ read_key_sequence (const char *buf, size_t len)
return ++p - buf;
return -escapes;
}
// We don't know this sequence, so just return M-Esc
if (escapes == 2)
return -escapes;
return escapes;
}
// Shift state encodings aren't going to work, though anything else should
@@ -916,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
@@ -1039,16 +1034,16 @@ main (int argc, char *argv[])
opt_handler_free (&oh);
if (!isatty (STDIN_FILENO))
print_fatal ("input is not a terminal");
exit_fatal ("input is not a terminal");
if (!isatty (STDOUT_FILENO))
print_fatal ("output is not a terminal");
exit_fatal ("output is not a terminal");
setlocale (LC_CTYPE, "");
// PulseAudio uses UTF-8, let's avoid encoding conversions
if (strcasecmp (nl_langinfo (CODESET), "UTF-8"))
print_fatal ("UTF-8 encoding required");
exit_fatal ("UTF-8 encoding required");
if (setvbuf (stdout, NULL, _IOLBF, 0) || !tty_start ())
print_fatal ("terminal initialization failed");
exit_fatal ("terminal initialization failed");
// TODO: we will need a logging function aware of our rendering
g_log_message_real = log_message_custom;
@@ -1074,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;
}

View File

@@ -1,361 +0,0 @@
/*
* pa.c: PulseAudio mainloop abstraction
*
* Copyright (c) 2016 - 2017, Přemysl 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;
}

View File

@@ -4,7 +4,7 @@
* Thanks to http://netsplit.com/the-proc-connector-and-socket-filters
* for showing the way around the proc connector and BPF.
*
* Copyright (c) 2017, Přemysl Janouch <p@janouch.name>
* Copyright (c) 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.

View File

@@ -1,7 +1,7 @@
/*
* siprandom.c: relatively fast pseudo-random data generator
*
* Copyright (c) 2016, Přemysl Janouch <p@janouch.name>
* Copyright (c) 2016, 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.

View File

@@ -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;
print $sock "GET $path HTTP/1.1\r\n"
. "Host: $host\r\n"
. "Connection: close\r\n\r\n";
# Quick and dirty XML parsing is more than fine for our purpose
my ($offset, $acceptable, $temp, $symbol) = (0, 0);
while (<$sock>) {
$offset = $1 * 60 if /utcoffsetMinutes="(.+?)"/;
next unless /<time/ .. /<\/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="(.+?)"/;
}
return "$temp ($symbol)" if $temp && $symbol;
}
return 'Weather error';
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);
}
# We need to be careful not to overload the service so that they don't ban us
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 ($acceptable, $temp, $symbol) = (0, undef, undef);
while (<$sock>) {
next unless m|<time| .. m|</time|;
# It gives forecast, so it doesn't necessarily contain the present;
# 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) . ")";
}
}
close($sock);
return "No weather ($?)";
}
# 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; }

1340
wmstatus.c

File diff suppressed because it is too large Load Diff

63
wmstatus.conf.example Normal file
View File

@@ -0,0 +1,63 @@
# vim: set ft=libertyconf:
keys = {
# This key should be labeled L on normal Qwert[yz] layouts
"Mod4 n" = "exec dm-tool lock" # gdm-switch-user
# xmodmap grep -e Alt_R -e Meta_R -e ISO_Level3_Shift -e Mode_switch
# can be used to figure out which modifier is AltGr
"Mod4 Up" = "mpd-play-toggle"
"Mod4 Down" = "mpd stop"
"Mod4 Left" = "mpd previous"
"Mod4 Right" = "mpd next"
"Mod4 Shift Left" = "mpd seekcur -10"
"Mod4 Shift Right" = "mpd seekcur +10"
"XF86AudioPlay" = "mpd-play-toggle"
"XF86AudioPrev" = "mpd previous"
"XF86AudioNext" = "mpd next"
"Mod4 F1" = "xkb-lock-group 0"
"Mod4 F2" = "xkb-lock-group 1"
"Mod4 F3" = "xkb-lock-group 2"
"Mod4 F4" = "xkb-lock-group 3"
"Mod4 Control F1" = "exec input-switch vga 1"
"Mod4 Control Shift F1" = "exec input-switch vga 2"
"Mod4 Control F2" = "exec input-switch dvi 1"
"Mod4 Control Shift F2" = "exec input-switch dvi 2"
"Mod4 Control F3" = "exec input-switch hdmi 1"
"Mod4 Control Shift F3" = "exec input-switch hdmi 2"
"Mod4 Control F4" = "exec input-switch dp 1"
"Mod4 Control Shift F4" = "exec input-switch dp 2"
"Mod4 Home" = "exec brightness +10"
"Mod4 End" = "exec brightness -10"
"XF86MonBrightnessUp" = "exec brightness +10"
"XF86MonBrightnessDown" = "exec brightness -10"
# We need to wait a little while until user releases the key
"Mod4 F5" = "exec sh -c 'sleep 1; xset dpms force standby'"
"Mod4 Shift F5" = "insomnia"
"Mod4 Pause" = "exec sh -c 'sleep 1; xset dpms force standby'"
"Mod4 Shift Pause" = "insomnia"
"Mod4 Insert" = "audio-switch"
"Mod4 Delete" = "audio-mute"
"Mod4 Shift Delete" = "audio-mic-mute"
"Mod4 Page_Up" = "audio-volume +5"
"Mod4 Shift Page_Up" = "audio-volume +1"
"Mod4 Page_Down" = "audio-volume -5"
"Mod4 Shift Page_Down" = "audio-volume -1"
" XF86AudioRaiseVolume" = "audio-volume +5"
"Shift XF86AudioRaiseVolume" = "audio-volume +1"
" XF86AudioLowerVolume" = "audio-volume -5"
"Shift XF86AudioLowerVolume" = "audio-volume -1"
" XF86AudioMute" = "audio-mute"
" XF86AudioMicMute" = "audio-mic-mute"
"Control XF86AudioRaiseVolume" = "noise-adjust +1"
"Control XF86AudioLowerVolume" = "noise-adjust -1"
# Turns on or off Pioneer integrated amplifiers
"Mod4 Control Delete" = "exec elksmart-comm --nec A538"
}