diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b34595..a8300b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,12 +1,13 @@ cmake_minimum_required(VERSION 3.12) project(thinkpad_keyboard_backlight) set(PROJECT_VERSION_MAJOR 1) -set(PROJECT_VERSION_MINOR 2) -set(PROJECT_VERSION_PATCH 1) +set(PROJECT_VERSION_MINOR 3) +set(PROJECT_VERSION_PATCH 0) # Add version compile definition add_compile_definitions(VERSION="${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") + set(CMAKE_INSTALL_PREFIX /usr/bin) set(CMAKE_CXX_STANDARD 17) @@ -28,6 +29,8 @@ set(OPTIMIZATION_LEVEL "-O3") # "" None set(DEBUG_LEVEL "") +set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DDEBUG=1") + # Configure C++ compiler flags set(CMAKE_CXX_FLAGS "${OPTIMIZATION_LEVEL} \ ${DEBUG_LEVEL} \ @@ -40,10 +43,16 @@ set(APP_TARGET_PATH ${CMAKE_INSTALL_PREFIX}/keyboard_backlight) set(APP_NAME keyboard_backlight) find_package (Threads) +# Some versions of gcc need to link filesystem lib +if (CMAKE_CXX_COMPILER_ID STREQUAL GNU) + set(CXX_FILESYSTEM_LIBRARIES stdc++fs) +else() + set(CXX_FILESYSTEM_LIBRARIES) +endif() add_executable(${APP_NAME} kbd_backlight.cpp) -target_link_libraries (keyboard_backlight ${CMAKE_THREAD_LIBS_INIT}) +target_link_libraries (keyboard_backlight ${CMAKE_THREAD_LIBS_INIT} ${CXX_FILESYSTEM_LIBRARIES}) install(TARGETS keyboard_backlight DESTINATION ${CMAKE_INSTALL_PREFIX}) diff --git a/PKGBUILD b/PKGBUILD index 275aeeb..dcc2ab7 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -9,6 +9,7 @@ url='https://github.com/alexmohr/keyboard-backlight' license=('MIT') depends=('libinput') makedepends=('git' 'cmake' 'gcc') +backup=('etc/systemd/system/keyboard_backlight.service') source=("git+https://github.com/alexmohr/keyboard-backlight") sha512sums=('SKIP') diff --git a/README.md b/README.md index c451523..3922cff 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,12 @@ other devices as well ### Arch Linux The package is in the AUR and called ``tp-kb-backlight-git`` -### Install from source -To build the binary run +### Build from source +Requirements to build the software from source are: +* Compiler with C++17 suppport +* CMake + +To build the binary run: ```` mkdir build cd build @@ -52,7 +56,7 @@ sudo rm /usr/bin/keyboard_backlight ## Configuration ```` -keyboard_backlight 1.2.1 +keyboard_backlight 1.3.0 -h show this help -i ignore an input device This device does not re enable keyboard backlight. @@ -65,9 +69,8 @@ keyboard_backlight 1.2.1 1 use all internal mice only 2 ignore mice -b set keyboard backlight device path - defaults to /sys/class/leds/tpacpi::kbd_backlight + defaults to /sys/class/leds/tpacpi::kbd_backlight/brightness -f stay in foreground and do not start daemon - -s Set a brightness value from 0..2 and exit + -s Set a brightness value and exit ```` - diff --git a/kbd_backlight.cpp b/kbd_backlight.cpp index 362f2fb..0cf0cad 100644 --- a/kbd_backlight.cpp +++ b/kbd_backlight.cpp @@ -35,11 +35,13 @@ #include #include +#include #include #include #include #include #include +#include using namespace std::chrono_literals; @@ -48,6 +50,8 @@ uint64_t originalBrightness_; uint64_t currentBrightness_; bool end_ = false; +const std::string DEFAULT_BACKLIGHT_PATH = "/sys/class/leds/tpacpi::kbd_backlight/brightness"; + enum MOUSE_MODE { ALL = 0, @@ -55,7 +59,15 @@ enum MOUSE_MODE { NONE = 2 }; -void help(const char* name) { +#if DEBUG +#define print_debug(fmt, ...) printf("%s:%d: " fmt, __FILE__, __LINE__, __VA_ARGS__) +#define print_debug_n(fmt) printf("%s:%d: " fmt, __FILE__, __LINE__) +#else +#define print_debug(...) +#define print_debug_n(...) +#endif + +void help(const char *name) { printf("%s %s \n", name, VERSION); printf("" " -h show this help\n" @@ -70,9 +82,10 @@ void help(const char* name) { " 1 use all internal mice only\n" " 2 ignore mice\n" " -b set keyboard backlight device path\n" - " defaults to /sys/class/leds/tpacpi::kbd_backlight\n" + " defaults to %s\n" " -f stay in foreground and do not start daemon\n" - " -s Set a brightness value from 0..2 and exit\n" + " -s Set a brightness value and exit\n", + DEFAULT_BACKLIGHT_PATH.c_str() ); } @@ -124,10 +137,68 @@ bool is_device_ignored(const std::string &device, return false; } -void get_devices_for_path(const std::vector &ignoredDevices, - const std::string &devicePath, - const std::regex ®ex, - std::vector &devices) { +/* Get keyboards from /proc/bus/input/devices + * Example entry + I: Bus=0011 Vendor=0001 Product=0001 Version=ab54 + N: Name="AT Translated Set 2 keyboard" + P: Phys=isa0060/serio0/input0 + S: Sysfs=/devices/platform/i8042/serio0/input/input3 + U: Uniq= + H: Handlers=sysrq kbd event3 leds + B: PROP=0 + B: EV=120013 + B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe + */ +void get_keyboards(std::vector &ignoredDevices, + std::vector &keyboards) { + const std::string path = "/proc/bus/input/devices"; + std::ifstream file(path); + if (!file.is_open()) { + print_debug("Failed to open %s...\n", path.c_str()); + return; + } + + bool isKeyboard = false; + std::string line; + std::string token; + std::istringstream ss; + while (std::getline(file, line)) { + // get device name + if (line.find("Name=") != std::string::npos) { + isKeyboard = line.find("keyboard") != std::string::npos; + if (isKeyboard) { + print_debug("Detected keyboard: %s\n", line.c_str()); + } else { + print_debug("Ignoring non keyboard device: %s\n", line.c_str()); + } + } + + if (line.find("Handlers=") != std::string::npos) { + if (!isKeyboard) { + continue; + } + + ss = std::istringstream(line); + while (std::getline(ss, token, ' ')) { + if (token.find("event") != std::string::npos) { + std::string deviceEventPath = "/dev/input/" + token; + if (!is_device_ignored(deviceEventPath, ignoredDevices)) { + print_debug_n("Added keyboard\n"); + keyboards.emplace_back(deviceEventPath); + } else { + print_debug_n("Keyboard is ignored\n"); + } + break; + } + } + } + } +} + +void get_devices_in_path(const std::vector &ignoredDevices, + const std::string &devicePath, + const std::regex ®ex, + std::vector &devices) { for (const auto &dev : std::filesystem::directory_iterator(devicePath)) { if (is_device_ignored(dev.path(), ignoredDevices)) { continue; @@ -157,8 +228,10 @@ std::vector open_devices(const std::vector &input_devices) { } return fds; } + void brightness_control(const std::string &brightnessPath, unsigned long timeoutMs) { + unsigned long tmpBrightness = currentBrightness_; while (!end_) { auto passedMs = std::chrono::duration_cast< std::chrono::milliseconds>( @@ -167,6 +240,7 @@ void brightness_control(const std::string &brightnessPath, if (lastEvent_ < std::chrono::system_clock::now()) { auto sleepTime = std::chrono::milliseconds(timeoutMs - passedMs.count()); if (0 != sleepTime.count()) { + print_debug("Sleeping for %lu ms\n", sleepTime.count()); std::this_thread::sleep_for(sleepTime); } } @@ -174,26 +248,20 @@ void brightness_control(const std::string &brightnessPath, passedMs = std::chrono::duration_cast< std::chrono::milliseconds>( std::chrono::system_clock::now() - lastEvent_); -#if DEBUG - printf("passed: %lu\n", passedMs.count()); -#endif + print_debug("Ms since last event: %lu\n", passedMs.count()); if (passedMs.count() >= static_cast(timeoutMs)) { -#if DEBUG - printf("timeoutMs reached \n"); - printf("o: %lu c: %lu\n", originalBrightness_, currentBrightness_); -#endif + print_debug_n("Timeout reached \n"); - if (currentBrightness_ != 0) { - file_read_uint64(brightnessPath, &originalBrightness_); + file_read_uint64(brightnessPath, &tmpBrightness); + if (tmpBrightness != 0) { + originalBrightness_ = tmpBrightness; currentBrightness_ = 0; - file_write_uint64(brightnessPath, 0); - -#if DEBUG - printf("o: %lu c: %lu\n", originalBrightness_, currentBrightness_); - printf("turning off \n"); -#endif + print_debug("New Original brightness: %lu New Current Brightness: %lu\n", + originalBrightness_, + currentBrightness_); + print_debug_n("Turning lights off\n"); } lastEvent_ = std::chrono::system_clock::now(); @@ -212,9 +280,7 @@ void read_events(int devFd, const std::string &brightnessPath) { file_write_uint64(brightnessPath, originalBrightness_); currentBrightness_ = originalBrightness_; -#if DEBUG - printf("on\n"); -#endif + print_debug("Event in fd %i, turning lights on\n", devFd); } } } @@ -233,10 +299,16 @@ void signal_handler(int sig) { } bool is_brightness_writable(const std::string &brightnessPath) { + std::filesystem::path p(brightnessPath); + if (!std::filesystem::exists(p)) { + printf("Brightness device %s does not exist\n", brightnessPath.c_str()); + return false; + } + if (!file_read_uint64(brightnessPath, &originalBrightness_) || !file_write_uint64(brightnessPath, originalBrightness_)) { - std::cout << "Write access to brightness device descriptor failed.\n" - "Please run with root privileges" << std::endl; + printf("Write access to brightness device %s failed." + " Please run with root privileges", brightnessPath.c_str()); return false; } return true; @@ -253,7 +325,7 @@ void parse_opts(int argc, int c; std::istringstream ss; std::string token; - unsigned long mode; + long mode; while ((c = getopt(argc, argv, "hs:i:t:m:b:f")) != -1) { switch (c) { @@ -267,10 +339,26 @@ void parse_opts(int argc, ss = std::istringstream(optarg); while (std::getline(ss, token, ' ')) { ignoredDevices.push_back(token); + + // if the device is a symlink add the actual target to the + // ignored device list too + std::filesystem::path p = token; + if (!std::filesystem::exists(p)) + continue; + + std::filesystem::current_path(p.parent_path()); + if (std::filesystem::is_symlink(p)) { + p = std::filesystem::read_symlink(p); + } + + if (!p.is_absolute()) { + p = std::filesystem::canonical(p); + } + ignoredDevices.push_back(p); } break; case 'm': - mode = strtoul(optarg, nullptr, 0); + mode = strtol(optarg, nullptr, 0); if ((MOUSE_MODE::ALL > mode) | (MOUSE_MODE::NONE < mode)) { printf("%s is not a valid mouse mode\n", optarg); exit(EXIT_FAILURE); @@ -286,10 +374,6 @@ void parse_opts(int argc, break; case 's': setBrightness = strtol(optarg, nullptr, 0); - if (setBrightness > 2 || setBrightness < 0) { - printf("%s is not a valid brightness\n", optarg); - exit(EXIT_FAILURE); - } break; case 'h': default: @@ -311,8 +395,10 @@ int main(int argc, char **argv) { unsigned long timeout = 15; long setBrightness = -1; MOUSE_MODE mouseMode = MOUSE_MODE::ALL; - std::string backlightPath = "/sys/class/leds/tpacpi::kbd_backlight"; + bool foreground = false; + std::string backlightPath = DEFAULT_BACKLIGHT_PATH; + print_debug_n("Parsing options...\n"); parse_opts(argc, argv, ignoredDevices, @@ -321,24 +407,26 @@ int main(int argc, char **argv) { backlightPath, foreground, setBrightness); + print_debug("Using backlight device: %s\n", backlightPath.c_str()); - get_devices_for_path(ignoredDevices, - "/dev/input/by-path", - std::regex(".*event\\-kbd.*"), - inputDevices); + print_debug_n("Getting keyboards...\n"); + get_keyboards(ignoredDevices, inputDevices); + if (inputDevices.empty()) { + std::cout << "Warning no keyboards found!" << std::endl; + } switch (mouseMode) { case ALL: - get_devices_for_path(ignoredDevices, - "/dev/input/", - std::regex(".*mice.*"), - inputDevices); + get_devices_in_path(ignoredDevices, + "/dev/input/", + std::regex(".*mice.*"), + inputDevices); break; case INTERNAL: - get_devices_for_path(ignoredDevices, - "/dev/input/by-path", - std::regex("..*event\\-mouse.*"), - inputDevices); + get_devices_in_path(ignoredDevices, + "/dev/input/by-path", + std::regex("..*event\\-mouse.*"), + inputDevices); break; case NONE: break; @@ -349,17 +437,12 @@ int main(int argc, char **argv) { exit(EXIT_FAILURE); } - if (backlightPath.at(backlightPath.size() - 1) != '/') { - backlightPath += '/'; - } - - std::string brightnessPath = backlightPath + "brightness"; - if (!is_brightness_writable(brightnessPath)){ - exit(EXIT_FAILURE); + if (!is_brightness_writable(backlightPath)) { + exit(EXIT_FAILURE); } if (setBrightness >= 0) { - file_write_uint64(brightnessPath, setBrightness); + file_write_uint64(backlightPath, setBrightness); exit(0); } @@ -383,13 +466,14 @@ int main(int argc, char **argv) { f.emplace_back(std::async(std::launch::async, read_events, fd, - brightnessPath)); + backlightPath)); } - brightness_control(brightnessPath, timeout * 1000); + brightness_control(backlightPath, timeout * 1000); for (const auto &fd : fds) { close(fd); } + exit(0); }