From add0b32cd189f37495cc077408b748f486b7d70c Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Mon, 11 Mar 2024 14:32:38 +0100 Subject: [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. --- .../data/opt/openslx/bin/debug_report | 1 + .../multi-user.target.wants/slx-brightness.service | 1 + .../data/etc/systemd/system/slx-brightness.service | 7 + .../opt/openslx/scripts/systemd-slx_brightness | 78 +++++ core/modules/slx-brightness/module.build | 8 + core/modules/slx-brightness/module.conf | 5 + core/modules/slx-brightness/src/main.c | 318 +++++++++++++++++++++ 7 files changed, 418 insertions(+) create mode 120000 core/modules/slx-brightness/data/etc/systemd/system/multi-user.target.wants/slx-brightness.service create mode 100644 core/modules/slx-brightness/data/etc/systemd/system/slx-brightness.service create mode 100755 core/modules/slx-brightness/data/opt/openslx/scripts/systemd-slx_brightness create mode 100644 core/modules/slx-brightness/module.build create mode 100644 core/modules/slx-brightness/module.conf create mode 100644 core/modules/slx-brightness/src/main.c 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 + + + + + + + +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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 \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; +} -- cgit v1.2.3-55-g7522