summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2024-03-11 14:32:38 +0100
committerSimon Rettberg2024-03-11 14:32:38 +0100
commitadd0b32cd189f37495cc077408b748f486b7d70c (patch)
tree2325fe682d060605060517b7d6867da0d959ca0d
parentkernel: Set default hostname (diff)
downloadmltk-add0b32cd189f37495cc077408b748f486b7d70c.tar.gz
mltk-add0b32cd189f37495cc077408b748f486b7d70c.tar.xz
mltk-add0b32cd189f37495cc077408b748f486b7d70c.zip
[slx-brightness] Add new module to control backlit displays
We do this raw by reading /dev/input/eventX to make sure no running program (e.g. VMware Player) can grab the keyboard and prevent any other running daemon (e.g. xfce-power-manager) from seeing the keystrokes. Another advantage is that this will work everywhere (tty, lightdm, VM, desktop session) since the daemon starts on system bootup.
-rwxr-xr-xcore/modules/debug-report-bwlp/data/opt/openslx/bin/debug_report1
l---------core/modules/slx-brightness/data/etc/systemd/system/multi-user.target.wants/slx-brightness.service1
-rw-r--r--core/modules/slx-brightness/data/etc/systemd/system/slx-brightness.service7
-rwxr-xr-xcore/modules/slx-brightness/data/opt/openslx/scripts/systemd-slx_brightness78
-rw-r--r--core/modules/slx-brightness/module.build8
-rw-r--r--core/modules/slx-brightness/module.conf5
-rw-r--r--core/modules/slx-brightness/src/main.c318
7 files changed, 418 insertions, 0 deletions
diff --git a/core/modules/debug-report-bwlp/data/opt/openslx/bin/debug_report b/core/modules/debug-report-bwlp/data/opt/openslx/bin/debug_report
index 4f676f5c..f228e9c5 100755
--- a/core/modules/debug-report-bwlp/data/opt/openslx/bin/debug_report
+++ b/core/modules/debug-report-bwlp/data/opt/openslx/bin/debug_report
@@ -15,6 +15,7 @@ TOOLS=(
mount
dmesg
dmidecode
+ "slx-brightness -l"
)
URLS="
diff --git a/core/modules/slx-brightness/data/etc/systemd/system/multi-user.target.wants/slx-brightness.service b/core/modules/slx-brightness/data/etc/systemd/system/multi-user.target.wants/slx-brightness.service
new file mode 120000
index 00000000..6cbe2a91
--- /dev/null
+++ b/core/modules/slx-brightness/data/etc/systemd/system/multi-user.target.wants/slx-brightness.service
@@ -0,0 +1 @@
+../slx-brightness.service \ No newline at end of file
diff --git a/core/modules/slx-brightness/data/etc/systemd/system/slx-brightness.service b/core/modules/slx-brightness/data/etc/systemd/system/slx-brightness.service
new file mode 100644
index 00000000..c75fdaf0
--- /dev/null
+++ b/core/modules/slx-brightness/data/etc/systemd/system/slx-brightness.service
@@ -0,0 +1,7 @@
+[Unit]
+Description=Handle display brightness keys
+
+[Service]
+Type=forking
+ExecStart=/opt/openslx/scripts/systemd-slx_brightness
+Restart=on-failure
diff --git a/core/modules/slx-brightness/data/opt/openslx/scripts/systemd-slx_brightness b/core/modules/slx-brightness/data/opt/openslx/scripts/systemd-slx_brightness
new file mode 100755
index 00000000..25925df8
--- /dev/null
+++ b/core/modules/slx-brightness/data/opt/openslx/scripts/systemd-slx_brightness
@@ -0,0 +1,78 @@
+#!/bin/bash
+
+if ! command -v slx-brightness &> /dev/null; then
+ echo "Error: Required tool slx-brightness not in path"
+ exit 1
+fi
+
+# How many steps we want from min to max
+declare -rg steps=15
+
+# Look for brightness config file
+#
+max_file="/sys/class/backlight/intel_backlight/max_brightness"
+cur_file="/sys/class/backlight/intel_backlight/brightness"
+
+if ! [ -e "$max_file" ] || ! [ -e "$cur_file" ]; then
+ for max_file in /sys/class/backlight/*/max_brightness; do
+ cur_file="${max_file%/*}/brightness"
+ [ -e "$max_file" ] && [ -e "$cur_file" ] && break
+ done
+fi
+
+if ! [ -e "$max_file" ] || ! [ -e "$cur_file" ]; then
+ echo "No brightness controls found, exiting"
+ exit 0
+fi
+
+# Calc sane limits
+#
+max=$( cat "$max_file" )
+if ! (( max > 0 )); then
+ echo "Max brightness is '$max', doing nothing"
+ exit 0
+fi
+
+min=$(( max / ( steps + 1 ) ))
+
+# Start our daemon
+#
+slx-brightness "$cur_file" "$min" "$max" "$steps" &
+pid=$!
+
+# Give it time to start
+sleep 3
+
+if ! kill -0 "$pid"; then
+ # slx-brightness exits if it doesn't find a known input device for brightness control
+ echo "slx-brightness didn't find a suitable device :-("
+ t="$( mktemp )"
+ if [ -n "$t" ]; then
+ slx-brightness -l &> "$t"
+ slxlog --delete --sync "screen-brightness" \
+ "Device seems to have controllable backlight, but cannot find suitable input device" "$t"
+ fi
+ exit 0
+fi
+
+# Disable handling of keys by xfce-power-manager
+#
+pmfile="/etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-power-manager.xml"
+if ! [ -s "$pmfile" ]; then
+ mkdir -p "${pmfile%/*}"
+ cat > "$pmfile" <<-EOF
+ <?xml version="1.0" encoding="UTF-8"?>
+
+ <channel name="xfce4-power-manager" version="1.0" locked="*" unlocked="root">
+ <property name="xfce4-power-manager" type="empty">
+ <property name="handle-brightness-keys" type="bool" value="false"/>
+ </property>
+ </channel>
+EOF
+else
+ xmlstarlet ed -u '/channel/property/property[@name="handle-brightness-keys"]/@value' \
+ -v "false" "$pmfile" > "$pmfile.tmp" \
+ && mv -f "$pmfile.tmp" "$pmfile"
+fi
+
+exit 0
diff --git a/core/modules/slx-brightness/module.build b/core/modules/slx-brightness/module.build
new file mode 100644
index 00000000..c6b478c4
--- /dev/null
+++ b/core/modules/slx-brightness/module.build
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+build() {
+ local dest="${MODULE_BUILD_DIR}/opt/openslx/sbin"
+ mkdir -p "$dest" || perror "Cannot create $dest"
+ gcc -Wall -pedantic -Wextra -Werror -o "$dest/slx-brightness" "${MODULE_DIR}/src/main.c" \
+ || perror "Cannot compile slx-brightness"
+}
diff --git a/core/modules/slx-brightness/module.conf b/core/modules/slx-brightness/module.conf
new file mode 100644
index 00000000..f4705015
--- /dev/null
+++ b/core/modules/slx-brightness/module.conf
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+REQUIRED_BINARIES="
+ slx-brightness
+"
diff --git a/core/modules/slx-brightness/src/main.c b/core/modules/slx-brightness/src/main.c
new file mode 100644
index 00000000..410154e0
--- /dev/null
+++ b/core/modules/slx-brightness/src/main.c
@@ -0,0 +1,318 @@
+#include <unistd.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+#include <stdio.h>
+#include <linux/input.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+int open_events( int ep );
+
+bool handle_fd( int fd );
+
+void handle_message( struct input_event *ev );
+
+ssize_t file_get_contents( const char *filename, char *buffer, size_t len );
+
+ssize_t file_put_contents( const char *filename, const char *buffer, size_t len );
+
+int _min, _max, _step;
+const char *_brightness_file;
+bool _all;
+
+int main( int argc, char **argv )
+{
+ int steps, ep, num, valid;
+ struct epoll_event events[10];
+
+ // List all devices by name
+ if ( argc > 1 && strcmp( "-l", argv[1] ) == 0 ) {
+ open_events( -1 );
+ return 0;
+ }
+
+ // Listen to all devices
+ if ( argc > 1 && strcmp( "-a", argv[1] ) == 0 ) {
+ _all = true;
+ argc--;
+ argv++;
+ }
+
+ if ( argc < 5 ) {
+ fprintf( stderr, "%s <path> <min> <max> <num_steps>\n", argv[0] );
+ return 1;
+ }
+
+ // Sanity checks, calculate step size
+ _brightness_file = argv[1];
+ _min = atoi( argv[2] );
+ _max = atoi( argv[3] );
+ steps = atoi( argv[4] );
+ if ( _max <= _min || steps < 1 ) {
+ fprintf( stderr, "Error: min(%d) >= max(%d), or steps(%d) < 1\n", _min, _max, steps );
+ return 1;
+ }
+ _step = ( _max - _min ) / steps;
+ if ( _step < 1 ) {
+ _step = 1;
+ }
+
+ ep = epoll_create( 100 );
+ if ( ep == -1 ) {
+ perror( "Cannot epoll_create" );
+ return 1;
+ }
+
+ // Scan all input-event devices, open all that potentially have brightness keys
+ // As of now, we match only one so epoll is pointless, but I only checked two
+ // laptop models, so maybe there are different ways out there. Let's be prepared.
+ valid = open_events( ep );
+ if ( valid < 1 ) {
+ fprintf( stderr, "Nothing to wait for, exiting\n" );
+ return 0;
+ }
+
+ printf( "Waiting...\n" );
+ while ( ( num = epoll_wait( ep, events, 10, -1 ) ) > 0
+ || ( num == -1 && errno == EINTR ) ) {
+ for ( int i = 0; i < num; ++i ) {
+ if ( !handle_fd( events[i].data.fd ) ) {
+ // Try to remove
+ if ( epoll_ctl( ep, EPOLL_CTL_DEL, events[i].data.fd, NULL ) == 0 ) {
+ valid--;
+ close( events[i].data.fd );
+ }
+ }
+ }
+ }
+ if ( num == -1 ) {
+ perror( "epoll error" );
+ return 1;
+ }
+ fprintf( stderr, "Unexpected loop exit\n" );
+ return 0;
+}
+
+/**
+ * Open all /dev/input/eventX, check the name against known ones with
+ * brightness controls.
+ * Pass ep == -1 to just print all the device names.
+ */
+int open_events( int ep )
+{
+ int opened = 0;
+ int fails = 0;
+ int h, len;
+ char fn[200];
+
+ for ( int id = 0 ;; ++id ) {
+ if ( snprintf( fn, sizeof(fn), "/dev/input/event%d", id ) == -1 ) {
+ perror( "snprintf broken" );
+ break;
+ }
+
+ // Open and see if success
+ h = open( fn, O_RDONLY );
+ if ( h == -1 ) {
+ if ( errno == ENOENT ) {
+ // Not found - potentially no more devices, but keep going for a bit, since for example
+ // unplugging a USB device would leave a gap in the numbering.
+ if ( ++fails < 10 )
+ continue;
+ } else {
+ perror( "Cannot open eventX" );
+ }
+ break;
+ }
+ // Success, inspect
+ if ( ep == -1 ) {
+ printf( "Opened %s: ", fn );
+ }
+ fails = 0;
+ len = ioctl( h, EVIOCGNAME( sizeof(fn) ), fn );
+ if ( len <= 0 ) {
+ perror( "Could not get device name" );
+ close( h );
+ continue;
+ }
+ if ( ep == -1 ) {
+ printf( "%.*s\n", len, fn );
+ close( h );
+ continue;
+ }
+
+ if ( !_all && strcmp( "Video Bus", fn ) != 0 ) {
+ printf( "Ignoring '%s'\n", fn );
+ close( h );
+ continue;
+ }
+
+ printf( "Listening on '%s'\n", fn );
+ // Make nonblocking, add to epoll set
+ fcntl( h, F_SETFL, O_NONBLOCK );
+ struct epoll_event ee = {
+ .events = EPOLLIN,
+ .data.fd = h,
+ };
+ if ( epoll_ctl( ep, EPOLL_CTL_ADD, h, &ee ) == -1 ) {
+ perror( "Cannot add eventX fd to epoll set" );
+ close( h );
+ } else {
+ opened++;
+ }
+ }
+
+ return opened;
+}
+
+/**
+ * Read events from fd.
+ */
+bool handle_fd( int fd )
+{
+ int num;
+ int done = 0;
+ struct input_event buf;
+
+ while ( ( num = read( fd, ( (char*)&buf ) + done, sizeof(buf) - done ) ) > 0
+ || ( num == -1 && errno == EINTR ) ) {
+ if ( num + done == sizeof(buf) ) {
+ handle_message( &buf );
+ if ( done != 0 ) {
+ done = 0;
+ // Switch to nonblocking again
+ fcntl( fd, F_SETFL, O_NONBLOCK );
+ }
+ } else {
+ printf( "WARNING: PARTIAL READ\n" );
+ // Switch to blocking so we can finish the message
+ fcntl( fd, F_SETFL, 0 );
+ done += num;
+ }
+ }
+ if ( num == -1 && errno != EAGAIN ) {
+ perror( "Error reading event" );
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Gets passed read events in input_event struct,
+ * check and handle brightness keys.
+ */
+void handle_message( struct input_event *ev )
+{
+ ssize_t len;
+ int val, newval;
+ char buf[100];
+
+ //printf( "Type: %d, Key %d, Value %d\n", (int)ev->type, (int)ev->code, (int)ev->value );
+ if ( ev->type != EV_KEY )
+ return;
+ if ( ev->value != 1 && ev->value != 2 )
+ return; // Down or Repeat only
+ if ( ev->code != KEY_BRIGHTNESSDOWN && ev->code != KEY_BRIGHTNESSUP )
+ return; // Match our keys
+
+ // Get current brightness
+ len = file_get_contents( _brightness_file, buf, sizeof(buf) );
+ if ( len <= 0 ) {
+ fprintf( stderr, "Cannot read brightness\n" );
+ return;
+ }
+
+ // Calculate new brightness
+ val = atoi( buf );
+ newval = val + _step * ( ev->code == KEY_BRIGHTNESSUP ? 1 : -1 );
+ if ( newval < _min ) {
+ newval = _min;
+ } else if ( newval > _max ) {
+ newval = _max;
+ }
+ if ( val == newval )
+ return;
+
+ // Set new brightness
+ len = snprintf( buf, sizeof(buf), "%d", newval );
+ if ( len == -1 ) {
+ perror( "Brightness snprintf fail" );
+ } else if ( len == 0 ) {
+ fprintf( stderr, "Brightness snprintf returned zero\n" );
+ } else {
+ file_put_contents( _brightness_file, buf, (size_t)len );
+ }
+}
+
+/**
+ * Read file contents into buffer.
+ */
+ssize_t file_get_contents( const char *filename, char *buffer, size_t len )
+{
+ ssize_t ret;
+ size_t done = 0;
+ ssize_t bytes_read = -1;
+ int fd;
+
+ if ( len <= 1 ) {
+ if ( len == 1 ) {
+ buffer[0] = '\0';
+ }
+ return 0;
+ }
+ len--;
+
+ fd = open( filename, O_RDONLY );
+ if ( fd == -1 ) {
+ fprintf( stderr, "%s: ", filename );
+ perror( "Error opening file" );
+ return -1;
+ }
+
+ while ( done < len && ( bytes_read = read( fd, buffer + done, len - done ) ) > 0 ) {
+ done += bytes_read;
+ }
+ if ( bytes_read == -1 ) {
+ fprintf( stderr, "%s: ", filename );
+ perror( "Error reading file" );
+ ret = -1;
+ } else {
+ ret = (ssize_t)done;
+ buffer[done] = '\0';
+ }
+
+ close( fd );
+ return ret;
+}
+
+/**
+ * Write buffer contents to file.
+ */
+ssize_t file_put_contents( const char *filename, const char *buffer, size_t len )
+{
+ ssize_t ret;
+ size_t done = 0;
+ ssize_t bytes_written = -1;
+ int fd = open( filename, O_WRONLY | O_CREAT | O_TRUNC, 0644 );
+
+ if ( fd == -1 ) {
+ fprintf( stderr, "%s: ", filename );
+ perror( "Error opening file" );
+ return -1;
+ }
+
+ while ( done < len && ( bytes_written = write( fd, buffer + done, len - done ) ) > 0 ) {
+ done += bytes_written;
+ }
+ if ( bytes_written == -1 ) {
+ fprintf( stderr, "%s: ", filename );
+ perror( "Error writing file" );
+ ret = -1;
+ } else {
+ ret = (ssize_t)done;
+ }
+ close( fd );
+ return ret;
+}