commit - f625c3f4b413f4308be2f654ce502e3e06a0727f
commit + 4fc826c2d403ff17d96ffca90f7fd96be3e7fc35
blob - 5d80e59e421d084f2d1500aafc346b271a312427
blob + 1572e329fa24a84dbeb3a808663fc79c745bb3ca
--- README.md
+++ README.md
| [speediness](speediness/) | network speed test |
| [tempfox](tempfox/) | Firefox user.js preference overrides |
| [wlr-river-title](wlr-river-title/) | River/Wayland focused view title printer |
+| [wlr-sway-title](wlr-sway-title/) | Sway/Wayland focused view title printer |
| [wrd](wrd/) | full-text search and reader for an offline web archive |
| [plants](plants/) | bluetooth battery monitoring for linux |
blob - /dev/null
blob + fa745fcd808bb68617018cc69c1f10d868c797ea (mode 644)
--- /dev/null
+++ wlr-sway-title/BUILD
+load("@rules_cc//cc:defs.bzl", "cc_binary")
+load("//bazel:local-deploy.bzl", "local_deploy")
+
+package(default_visibility = ["//visibility:public"])
+
+COMMON_COPTS = [
+ "-Wall",
+ "-Wextra",
+ "-O2",
+ "-fanalyzer",
+ "-Wshadow",
+]
+
+cc_binary(
+ name = "wlr-sway-title",
+ srcs = ["wlr-sway-title.c"],
+ copts = COMMON_COPTS,
+)
+
+local_deploy(
+ name = "deploy",
+ srcs = [
+ ":wlr-sway-title",
+ ],
+)
\ No newline at end of file
blob - /dev/null
blob + 7117821834ef5b0692672f7d3a8f38a1727325d9 (mode 644)
--- /dev/null
+++ wlr-sway-title/README.md
+# wlr-sway-title
+
+Prints the title of the currently focused view in a [sway](https://github.com/swaywm/sway)-compatible Wayland compositor (sway, swayfx, volare, …) via the i3 IPC protocol and exits. Useful for feeding into status bars or scripts like `monofetch`.
+
+## Building
+
+```sh
+make
+# or
+cc wlr-sway-title.c -o wlr-sway-title
+```
+
+## Usage
+
+```sh
+wlr-sway-title
+```
+
+Prints one line to stdout and exits. Reads the IPC socket from `SWAYSOCK`. Exits with status 1 if `SWAYSOCK` is unset, the socket cannot be connected to, or the IPC reply is malformed.
\ No newline at end of file
blob - /dev/null
blob + b6d9a4421664864d534397c417ecc54ed0ae8f1f (mode 644)
--- /dev/null
+++ wlr-sway-title/wlr-sway-title.c
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#define I3_IPC_MAGIC "i3-ipc"
+#define I3_IPC_MAGIC_LEN 6
+#define I3_IPC_MESSAGE_TYPE_GET_TREE 4
+
+static int send_request(int sock, uint32_t type, const char *payload) {
+ size_t payload_len = payload ? strlen(payload) : 0;
+ char header[I3_IPC_MAGIC_LEN + sizeof(uint32_t) * 2];
+ memcpy(header, I3_IPC_MAGIC, I3_IPC_MAGIC_LEN);
+ uint32_t len_field = (uint32_t)payload_len;
+ uint32_t type_field = type;
+ memcpy(header + I3_IPC_MAGIC_LEN, &len_field, sizeof(len_field));
+ memcpy(header + I3_IPC_MAGIC_LEN + sizeof(uint32_t), &type_field,
+ sizeof(type_field));
+
+ size_t total = sizeof(header) + payload_len;
+ char *buf = malloc(total);
+ if (!buf)
+ return -1;
+ memcpy(buf, header, sizeof(header));
+ if (payload_len)
+ memcpy(buf + sizeof(header), payload, payload_len);
+
+ size_t sent = 0;
+ while (sent < total) {
+ ssize_t n = send(sock, buf + sent, total - sent, 0);
+ if (n < 0) {
+ free(buf);
+ return -1;
+ }
+ sent += (size_t)n;
+ }
+ free(buf);
+ return 0;
+}
+
+static int recv_exact(int sock, char *buf, size_t need) {
+ size_t got = 0;
+ while (got < need) {
+ ssize_t n = recv(sock, buf + got, need - got, 0);
+ if (n < 0)
+ return -1;
+ if (n == 0)
+ return -1;
+ got += (size_t)n;
+ }
+ return 0;
+}
+
+/*
+ * Reads one full IPC message from the socket. The server keeps the connection
+ * open after replying, so we must parse the header to know the payload length
+ * rather than waiting for EOF. Returns a malloc'd buffer containing the whole
+ * message (magic + length + type + payload, NUL-terminated) and points
+ * *out_payload at the payload start.
+ */
+static char *recv_message(int sock, char **out_payload,
+ size_t *out_payload_len) {
+ char header[I3_IPC_MAGIC_LEN + sizeof(uint32_t) * 2];
+ if (recv_exact(sock, header, sizeof(header)) < 0)
+ return NULL;
+ if (memcmp(header, I3_IPC_MAGIC, I3_IPC_MAGIC_LEN) != 0)
+ return NULL;
+
+ uint32_t payload_len;
+ memcpy(&payload_len, header + I3_IPC_MAGIC_LEN, sizeof(uint32_t));
+
+ size_t total = sizeof(header) + payload_len;
+ char *buf = malloc(total + 1);
+ if (!buf)
+ return NULL;
+ memcpy(buf, header, sizeof(header));
+ if (payload_len > 0) {
+ if (recv_exact(sock, buf + sizeof(header), payload_len) < 0) {
+ free(buf);
+ return NULL;
+ }
+ }
+ buf[total] = '\0';
+ *out_payload = buf + sizeof(header);
+ if (out_payload_len)
+ *out_payload_len = payload_len;
+ return buf;
+}
+
+/*
+ * Minimal recursive JSON search: find the first object with a "focused":true
+ * member and return a malloc'd copy of its "name" string value.
+ *
+ * This is a tiny purpose-built parser; it is not a general JSON library.
+ * It walks the tree tracking object nesting and recognises the two keys
+ * we care about.
+ */
+struct search_ctx {
+ int focused_found;
+ char *name;
+};
+
+static void skip_ws(const char **p) {
+ while (**p && isspace((unsigned char)**p))
+ (*p)++;
+}
+
+static int match_key(const char **p, const char *key) {
+ skip_ws(p);
+ const char *start = *p;
+ if (**p != '"')
+ return 0;
+ (*p)++;
+ const char *k = key;
+ while (**p && *k) {
+ if (**p != *k) {
+ *p = start;
+ return 0;
+ }
+ (*p)++;
+ k++;
+ }
+ if (**p != '"') {
+ *p = start;
+ return 0;
+ }
+ (*p)++;
+ return 1;
+}
+
+static char *parse_string(const char **p) {
+ skip_ws(p);
+ if (**p != '"')
+ return NULL;
+ (*p)++;
+ size_t cap = 64;
+ size_t len = 0;
+ char *out = malloc(cap);
+ if (!out)
+ return NULL;
+ while (**p && **p != '"') {
+ char c = **p;
+ if (c == '\\') {
+ (*p)++;
+ c = **p;
+ switch (c) {
+ case 'n':
+ c = '\n';
+ break;
+ case 't':
+ c = '\t';
+ break;
+ case 'r':
+ c = '\r';
+ break;
+ case '"':
+ c = '"';
+ break;
+ case '\\':
+ c = '\\';
+ break;
+ case '/':
+ c = '/';
+ break;
+ case 'b':
+ c = '\b';
+ break;
+ case 'f':
+ c = '\f';
+ break;
+ case 'u': {
+ /* skip 4 hex digits, substitute with '?' for simplicity */
+ (*p)++;
+ for (int i = 0; i < 4 && isxdigit((unsigned char)**p); i++)
+ (*p)++;
+ c = '?';
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ if (len + 1 >= cap) {
+ cap *= 2;
+ char *nb = realloc(out, cap);
+ if (!nb) {
+ free(out);
+ return NULL;
+ }
+ out = nb;
+ }
+ out[len++] = c;
+ (*p)++;
+ }
+ if (**p == '"')
+ (*p)++;
+ out[len] = '\0';
+ return out;
+}
+
+static void walk_node(const char **p, struct search_ctx *ctx);
+
+static void skip_value(const char **p);
+
+static void skip_string(const char **p) {
+ if (**p != '"')
+ return;
+ (*p)++;
+ while (**p && **p != '"') {
+ if (**p == '\\') {
+ (*p)++;
+ if (!**p)
+ return;
+ }
+ (*p)++;
+ }
+ if (**p)
+ (*p)++;
+}
+
+static void skip_object(const char **p) {
+ if (**p != '{')
+ return;
+ (*p)++;
+ for (;;) {
+ skip_ws(p);
+ if (**p == '}') {
+ (*p)++;
+ return;
+ }
+ if (**p == ',') {
+ (*p)++;
+ continue;
+ }
+ skip_string(p);
+ skip_ws(p);
+ if (**p == ':')
+ (*p)++;
+ skip_value(p);
+ }
+}
+
+static void skip_array(const char **p) {
+ if (**p != '[')
+ return;
+ (*p)++;
+ for (;;) {
+ skip_ws(p);
+ if (**p == ']') {
+ (*p)++;
+ return;
+ }
+ if (**p == ',') {
+ (*p)++;
+ continue;
+ }
+ skip_value(p);
+ }
+}
+
+static void skip_value(const char **p) {
+ skip_ws(p);
+ if (**p == '"') {
+ skip_string(p);
+ return;
+ }
+ if (**p == '{') {
+ skip_object(p);
+ return;
+ }
+ if (**p == '[') {
+ skip_array(p);
+ return;
+ }
+ while (**p && !isspace((unsigned char)**p) && **p != ',' && **p != '}' &&
+ **p != ']')
+ (*p)++;
+}
+
+static void walk_node(const char **p, struct search_ctx *ctx) {
+ skip_ws(p);
+ if (**p != '{') {
+ skip_value(p);
+ return;
+ }
+ (*p)++;
+
+ int this_focused = 0;
+ char *this_name = NULL;
+
+ for (;;) {
+ skip_ws(p);
+ if (**p == '}') {
+ (*p)++;
+ break;
+ }
+ if (**p == ',') {
+ (*p)++;
+ continue;
+ }
+
+ if (match_key(p, "focused")) {
+ skip_ws(p);
+ if (**p == ':')
+ (*p)++;
+ skip_ws(p);
+ if (strncmp(*p, "true", 4) == 0) {
+ this_focused = 1;
+ *p += 4;
+ } else if (strncmp(*p, "false", 5) == 0) {
+ *p += 5;
+ } else {
+ skip_value(p);
+ }
+ continue;
+ }
+
+ if (match_key(p, "name")) {
+ skip_ws(p);
+ if (**p == ':')
+ (*p)++;
+ char *s = parse_string(p);
+ if (s) {
+ free(this_name);
+ this_name = s;
+ }
+ continue;
+ }
+
+ if (match_key(p, "nodes") || match_key(p, "floating_nodes")) {
+ skip_ws(p);
+ if (**p == ':')
+ (*p)++;
+ skip_ws(p);
+ if (**p != '[') {
+ skip_value(p);
+ continue;
+ }
+ (*p)++;
+ for (;;) {
+ skip_ws(p);
+ if (**p == ']') {
+ (*p)++;
+ break;
+ }
+ if (**p == ',') {
+ (*p)++;
+ continue;
+ }
+ walk_node(p, ctx);
+ if (ctx->name) {
+ free(this_name);
+ this_name = NULL;
+ this_focused = 0;
+ goto done;
+ }
+ }
+ continue;
+ }
+
+ skip_ws(p);
+ if (**p == '"') {
+ skip_string(p);
+ skip_ws(p);
+ if (**p == ':')
+ (*p)++;
+ skip_value(p);
+ } else {
+ skip_value(p);
+ }
+ }
+
+done:
+ if (this_focused) {
+ ctx->focused_found = 1;
+ if (!ctx->name && this_name) {
+ ctx->name = this_name;
+ this_name = NULL;
+ }
+ }
+ free(this_name);
+}
+
+static char *find_focused_name(char *json) {
+ const char *p = json;
+ struct search_ctx ctx = {0};
+ walk_node(&p, &ctx);
+ if (ctx.focused_found && ctx.name)
+ return ctx.name;
+ free(ctx.name);
+ return NULL;
+}
+
+int main(void) {
+ const char *sock_path = getenv("SWAYSOCK");
+ if (!sock_path || !*sock_path) {
+ fprintf(stderr, "SWAYSOCK is not set\n");
+ return 1;
+ }
+
+ int sock = socket(AF_UNIX, SOCK_STREAM, 0);
+ if (sock < 0) {
+ perror("socket");
+ return 1;
+ }
+
+ struct sockaddr_un addr;
+ memset(&addr, 0, sizeof(addr));
+ addr.sun_family = AF_UNIX;
+ strncpy(addr.sun_path, sock_path, sizeof(addr.sun_path) - 1);
+
+ if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ perror("connect");
+ close(sock);
+ return 1;
+ }
+
+ if (send_request(sock, I3_IPC_MESSAGE_TYPE_GET_TREE, NULL) < 0) {
+ fprintf(stderr, "failed to send IPC request\n");
+ close(sock);
+ return 1;
+ }
+
+ char *payload = NULL;
+ size_t payload_len = 0;
+ char *data = recv_message(sock, &payload, &payload_len);
+ close(sock);
+ if (!data) {
+ fprintf(stderr, "failed to read IPC reply\n");
+ return 1;
+ }
+
+ char *title = find_focused_name(payload);
+ if (title) {
+ printf("%s\n", title);
+ free(title);
+ }
+
+ free(data);
+ return 0;
+}
\ No newline at end of file