#!/usr/bin/env python3 import json import os import subprocess import sys import threading import time from concurrent.futures import ThreadPoolExecutor from typing import Literal import requests def die(r: str): print(r, file=sys.stderr) exit(1) with open(f"{os.environ['HOME']}/.secrets/openrouter.nike.secret") as f: KEY=f.readline().strip() if not KEY: die("Missing key!") HEADERS = {'Authorization': f'Bearer {KEY}'} with subprocess.Popen(["git", "diff", "--staged"], stdout=subprocess.PIPE) as ps: ps.wait() DIFF = ps.stdout and ps.stdout.read().decode('utf-8').strip() if not DIFF or DIFF == "": die("Nothing to commit!") URL = 'http://higpu:11434/api/chat' MODEL = 'gemma3:4b' SAMPLES = [ { "role": "user", "content": "\ndiff --git a/meson.build b/meson.build\nindex 5ee43f7..bae848d 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -1,5 +1,5 @@\n project('ukb', 'c',\n- version: '0.1.0',\n+ version: '0.2.1',\n default_options: ['warning_level=2', 'optimization=2'],\n )" }, { "role": "assistant", "content": "chore: bump version to `0.2.1`" }, { "role": "user", "content": "\ndiff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml\nindex fa4f919..9fadc0d 100644\n--- a/.github/workflows/workflow.yml\n+++ b/.github/workflows/workflow.yml\n@@ -19,7 +19,7 @@ jobs:\n libjson-c-dev libx11-dev \\\n zip\n \n- - name: Build (Linux)\n+ - name: Build (`linux-x86_64`)\n run: |\n meson setup build.linux \\\n --prefix=/ \\\n@@ -29,7 +29,7 @@ jobs:\n meson compile -C build.linux\n meson install -C build.linux --destdir=install\n \n- - name: Build (Windows)\n+ - name: Build (`windows-x86_64`)\n run: |\n cat > mingw-cross.txt < mingw-cross.txt <\n+#include \n+#include \n+\n+#include \"ukb.h\"\n+#include \"../utils.h\"\n+\n+\n+#define POLL_DELAY 1\n+\n+\n+static HKL prev_layout = NULL;\n+static char current_layout[128] = {0};\n+\n+\n+static bool hkl_to_name(HKL hkl, char* dst, size_t n) {\n+ LANGID langid = LOWORD((DWORD_PTR)hkl);\n+ LCID lcid = MAKELCID(langid, SORT_DEFAULT);\n+\n+ char name[LOCALE_NAME_MAX_LENGTH];\n+\n+ if (GetLocaleInfoA(lcid, LOCALE_SENGLANGUAGE, name, LOCALE_NAME_MAX_LENGTH)) {\n+ strncpy(dst, name, n);\n+ return true;\n+ } else {\n+ return false;\n+ }\n+}\n+\n+static HKL poll_layout_switch() {\n+ while(1) {\n+ HWND hwnd = GetForegroundWindow();\n+ DWORD thread_id = GetWindowThreadProcessId(hwnd, NULL);\n+ HKL new_layout = GetKeyboardLayout(thread_id);\n+\n+ if (prev_layout != new_layout) {\n+ prev_layout = new_layout;\n+ return new_layout;\n+ }\n+\n+ Sleep(POLL_DELAY);\n+ }\n+}\n+\n+/********************************* BACKEND ***********************************/\n+\n+static bool windows_can_use(void) {\n+ return true;\n+}\n+\n+static ukb_err_t windows_listen(ukb_layout_cb_t cb) {\n+ while (1) {\n+ HKL layout = poll_layout_switch();\n+ if (!hkl_to_name(layout, current_layout, sizeof(current_layout) - 1)) {\n+ UKB_ERR(\"Unknown layout.\");\n+ }\n+ cb(current_layout);\n+ }\n+\n+ UKB_OK();\n+}\n+\n+const ukb_backend_t ukb_backend_windows = {\n+ .name = \"windows\",\n+ .can_use = windows_can_use,\n+ .listen = windows_listen,\n+};\ndiff --git a/src/ukb.c b/src/ukb.c\nindex ec55ec8..74ed7df 100644\n--- a/src/ukb.c\n+++ b/src/ukb.c\n@@ -11,9 +11,16 @@\n \n /********************************* BACKENDS **********************************/\n \n+#if defined PLATFORM_LINUX\n #define UKB_BACKENDS(M) \\\n M(ukb_backend_sway) \\\n M(ukb_backend_xorg)\n+#elif defined PLATFORM_WINDOWS\n+#define UKB_BACKENDS(M) \\\n+ M(ukb_backend_windows)\n+#else\n+#error \"No platform is set.\"\n+#endif\n \n /*****************************************************************************/" }, { "role": "assistant", "content": "feat(windows): add new backend" }, { "role": "user", "content": "\ndiff --git a/Makefile b/Makefile\ndeleted file mode 100644\nindex 9a8818d..0000000\n--- a/Makefile\n+++ /dev/null\n@@ -1,52 +0,0 @@\n-CC = gcc\n-AR = ar\n-\n-DEPS = json-c x11\n-\n-CFLAGS = -Iinclude -Wall -O2 $(shell pkg-config --cflags $(DEPS))\n-LDFLAGS = $(shell pkg-config --libs $(DEPS))\n-\n-LIB_SRC = src/ukb.c $(wildcard src/backends/*.c)\n-LIB_OBJ = $(LIB_SRC:.c=.o)\n-\n-BIN_SRC = src/main.c\n-BIN_OBJ = $(BIN_SRC:.c=.o)\n-\n-PREFIX ?= /usr/local\n-BIN_DIR = $(PREFIX)/bin\n-LIB_DIR = $(PREFIX)/lib\n-INCLUDE_DIR = $(PREFIX)/include/libukb\n-\n-all: libukb.a libukb.so ukb\n-\n-$(LIB_OBJ): %.o: %.c\n-\t$(CC) $(CFLAGS) -fPIC -DUKB_BACKENDS_INTERNAL -c $< -o $@\n-\n-$(BIN_OBJ): %.o: %.c\n-\t$(CC) $(CFLAGS) -c $< -o $@\n-\n-libukb.so: $(LIB_OBJ)\n-\t$(CC) -shared -o $@ $^ $(LDFLAGS)\n-\n-libukb.a: $(LIB_OBJ)\n-\t$(AR) rcs $@ $^\n-\n-ukb: $(BIN_OBJ) libukb.a\n-\t$(CC) $(BIN_OBJ) -L. -l:libukb.a -o $@ $(LDFLAGS)\n-\n-.PHONY: install\n-install: all\n-\tinstall -d $(BIN_DIR) $(LIB_DIR) $(INCLUDE_DIR)\n-\tinstall -m 0755 ukb $(BIN_DIR)/\n-\tinstall -m 0644 libukb.a libukb.so $(LIB_DIR)/\n-\tinstall -m 0644 include/*.h $(INCLUDE_DIR)/\n-\n-.PHONY: uninstall\n-uninstall:\n-\trm -f $(BIN_DIR)/ukb\n-\trm -f $(LIB_DIR)/libukb.a $(LIB_DIR)/libukb.so\n-\trm -rf $(INCLUDE_DIR)\n-\n-.PHONY: clean\n-clean:\n-\trm -f $(LIB_OBJ) $(BIN_OBJ) ukb libukb.so libukb.a\ndiff --git a/meson.build b/meson.build\nnew file mode 100644\nindex 0000000..229d3fd\n--- /dev/null\n+++ b/meson.build\n@@ -0,0 +1,44 @@\n+project('ukb', 'c',\n+ version: '0.1.0',\n+ default_options: ['warning_level=2', 'optimization=2'],\n+)\n+\n+\n+inc = include_directories('include')\n+lib_src = files('src/ukb.c')\n+deps = []\n+\n+platform = host_machine.system()\n+\n+if platform == 'linux'\n+ add_project_arguments('-DPLATFORM=LINUX', language: ['c'])\n+ lib_src += files(\n+ 'src/backends/sway.c',\n+ 'src/backends/xorg.c',\n+ )\n+ deps += [\n+ dependency('json-c', required: true),\n+ dependency('x11', required: true),\n+ ]\n+else\n+ error('unknown platform: ' + platform)\n+endif\n+\n+\n+install_headers('include/ukb.h', subdir: 'ukb')\n+\n+libukb_kwargs = {\n+ 'include_directories': inc,\n+ 'dependencies': deps,\n+ 'c_args': ['-DUKB_BACKENDS_INTERNAL'],\n+ 'install': true,\n+}\n+libukb_a = static_library('ukb', lib_src, kwargs: libukb_kwargs)\n+libukb_so = shared_library('ukb', lib_src, kwargs: libukb_kwargs)\n+\n+\n+executable('ukb', 'src/main.c',\n+ include_directories: inc,\n+ link_with: libukb_a,\n+ install: true,\n+)" }, { "role": "assistant", "content": "feat: transition to meson build system" }, { "role": "user", "content": "\ndiff --git a/src/backends/sway.c b/src/backends/sway.c\nindex a7dfd9c..229b8bb 100644\n--- a/src/backends/sway.c\n+++ b/src/backends/sway.c\n@@ -80,7 +80,7 @@ static bool sway_ipc_recv(int fd, header_t *h, char **data) {\n left -= n;\n }\n \n- *data = (char*)malloc(h->fields.length);\n+ *data = (char*)calloc(1, h->fields.length + 1);\n left = h->fields.length;\n dst = (uint8_t*)*data;" }, { "role": "assistant", "content": "fix(sway): buffer overflow" }, { "role": "user", "content": "\ndiff --git a/src/backends/xorg.c b/src/backends/xorg.c\nindex 00f6ae8..d74c696 100644\n--- a/src/backends/xorg.c\n+++ b/src/backends/xorg.c\n@@ -89,6 +89,27 @@ static int xorg_open_display() {\n /********************************* BACKEND ***********************************/\n \n static bool xorg_can_use(void) {\n+ // xkblib is pretty much useless when running under xwayland,\n+ // so we'll try to detect xwayland with some simple heuristics.\n+\n+ const char* session = getenv(\"XDG_SESSION_TYPE\");\n+ if (session) {\n+ if (strcmp(session, \"wayland\") == 0) {\n+ return false;\n+ }\n+\n+ if (strcmp(session, \"x11\") != 0) {\n+ // require an additional check if session is neither wayland nor x11\n+ session = NULL;\n+ }\n+ }\n+\n+ if (!session) {\n+ if (getenv(\"WAYLAND_DISPLAY\") != NULL) {\n+ return false;\n+ }\n+ }\n+\n int reason_return = xorg_open_display();\n XCloseDisplay(x_display);\n return reason_return == XkbOD_Success;" }, { "role": "assistant", "content": "feat(xorg): detect xwayland" }, { "role": "user", "content": "\ndiff --git a/src/backends/xorg.c b/src/backends/xorg.c\nindex 86820df..00f6ae8 100644\n--- a/src/backends/xorg.c\n+++ b/src/backends/xorg.c\n@@ -59,7 +59,7 @@ static ukb_err_t xorg_wait_event() {\n \n XEvent event;\n int iret = XNextEvent(x_display, &event);\n- if (!iret) {\n+ if (iret) {\n UKB_ERR(\"XNextEvent failed\");\n }" }, { "role": "assistant", "content": "fix: erroneous error on XNextEvent (#2)" }, { "role": "user", "content": "\ndiff --git a/src/backends/xorg.c b/src/backends/xorg.c\nindex dfb5459..86820df 100644\n--- a/src/backends/xorg.c\n+++ b/src/backends/xorg.c\n@@ -54,7 +54,7 @@ static ukb_err_t update_current_layout() {\n static ukb_err_t xorg_wait_event() {\n Bool bret = XkbSelectEventDetails(x_display, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask);\n if (!bret) {\n- UKB_ERR(\"XkbSelectEventDetails failed\")\n+ UKB_ERR(\"XkbSelectEventDetails failed\");\n }\n \n XEvent event;\ndiff --git a/src/utils.h b/src/utils.h\nindex fee5425..f15f4d4 100644\n--- a/src/utils.h\n+++ b/src/utils.h\n@@ -2,10 +2,10 @@\n \n #define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))\n \n-#define UKB_OK() return (ukb_err_t) { .msg = NULL };\n-#define UKB_ERR(m) return (ukb_err_t) { .msg = m };\n+#define UKB_OK() return (ukb_err_t) { .msg = NULL }\n+#define UKB_ERR(m) return (ukb_err_t) { .msg = m }\n #define UKB_PROPAGATE(expr) \\\n- { \\\n+ do { \\\n ukb_err_t err = expr; \\\n if (err.msg != NULL) return err; \\\n- };\n+ } while (0)" }, { "role": "assistant", "content": "fix: require semicolons for util macros" }, { "role": "user", "content": "\ndiff --git a/Makefile b/Makefile\nindex 702edec..9a8818d 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1,7 +1,7 @@\n CC = gcc\n AR = ar\n \n-DEPS = json-c\n+DEPS = json-c x11\n \n CFLAGS = -Iinclude -Wall -O2 $(shell pkg-config --cflags $(DEPS))\n LDFLAGS = $(shell pkg-config --libs $(DEPS))\ndiff --git a/src/backends/xorg.c b/src/backends/xorg.c\nindex aebd709..dfb5459 100644\n--- a/src/backends/xorg.c\n+++ b/src/backends/xorg.c\n@@ -32,7 +32,7 @@ static ukb_err_t update_current_layout() {\n }\n \n int num_groups = desc_ptr->ctrls->num_groups;\n- if (xkbState.group >= num_groups) {\n+ if (xkb_state.group >= num_groups) {\n UKB_ERR(\"Group index out of range.\");\n }" }, { "role": "assistant", "content": "fix: x11 linking and incorrect variable name" } ] SAMPLES = [] PROMPT="""You are an assistant that helps user to write semantic commit messages. You will be presented with git diff of the code. Write a single short, concise and meaningful message to describe changes that were made. You can only write a single commit message for the entire diff. The single message that you write has to include ALL the changes, that exist in the diff. You can't skip anything. Git commit messages are LIMITED TO 72 CHARACTERS. YOUR MESSAGE COULD NOT BE LONGER THAN 72 CHARACTERS. Semantic commit messages are a structured way of writing commit messages to clearly describe the purpose and scope of changes in a project. They follow a standardized format, using a prefix that conveys the intent of the change. The general structure is: ``` : ``` - **Type**: Specifies the kind of change (e.g., `feat` for a new feature, `fix` for a bug fix, `docs` for documentation updates, `ci` for updates to the CI/CD pipeline, `chore` for something less usefull). - **Short Description**: A concise summary of the change. For example: - feat: add OAuth2 support - fix: resolve dropdown alignment issue - docs: update README.md - ci: fix `test/windows` pipeline The `short description` in the commit message should concisely describe the changes that this commit contains. The `short description` should not be a filename. Do not use the same `short description` for every patch in a whole patch series (where a patch series is an ordered sequence of multiple, related patches). Bear in mind that the `short description` of your commit becomes a globally-unique identifier for that patch. It propagates all the way into the `git changelog`. The `short description` may later be used in developer discussions which refer to the patch. People will want to google for the `short description` to read discussion regarding that patch. It will also be the only thing that people may quickly see when, two or three months later, they are going through perhaps thousands of patches using tools such as `gitk` or `git log --oneline`. Use the "`" (backtick) symbol when referring to certain parts of code in your commit message.""" REQUEST = { "model": MODEL, "messages": [ { "role": "system", "content": PROMPT }, *SAMPLES, { "role": "user", "content": DIFF }, ], "stream": False, "options": { "temperature": 1, "num_ctx": 117000, } } N: int = 1 argv = iter(sys.argv[1:]) for arg in argv: if arg == "-n": N = int(next(argv)) def strip_equal(msg: str, substring: str, n_max: int) -> str: if n_max <= 0 or msg[0] != substring or msg[-1] != substring: return msg return strip_equal(msg[1:-1], substring, n_max-1) def cleanup_string(s: str) -> str: return strip_equal(s.replace('\n','').strip(), '`', 4) class Spinner: _thread: threading.Thread | None = None _state: Literal['loading', 'done', 'fail'] total: int = N current = 0 @classmethod def start(cls, text: str): if cls._thread: return cls._state = 'loading' cls.text = text cls._thread = threading.Thread(target = cls._spin) cls._thread.start() @classmethod def stop(cls, state: bool = True): if not cls._thread: return cls._state = 'done' if state else 'fail' cls._thread.join() @classmethod def _spin(cls): interval = 80 spinner = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] i = 0 while cls._state == "loading": i = (i + 1) % len(spinner) print(f"\r{spinner[i]} {cls.text} ({cls.current}/{cls.total})", end=' ') time.sleep(interval / 1000) symbol = "✓" if cls._state == "done" else "⨯" print(f"\r{symbol} {cls.text} ") def main(): Spinner.start('Generating...') choices = [] futs = [] with ThreadPoolExecutor(max_workers=1) as ex: for _ in range(N): futs.append(ex.submit(requests.post, URL, headers=HEADERS, json=REQUEST)) for fut in futs: r: requests.Response = fut.result() Spinner.current += 1 if r.status_code != 200: Spinner.stop(False) die(f"status code: {r.status_code}\n{r.text}") choices.append(r.json()) Spinner.stop() choices = '# ' + '\n# '.join(map(lambda x: cleanup_string(x['message']['content']), choices)) fd = os.memfd_create("file.txt") with os.fdopen(fd, mode="w+") as fd_wrapper: fd_wrapper.write(choices) fd_wrapper.flush() os.system(f"git commit -v -e -F- < /proc/{os.getpid()}/fd/{fd}") try: main() finally: Spinner.stop(False)