Initial commit
This commit is contained in:
commit
9b34bfc435
7 changed files with 456 additions and 0 deletions
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
*.o
|
||||||
|
*.so
|
||||||
|
*.a
|
||||||
|
ukb
|
||||||
|
.cache/
|
||||||
|
compile_commands.json
|
52
Makefile
Normal file
52
Makefile
Normal 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
51
include/ukb.h
Normal 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
191
src/backends/sway.c
Normal 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
86
src/main.c
Normal 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
59
src/ukb.c
Normal 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
11
src/utils.h
Normal 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; \
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue