summaryrefslogblamecommitdiffstats
path: root/core/modules/slx-brightness/src/main.c
blob: 410154e087945d5548e58398645a6828aaae5445 (plain) (tree)





























































































































































































































































































































                                                                                                                       
#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;
}