diff --git a/.gitignore b/.gitignore
index 78d5d3a..5e82c02 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,3 +22,5 @@ chiaki.rb
*.jks
secret.tar
keystore-env.sh
+compile_commands.json
+.ccls-cache
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 4c203ef..841e1d3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -65,6 +65,7 @@ if(CHIAKI_ENABLE_CLI)
endif()
if(CHIAKI_ENABLE_GUI)
+ add_subdirectory(setsu)
add_subdirectory(gui)
endif()
diff --git a/setsu/CMakeLists.txt b/setsu/CMakeLists.txt
new file mode 100644
index 0000000..e230c49
--- /dev/null
+++ b/setsu/CMakeLists.txt
@@ -0,0 +1,24 @@
+
+cmake_minimum_required(VERSION 3.2)
+
+project(libsetsu)
+
+option(SETSU_BUILD_TEST "Build testing executable for libsetsu" OFF)
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
+
+add_library(setsu
+ include/setsu.h
+ src/setsu.c)
+
+target_include_directories(setsu PUBLIC include)
+
+find_package(UDEV REQUIRED)
+target_link_libraries(setsu UDEV::libudev)
+
+if(SETSU_BUILD_TEST)
+ add_executable(setsutest
+ test/main.c)
+ target_link_libraries(setsutest setsu)
+endif()
+
diff --git a/setsu/cmake/FindUDEV.cmake b/setsu/cmake/FindUDEV.cmake
new file mode 100644
index 0000000..3b6b1e0
--- /dev/null
+++ b/setsu/cmake/FindUDEV.cmake
@@ -0,0 +1,26 @@
+# Provides: UDEV::libudev
+
+set(_prefix UDEV)
+set(_target "${_prefix}::libudev")
+
+find_package(PkgConfig REQUIRED)
+
+pkg_check_modules("${_prefix}" libudev)
+
+function(resolve_location)
+
+endfunction()
+
+if(${_prefix}_FOUND)
+ add_library("${_target}" INTERFACE IMPORTED)
+ target_link_libraries("${_target}" INTERFACE ${${_prefix}_LIBRARIES})
+ target_include_directories("${_target}" INTERFACE ${${_prefix}_INCLUDE_DIRS})
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args("${_prefix}"
+ REQUIRED_VARS "${_prefix}_LIBRARIES"
+ VERSION_VAR "${_prefix}_VERSION")
+
+unset(_prefix)
+unset(_target)
diff --git a/setsu/include/setsu.h b/setsu/include/setsu.h
new file mode 100644
index 0000000..8df7536
--- /dev/null
+++ b/setsu/include/setsu.h
@@ -0,0 +1,26 @@
+/*
+ * This file is part of Chiaki.
+ *
+ * Chiaki is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Chiaki is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Chiaki. If not, see .
+ */
+
+#ifndef _SETSU_H
+#define _SETSU_H
+
+typedef struct setsu_ctx_t SetsuCtx;
+
+SetsuCtx *setsu_ctx_new();
+void setsu_ctx_free(SetsuCtx *ctx);
+
+#endif
diff --git a/setsu/src/setsu.c b/setsu/src/setsu.c
new file mode 100644
index 0000000..76ae7d5
--- /dev/null
+++ b/setsu/src/setsu.c
@@ -0,0 +1,177 @@
+
+#include
+
+#include
+
+#include
+#include
+#include
+
+
+#include
+
+typedef struct setsu_device_t
+{
+ struct setsu_device_t *next;
+ char *path;
+ int fd;
+} SetsuDevice;
+
+struct setsu_ctx_t
+{
+ struct udev *udev_ctx;
+ SetsuDevice *dev;
+};
+
+
+static void scan(SetsuCtx *ctx);
+static void update_device(SetsuCtx *ctx, struct udev_device *dev, bool added);
+static SetsuDevice *connect(SetsuCtx *ctx, const char *path);
+static void disconnect(SetsuCtx *ctx, SetsuDevice *dev);
+
+SetsuCtx *setsu_ctx_new()
+{
+ SetsuCtx *ctx = malloc(sizeof(SetsuCtx));
+ if(!ctx)
+ return NULL;
+
+ ctx->dev = NULL;
+
+ ctx->udev_ctx = udev_new();
+ if(!ctx->udev_ctx)
+ {
+ free(ctx);
+ return NULL;
+ }
+
+ // TODO: monitor
+
+ scan(ctx);
+
+ return ctx;
+}
+
+void setsu_ctx_free(SetsuCtx *ctx)
+{
+ while(ctx->dev)
+ disconnect(ctx, ctx->dev);
+ udev_unref(ctx->udev_ctx);
+ free(ctx);
+}
+
+static void scan(SetsuCtx *ctx)
+{
+ struct udev_enumerate *udev_enum = udev_enumerate_new(ctx->udev_ctx);
+ if(!udev_enum)
+ return;
+
+ if(udev_enumerate_add_match_subsystem(udev_enum, "input") < 0)
+ goto beach;
+
+ if(udev_enumerate_add_match_property(udev_enum, "ID_INPUT_TOUCHPAD", "1") < 0)
+ goto beach;
+
+ if(udev_enumerate_scan_devices(udev_enum) < 0)
+ goto beach;
+
+ for(struct udev_list_entry *entry = udev_enumerate_get_list_entry(udev_enum); entry; entry = udev_list_entry_get_next(entry))
+ {
+ const char *path = udev_list_entry_get_name(entry);
+ if(!path)
+ continue;
+ struct udev_device *dev = udev_device_new_from_syspath(ctx->udev_ctx, path);
+ if(!dev)
+ continue;
+ update_device(ctx, dev, true);
+ udev_device_unref(dev);
+ }
+
+beach:
+ udev_enumerate_unref(udev_enum);
+}
+
+static bool is_device_interesting(struct udev_device *dev)
+{
+ static const char *device_ids[] = {
+ // vendor id, model id
+ "054c", "05c4", // DualShock 4 USB
+ NULL
+ };
+
+ // Filter mouse-device (/dev/input/mouse*) away and only keep the evdev (/dev/input/event*) one:
+ if(!udev_device_get_property_value(dev, "ID_INPUT_TOUCHPAD_INTEGRATION"))
+ return false;
+
+ const char *vendor = udev_device_get_property_value(dev, "ID_VENDOR_ID");
+ const char *model = udev_device_get_property_value(dev, "ID_MODEL_ID");
+ if(!vendor || !model)
+ return false;
+
+ for(const char **dev_id = device_ids; *dev_id; dev_id += 2)
+ {
+ if(!strcmp(vendor, dev_id[0]) && !strcmp(model, dev_id[1]))
+ return true;
+ }
+ return false;
+}
+
+static void update_device(SetsuCtx *ctx, struct udev_device *dev, bool added)
+{
+ if(!is_device_interesting(dev))
+ return;
+ const char *path = udev_device_get_devnode(dev);
+ if(!path)
+ return;
+
+ for(SetsuDevice *dev = ctx->dev; dev; dev=dev->next)
+ {
+ if(!strcmp(dev->path, path))
+ {
+ if(added)
+ return; // already added, do nothing
+ disconnect(ctx, dev);
+ }
+ }
+ connect(ctx, path);
+}
+
+static SetsuDevice *connect(SetsuCtx *ctx, const char *path)
+{
+ SetsuDevice *dev = malloc(sizeof(SetsuDevice));
+ if(!dev)
+ return NULL;
+ dev->path = strdup(path);
+ if(!dev->path)
+ {
+ free(dev);
+ return NULL;
+ }
+
+ printf("connect %s\n", dev->path);
+ //dev->fd = open(dev->path, O_RDONLY | O_NONBLOCK);
+
+ dev->next = ctx->dev;
+ ctx->dev = dev;
+ return dev;
+}
+
+static void disconnect(SetsuCtx *ctx, SetsuDevice *dev)
+{
+ if(ctx->dev == dev)
+ ctx->dev = dev->next;
+ else
+ {
+ for(SetsuDevice *pdev = ctx->dev; pdev; pdev=pdev->next)
+ {
+ if(pdev->next == dev)
+ {
+ pdev->next = dev->next;
+ break;
+ }
+ }
+ }
+
+ free(dev->path);
+ free(dev);
+}
+
diff --git a/setsu/test/main.c b/setsu/test/main.c
new file mode 100644
index 0000000..823c283
--- /dev/null
+++ b/setsu/test/main.c
@@ -0,0 +1,25 @@
+
+#include
+
+#include
+#include
+
+SetsuCtx *ctx;
+
+void quit()
+{
+ setsu_ctx_free(ctx);
+}
+
+int main()
+{
+ ctx = setsu_ctx_new();
+ if(!ctx)
+ {
+ printf("Failed to init setsu\n");
+ return 1;
+ }
+ atexit(quit);
+ return 0;
+}
+