Switched to libinput.

This commit is contained in:
2025-09-10 10:50:45 +02:00
parent 63191dd5dd
commit 9fc3b3c790
4 changed files with 146 additions and 83 deletions

2
.gitignore vendored
View File

@ -15,7 +15,7 @@ _deps
CMakeUserPresets.json CMakeUserPresets.json
# Binary itself # Binary itself
hyprevents autotablet
# CLion # CLion
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can # JetBrains specific template is maintained in a separate JetBrains.gitignore that can

View File

@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.2) cmake_minimum_required(VERSION 3.2)
project( project(
hyprevents autotablet
DESCRIPTION "React on hyprland events" DESCRIPTION "React on hyprland events"
LANGUAGES CXX LANGUAGES CXX
) )
@ -9,6 +9,10 @@ project(
set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR}) set(CMAKE_INSTALL_PREFIX ${PROJECT_SOURCE_DIR})
set(CMAKE_EXPORT_COMPILE_COMMANDS, 1) 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 "-Wall -Wextra -W")
set(CMAKE_CXX_FLAGS_DEBUG "-g") set(CMAKE_CXX_FLAGS_DEBUG "-g")
set(CMAKE_CXX_FLAGS_RELEASE "-O3") set(CMAKE_CXX_FLAGS_RELEASE "-O3")
@ -17,7 +21,7 @@ set(CMAKE_CXX_STANDARD 23)
add_subdirectory(src) add_subdirectory(src)
add_custom_target(run add_custom_target(run
COMMAND hyprevents COMMAND autotablet
DEPENDS hyprevents DEPENDS autotablet
WORKING_DIRECTORY src WORKING_DIRECTORY src
) )

View File

@ -1,2 +1,3 @@
add_executable(hyprevents main.cpp) add_executable(autotablet main.cpp)
install(TARGETS hyprevents) target_link_libraries(autotablet -ludev -linput)
install(TARGETS autotablet)

View File

@ -1,102 +1,160 @@
#include <csignal> #include <cerrno>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#include <exception>
#include <fcntl.h>
#include <format>
#include <iostream>
#include <libinput.h>
#include <libudev.h>
#include <print> #include <print>
#include <string> #include <signal.h>
#include <sys/socket.h> #include <stdexcept>
#include <sys/un.h> #include <sys/poll.h>
#include <unistd.h> #include <unistd.h>
std::string instanceSignature; static volatile sig_atomic_t stop = 0;
int hypr_socket;
bool sigintReceived;
std::string getRuntimeDir() { static void sighandler(int signal) {
const auto XDG = getenv("XDG_RUNTIME_DIR"); std::println("Received signal {}", signal);
stop = 1;
if (!XDG) {
const std::string USERID = std::to_string(getuid());
return "/run/user/" + USERID + "/hypr";
}
return std::string(XDG) + "/hypr";
} }
int readLoop(int hypr_socket) { void close_restricted(int fd, [[maybe_unused]] void *user_data) {
constexpr size_t BUFFER_SIZE = 8192; auto file = fdopen(fd, "r+");
std::array<char, BUFFER_SIZE> buffer = {0}; std::fclose(file);
long sizeWritten = 0; }
while (!sigintReceived) { int open_restricted(const char *path, int flags,
sizeWritten = read(hypr_socket, buffer.data(), BUFFER_SIZE); [[maybe_unused]] void *user_data) {
if (sizeWritten < 0 && errno != EAGAIN) { auto fd = open(path, flags);
if (errno != EINTR) if (fd < 0) {
std::println("Couldn't read (5): {}: {}", strerror(errno), errno); perror(std::format("Cannot open {}", path).c_str());
close(hypr_socket); return -errno;
return 5; }
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) if (libinput_udev_assign_seat(li, seat) == -1) {
break; libinput_unref(li);
throw std::runtime_error("Cannot assign seat");
if (sizeWritten > 0) {
std::println("{}", std::string(buffer.data(), sizeWritten));
buffer.fill('\0');
} }
usleep(100000);
} }
close(hypr_socket);
return 0;
}
void signalHandler(int signal) { ~LibInputListener() {
std::println("Received {}", signal); if (li) {
sigintReceived = true; libinput_unref(li);
} }
}
int main() { void listen() {
auto runtimeDir = getRuntimeDir(); libinput_dispatch(li);
instanceSignature = std::string(std::getenv("HYPRLAND_INSTANCE_SIGNATURE")); struct libinput_event *event;
if (instanceSignature.empty()) { struct pollfd fds;
std::println("Hyprland instance isn't running."); 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<char *const *>(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; return 1;
} }
std::string deviceFilter(argv[1]);
auto socket_fpath = runtimeDir + "/" + instanceSignature + "/.socket2.sock"; struct udev *udev = udev_new();
std::println("Found socket file path: {}", socket_fpath);
struct sockaddr_un socket_address; signal(SIGINT, sighandler);
socket_address.sun_family = AF_UNIX; signal(SIGTERM, sighandler);
strncpy(socket_address.sun_path, socket_fpath.c_str(),
sizeof(socket_address.sun_path) - 1);
auto hypr_socket = socket(AF_UNIX, SOCK_STREAM, 0); try {
if (hypr_socket < 1) { LibInputListener listener(udev, "seat0", &deviceFilter);
std::println("Cannot create socket (1)."); listener.listen();
} catch (const std::exception &e) {
std::cerr << "Error: " << e.what() << std::endl;
udev_unref(udev);
return 1; return 1;
} }
udev_unref(udev);
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);
return 0; return 0;
} }