Initial commit

This commit is contained in:
mrsobakin 2025-03-26 13:11:43 +03:00
commit 9b34bfc435
No known key found for this signature in database
GPG key ID: 325CBF665E4FFD6E
7 changed files with 456 additions and 0 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
*.o
*.so
*.a
ukb
.cache/
compile_commands.json

52
Makefile Normal file
View file

@ -0,0 +1,52 @@
CC = gcc
AR = ar
DEPS = json-c
CFLAGS = -Iinclude -Wall -O2 $(shell pkg-config --cflags $(DEPS))
LDFLAGS = $(shell pkg-config --libs $(DEPS))
LIB_SRC = src/ukb.c $(wildcard src/backends/*.c)
LIB_OBJ = $(LIB_SRC:.c=.o)
BIN_SRC = src/main.c
BIN_OBJ = $(BIN_SRC:.c=.o)
PREFIX ?= /usr/local
BIN_DIR = $(PREFIX)/bin
LIB_DIR = $(PREFIX)/lib
INCLUDE_DIR = $(PREFIX)/include/libukb
all: libukb.a libukb.so ukb
$(LIB_OBJ): %.o: %.c
$(CC) $(CFLAGS) -fPIC -DUKB_BACKENDS_INTERNAL -c $< -o $@
$(BIN_OBJ): %.o: %.c
$(CC) $(CFLAGS) -c $< -o $@
libukb.so: $(LIB_OBJ)
$(CC) -shared -o $@ $^ $(LDFLAGS)
libukb.a: $(LIB_OBJ)
$(AR) rcs $@ $^
ukb: $(BIN_OBJ) libukb.a
$(CC) $(BIN_OBJ) -L. -l:libukb.a -o $@ $(LDFLAGS)
.PHONY: install
install: all
install -d $(BIN_DIR) $(LIB_DIR) $(INCLUDE_DIR)
install -m 0755 ukb $(BIN_DIR)/
install -m 0644 libukb.a libukb.so $(LIB_DIR)/
install -m 0644 include/*.h $(INCLUDE_DIR)/
.PHONY: uninstall
uninstall:
rm -f $(BIN_DIR)/ukb
rm -f $(LIB_DIR)/libukb.a $(LIB_DIR)/libukb.so
rm -rf $(INCLUDE_DIR)
.PHONY: clean
clean:
rm -f $(LIB_OBJ) $(BIN_OBJ) ukb libukb.so libukb.a

51
include/ukb.h Normal file
View file

@ -0,0 +1,51 @@
#pragma once
#include <stdbool.h>
#include <stdlib.h>
typedef struct ukb_err {
const char* msg;
} ukb_err_t;
typedef void (*ukb_layout_cb_t)(const char *layout);
typedef struct ukb_backend ukb_backend_t;
#ifdef UKB_BACKENDS_INTERNAL
struct ukb_backend {
const char *name;
bool (*can_use)(void);
ukb_err_t (*listen)(ukb_layout_cb_t);
};
#endif
// Array of all registered backends.
extern const ukb_backend_t* ukb_backends[];
// Number of all registered backends.
extern size_t ukb_backends_number;
// Find first available keyboard backend.
// If no available backends were found, NULL is returned.
const ukb_backend_t* ukb_find_available(void);
// Find keyboard backend with the given name.
// Before using the backend, you must verify that it is currently available by calling its `can_use` function.
// If backend with the given name was not found, NULL is returned.
const ukb_backend_t* ukb_find(const char* name);
// Get backend name.
const char* ukb_backend_name(const ukb_backend_t*);
// Check whether this backend is available.
bool ukb_backend_can_use(const ukb_backend_t*);
// Listen for layout changes and report them to the given callback.
// Upon calling this function, current layout will be immediately reported to callback.
ukb_err_t ukb_backend_listen(const ukb_backend_t*, ukb_layout_cb_t);

191
src/backends/sway.c Normal file
View file

@ -0,0 +1,191 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <json-c/json.h>
#include "ukb.h"
#include "../utils.h"
static char current_layout[128] = {0};
/********************************* SWAY IPC **********************************/
static const char SWAY_MAGIC[] = {'i', '3', '-', 'i', 'p', 'c'};
static const char INPUT_SUB_MSG[] = "[\"input\"]";
#define SWAY_IPC_SUBSCRIBE 0x2
#define SWAY_IPC_INPUT 0x80000015
#define SWAY_IPC_GET_INPUTS 100
typedef union header {
uint8_t raw[14];
#pragma pack(push, 1)
struct fields {
uint8_t magic[6];
uint32_t length;
uint32_t type;
} fields;
#pragma pack(pop)
} header_t;
static int sway_ipc_open(const char *socket_path) {
int socketfd;
if ((socketfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
return -1;
}
struct sockaddr_un addr = {
.sun_family = AF_UNIX,
.sun_path = {0},
};
strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
if (connect(socketfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_un)) == -1) {
return -1;
}
return socketfd;
}
static bool sway_ipc_send(int fd, uint32_t type, uint32_t length, const char *data) {
header_t h;
memcpy(h.fields.magic, SWAY_MAGIC, 6);
h.fields.length = length;
h.fields.type = type;
if (write(fd, h.raw, sizeof(header_t)) == -1) return false;
if (write(fd, data, length) == -1) return false;
return true;
}
static bool sway_ipc_recv(int fd, header_t *h, char **data) {
uint32_t left = sizeof(header_t);
uint8_t *dst = h->raw;
ssize_t n;
while (left) {
if ((n = recv(fd, dst, left, 0)) == -1) {
return false;
}
dst += n;
left -= n;
}
*data = (char*)malloc(h->fields.length);
left = h->fields.length;
dst = (uint8_t*)*data;
while (left) {
if ((n = recv(fd, dst, left, 0)) == -1) {
free(*data);
return false;
}
dst += n;
left -= n;
}
return true;
}
static bool sway_update_layout_with_json(json_object *j) {
const char *new_layout = json_object_get_string(json_object_object_get(j, "xkb_active_layout_name"));
if (!new_layout) return false;
if (strncmp(current_layout, new_layout, sizeof(current_layout)) != 0) {
strncpy(current_layout, new_layout, sizeof(current_layout) - 1);
return true;
}
return false;
}
static bool sway_update_layout_with_event(json_object *j) {
const char *change = json_object_get_string(json_object_object_get(j, "change"));
if (strcmp(change, "xkb_layout") != 0) return false;
return sway_update_layout_with_json(json_object_object_get(j, "input"));
}
static ukb_err_t sway_init_layout(int fd) {
if (!sway_ipc_send(fd, SWAY_IPC_GET_INPUTS, 0, NULL)) {
UKB_ERR("layout init: ipc send error");
}
header_t h;
char *payload;
if (!sway_ipc_recv(fd, &h, &payload)) {
UKB_ERR("layout init: ipc recv error");
}
json_object *j = json_tokener_parse(payload);
int n = json_object_array_length(j);
for (int i = 0; i < n; i++) {
sway_update_layout_with_json(json_object_array_get_idx(j, i));
}
json_object_put(j);
free(payload);
UKB_OK();
}
/********************************* BACKEND ***********************************/
static bool sway_can_use(void) {
return getenv("SWAYSOCK") != NULL;
}
static ukb_err_t sway_listen(ukb_layout_cb_t cb) {
const char *swaysock = getenv("SWAYSOCK");
if (swaysock == NULL) {
UKB_ERR("listen: SWAYSOCK env is not defined");
}
int fd = sway_ipc_open(swaysock);
if (fd == -1) {
UKB_ERR("listen: ipc open error");
}
UKB_PROPAGATE(sway_init_layout(fd));
cb(current_layout);
sway_ipc_send(fd, SWAY_IPC_SUBSCRIBE, sizeof(INPUT_SUB_MSG), INPUT_SUB_MSG);
while (1) {
header_t h;
char *payload;
if (!sway_ipc_recv(fd, &h, &payload)) {
UKB_ERR("listen: ipc recv error");
}
if (h.fields.type == SWAY_IPC_INPUT) {
json_object *j = json_tokener_parse(payload);
if (sway_update_layout_with_event(j)) {
cb(current_layout);
}
json_object_put(j);
}
free(payload);
}
}
const ukb_backend_t ukb_backend_sway = {
.name = "sway",
.can_use = sway_can_use,
.listen = sway_listen,
};

86
src/main.c Normal file
View file

@ -0,0 +1,86 @@
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "ukb.h"
#define _maybe_fprintf(...) if (!args.silent) { fprintf(__VA_ARGS__); };
#define info(msg, ...) _maybe_fprintf(stderr, "\033[33m" msg "\033[0m\n", ##__VA_ARGS__);
#define fatal(msg, ...) _maybe_fprintf(stderr, "\033[31m" msg "\033[0m\n", ##__VA_ARGS__); exit(1);
static struct {
bool silent;
const char* backend;
} args;
static const char* usage =
"Usage: ukb [-h] [-s] [-b backend]\n"
"\n"
" -h show this help page\n"
" -s be silent and don't print anything to stderr\n"
" -b manually specify backend that should be used\n"
" it will be used even if it is reported as unavailable\n"
"";
void parse_args(int argc, char **argv) {
int opt;
args.silent = false;
args.backend = NULL;
while ((opt = getopt(argc, argv, "shb:")) != -1) {
switch (opt) {
case 's':
args.silent = true;
break;
case 'b':
args.backend = optarg;
break;
case '?':
fatal("Invalid option or missing argument");
break;
case 'h':
default:
fprintf(stderr, "%s", usage);
exit(1);
break;
}
}
}
void cb_print(const char* layout) {
printf("%s\n", layout);
fflush(stdout);
}
int main(int argc, char **argv) {
parse_args(argc, argv);
const ukb_backend_t *b;
if (args.backend) {
b = ukb_find(args.backend);
if (!b) {
fatal("ukb: backend was not found: %s", args.backend);
}
} else {
b = ukb_find_available();
if (!b) {
fatal("ukb: no available backend was found");
}
}
info("ukb: Using %s backend", ukb_backend_name(b));
ukb_err_t err = ukb_backend_listen(b, cb_print);
if (err.msg) {
fatal("ukb: %s", err.msg);
}
}

59
src/ukb.c Normal file
View file

@ -0,0 +1,59 @@
#include <ukb.h>
#include <stdlib.h>
#include <string.h>
#include "utils.h"
#define DECLARE(backend) extern const ukb_backend_t backend;
#define REFERENCE(backend) &backend,
/********************************* BACKENDS **********************************/
#define UKB_BACKENDS(M) \
M(ukb_backend_sway)
/*****************************************************************************/
UKB_BACKENDS(DECLARE)
const ukb_backend_t* ukb_backends[] = {
UKB_BACKENDS(REFERENCE)
};
size_t ukb_backends_number = ARRAY_SIZE(ukb_backends);
const ukb_backend_t* ukb_find_available(void) {
for (size_t i = 0; i < ukb_backends_number; i++) {
if (ukb_backends[i]->can_use()) {
return ukb_backends[i];
}
}
return NULL;
}
const ukb_backend_t* ukb_find(const char* name) {
for (size_t i = 0; i < ukb_backends_number; i++) {
if (strcmp(ukb_backends[i]->name, name) == 0) {
return ukb_backends[i];
}
}
return NULL;
}
const char* ukb_backend_name(const ukb_backend_t *backend) {
return backend->name;
}
bool ukb_backend_can_use(const ukb_backend_t *backend) {
return backend->can_use();
}
ukb_err_t ukb_backend_listen(const ukb_backend_t *backend, ukb_layout_cb_t cb) {
return backend->listen(cb);
}

11
src/utils.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
#define UKB_OK() return (ukb_err_t) { .msg = NULL };
#define UKB_ERR(m) return (ukb_err_t) { .msg = m };
#define UKB_PROPAGATE(expr) \
{ \
ukb_err_t err = expr; \
if (err.msg != NULL) return err; \
};