diff --git a/.gitignore b/.gitignore index b876454..8dcc23b 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ _deps CMakeUserPresets.json # Binary itself -hyprevents +autotablet # CLion # JetBrains specific template is maintained in a separate JetBrains.gitignore that can diff --git a/CMakeLists.txt b/CMakeLists.txt index 207abee..f36bdeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.2) project( - hyprevents + autotablet DESCRIPTION "React on hyprland events" LANGUAGES CXX ) @@ -9,6 +9,10 @@ project( set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}) set(CMAKE_EXPORT_COMPILE_COMMANDS, 1) +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() + set(CMAKE_CXX_FLAGS "-Wall -Wextra -W") set(CMAKE_CXX_FLAGS_DEBUG "-g") set(CMAKE_CXX_FLAGS_RELEASE "-O3") @@ -17,7 +21,7 @@ set(CMAKE_CXX_STANDARD 23) add_subdirectory(src) add_custom_target(run - COMMAND hyprevents - DEPENDS hyprevents + COMMAND autotablet + DEPENDS autotablet WORKING_DIRECTORY src ) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ea635e6..f1b0628 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,2 +1,3 @@ -add_executable(hyprevents main.cpp) -install(TARGETS hyprevents) +add_executable(autotablet main.cpp) +target_link_libraries(autotablet -ludev -linput) +install(TARGETS autotablet) diff --git a/src/main.cpp b/src/main.cpp index 41bbf44..60853a2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,102 +1,160 @@ -#include +#include #include #include #include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include #include -std::string instanceSignature; -int hypr_socket; -bool sigintReceived; +static volatile sig_atomic_t stop = 0; -std::string getRuntimeDir() { - const auto XDG = getenv("XDG_RUNTIME_DIR"); - - if (!XDG) { - const std::string USERID = std::to_string(getuid()); - return "/run/user/" + USERID + "/hypr"; - } - - return std::string(XDG) + "/hypr"; +static void sighandler(int signal) { + std::println("Received signal {}", signal); + stop = 1; } -int readLoop(int hypr_socket) { - constexpr size_t BUFFER_SIZE = 8192; - std::array buffer = {0}; - long sizeWritten = 0; +void close_restricted(int fd, [[maybe_unused]] void *user_data) { + auto file = fdopen(fd, "r+"); + std::fclose(file); +} - while (!sigintReceived) { - sizeWritten = read(hypr_socket, buffer.data(), BUFFER_SIZE); - if (sizeWritten < 0 && errno != EAGAIN) { - if (errno != EINTR) - std::println("Couldn't read (5): {}: {}", strerror(errno), errno); - close(hypr_socket); - return 5; +int open_restricted(const char *path, int flags, + [[maybe_unused]] void *user_data) { + auto fd = open(path, flags); + if (fd < 0) { + perror(std::format("Cannot open {}", path).c_str()); + return -errno; + } + return fd; +} + +const static struct libinput_interface interface = { + .open_restricted = open_restricted, + .close_restricted = close_restricted, +}; + +class LibInputListener { +public: + LibInputListener(struct udev *udev, const char *seat, std::string *filter) { + deviceFilter = filter; + keyboard_pid = -1; + li = libinput_udev_create_context(&interface, nullptr, udev); + if (!li) { + throw std::runtime_error("Cannot create udev context"); } - if (sizeWritten == 0) - break; - - if (sizeWritten > 0) { - std::println("{}", std::string(buffer.data(), sizeWritten)); - buffer.fill('\0'); + if (libinput_udev_assign_seat(li, seat) == -1) { + libinput_unref(li); + throw std::runtime_error("Cannot assign seat"); } - - usleep(100000); } - close(hypr_socket); - return 0; -} -void signalHandler(int signal) { - std::println("Received {}", signal); - sigintReceived = true; -} + ~LibInputListener() { + if (li) { + libinput_unref(li); + } + } -int main() { - auto runtimeDir = getRuntimeDir(); - instanceSignature = std::string(std::getenv("HYPRLAND_INSTANCE_SIGNATURE")); - if (instanceSignature.empty()) { - std::println("Hyprland instance isn't running."); + void listen() { + libinput_dispatch(li); + struct libinput_event *event; + struct pollfd fds; + fds.fd = libinput_get_fd(li); + fds.events = POLLIN; + fds.revents = 0; + + while (!stop && poll(&fds, 1, -1) > -1) { + libinput_dispatch(li); + while ((event = libinput_get_event(li))) { + handleEvent(event); + + libinput_event_destroy(event); + libinput_dispatch(li); + } + }; + } + +private: + struct libinput *li; + std::string *deviceFilter; + pid_t keyboard_pid; + + void handleLaptopMode() { + if (keyboard_pid > 0) { + std::println("Stopping on-screen keyboard with PID {}", keyboard_pid); + kill(keyboard_pid, SIGTERM); + keyboard_pid = -1; + } + } + + void handleTabletMode() { + keyboard_pid = fork(); + if (keyboard_pid == 0) { + std::println("Starting on-screen keyboard"); + const char *binary = "squeekboard"; + const char *const argv[] = {nullptr}; + auto ret = execvp(binary, const_cast(argv)); + if (ret == -1) { + std::cerr << "execv failed: " << strerror(errno) << std::endl; + } + std::println("I was supposed to execve, but got {}", ret); + exit(ret); + } else if (keyboard_pid < 0) { + std::cerr << "Fork failed: " << strerror(errno) << std::endl; + } else { + std::println("Started on-screen keyboard with PID {}", keyboard_pid); + } + } + + void handleEvent(struct libinput_event *event) { + auto ty = libinput_event_get_type(event); + if (ty != LIBINPUT_EVENT_SWITCH_TOGGLE) { + return; + } + auto device = libinput_event_get_device(event); + auto device_name = std::string(libinput_device_get_name(device)); + if (device_name.compare(*deviceFilter) != 0) { + return; + } + struct libinput_event_switch *event_switch = + libinput_event_get_switch_event(event); + auto switch_state = libinput_event_switch_get_switch_state(event_switch); + if (switch_state == LIBINPUT_SWITCH_STATE_ON) { + handleTabletMode(); + } else { + handleLaptopMode(); + } + }; +}; + +int main(int argc, char **argv) { + if (argc < 2) { + std::println("Yo, give me proper tablet switch device name."); return 1; } + std::string deviceFilter(argv[1]); - auto socket_fpath = runtimeDir + "/" + instanceSignature + "/.socket2.sock"; - std::println("Found socket file path: {}", socket_fpath); + struct udev *udev = udev_new(); - struct sockaddr_un socket_address; - socket_address.sun_family = AF_UNIX; - strncpy(socket_address.sun_path, socket_fpath.c_str(), - sizeof(socket_address.sun_path) - 1); + signal(SIGINT, sighandler); + signal(SIGTERM, sighandler); - auto hypr_socket = socket(AF_UNIX, SOCK_STREAM, 0); - if (hypr_socket < 1) { - std::println("Cannot create socket (1)."); + try { + LibInputListener listener(udev, "seat0", &deviceFilter); + listener.listen(); + } catch (const std::exception &e) { + std::cerr << "Error: " << e.what() << std::endl; + udev_unref(udev); return 1; } - - std::signal(SIGINT, signalHandler); - std::signal(SIGTERM, signalHandler); - - auto timeout = timeval{.tv_sec = 5, .tv_usec = 0}; - if (setsockopt(hypr_socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, - sizeof(struct timeval)) < 0) { - std::println("Cannot set timeout for a socket (2)."); - return 2; - } - - if (connect(hypr_socket, (struct sockaddr *)&socket_address, - SUN_LEN(&socket_address)) < 0) { - perror("connect"); - close(hypr_socket); - std::println("Cannot connect to {} (3).", socket_fpath); - return 3; - } - - readLoop(hypr_socket); - + udev_unref(udev); return 0; }