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
# Binary itself
hyprevents
autotablet
# CLion
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can

View File

@ -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
)

View File

@ -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)

View File

@ -1,102 +1,160 @@
#include <csignal>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <fcntl.h>
#include <format>
#include <iostream>
#include <libinput.h>
#include <libudev.h>
#include <print>
#include <string>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>
#include <stdexcept>
#include <sys/poll.h>
#include <unistd.h>
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<char, BUFFER_SIZE> 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<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;
}
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;
}