Files
peachwm/include/ipc_socket.h
T

1236 lines
29 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* See LICENSE file for copyright and license details.
* PeachWM IPC Socket - swaymsg-compatible Unix domain socket IPC
*
* This is included directly in peachwm.c (similar to ipc.h / client.h).
* It provides a swaymsg-compatible JSON-based IPC over a Unix domain socket,
* allowing external tools like waybar to query peachwm state.
*
* Protocol (sway/i3-compatible):
* Header: "i3-ipc" magic (6 bytes)
* uint32_t payload_length (little-endian)
* uint32_t message_type (little-endian)
* Payload: JSON string (UTF-8)
*
* Message types:
* 0 = COMMAND 1 = GET_WORKSPACES 2 = SUBSCRIBE
* 3 = GET_OUTPUTS 4 = GET_TREE 5 = GET_VERSION
*
* Events (type has 0x80000000 bit set):
* 0x80000000 = workspace 0x80000001 = output
* 0x80000002 = mode 0x80000003 = window
*/
#include <arpa/inet.h>
#include <assert.h>
#include <fcntl.h>
#include <pwd.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <unistd.h>
/* ------------------------------------------------------------------ */
/* Protocol constants */
/* ------------------------------------------------------------------ */
#define IPC_MAGIC "i3-ipc"
#define IPC_MAGIC_LEN 6
#define IPC_HEADER_SIZE 14 /* magic(6) + len(4) + type(4) */
enum ipc_message_type {
IPC_COMMAND = 0,
IPC_GET_WORKSPACES = 1,
IPC_SUBSCRIBE = 2,
IPC_GET_OUTPUTS = 3,
IPC_GET_TREE = 4,
IPC_GET_VERSION = 5,
};
/* Event types use the high bit (0x80000000) which exceeds INT_MAX.
* Use uint32_t constants instead of enum members to stay C11-clean. */
#define IPC_EVENT_WORKSPACE ((uint32_t)0x80000000u)
#define IPC_EVENT_OUTPUT ((uint32_t)0x80000001u)
#define IPC_EVENT_MODE ((uint32_t)0x80000002u)
#define IPC_EVENT_WINDOW ((uint32_t)0x80000003u)
enum ipc_sub_flag {
IPC_SUB_WORKSPACE = 1 << 0,
IPC_SUB_WINDOW = 1 << 1,
IPC_SUB_OUTPUT = 1 << 2,
IPC_SUB_MODE = 1 << 3,
};
/* ------------------------------------------------------------------ */
/* Per-client state */
/* ------------------------------------------------------------------ */
struct ipc_client {
struct wl_list link;
int fd;
uint32_t subscribed;
struct wl_event_source *src;
/* partial-read state */
uint8_t hdr_buf[IPC_HEADER_SIZE];
size_t hdr_len;
char *payload;
size_t payload_size; /* total expected payload length */
size_t payload_len; /* amount read so far */
size_t payload_alloc;
uint32_t msg_type;
int header_done;
};
/* ------------------------------------------------------------------ */
/* Globals */
/* ------------------------------------------------------------------ */
static int ipc_listen_fd = -1;
static struct wl_event_source *ipc_listen_src;
static struct wl_list ipc_clients;
static struct wl_event_loop *ipc_event_loop;
static char ipc_socket_path[256];
/* ------------------------------------------------------------------ */
/* Simple JSON builder */
/* ------------------------------------------------------------------ */
struct json_writer {
char *buf;
size_t len;
size_t cap;
int depth;
int need_comma[32];
};
static void
json_init(struct json_writer *w)
{
w->cap = 4096;
w->buf = ecalloc(1, w->cap);
w->len = 0;
w->depth = -1;
memset(w->need_comma, 0, sizeof(w->need_comma));
}
static void
json_finish(struct json_writer *w)
{
free(w->buf);
}
static void
json_grow(struct json_writer *w, size_t needed)
{
if (w->len + needed <= w->cap)
return;
while (w->cap < w->len + needed)
w->cap *= 2;
w->buf = realloc(w->buf, w->cap);
if (!w->buf)
die("json_grow: realloc:");
}
static void
json_raw(struct json_writer *w, const char *s, size_t n)
{
json_grow(w, n);
memcpy(w->buf + w->len, s, n);
w->len += n;
}
static void
json_puts(struct json_writer *w, const char *s)
{
json_raw(w, s, strlen(s));
}
__attribute__((__format__(__printf__, 2, 3)))
static void
json_printf(struct json_writer *w, const char *fmt, ...)
{
va_list ap;
int n;
json_grow(w, 128);
va_start(ap, fmt);
n = vsnprintf(w->buf + w->len, w->cap - w->len, fmt, ap);
va_end(ap);
if ((size_t)n >= w->cap - w->len) {
json_grow(w, (size_t)n + 1);
va_start(ap, fmt);
n = vsnprintf(w->buf + w->len, w->cap - w->len, fmt, ap);
va_end(ap);
}
w->len += (size_t)n;
}
static void
json_comma(struct json_writer *w)
{
if (w->need_comma[w->depth + 1])
json_puts(w, ",");
w->need_comma[w->depth + 1] = 1;
}
static void
json_object_start(struct json_writer *w)
{
json_comma(w);
json_puts(w, "{");
++w->depth;
w->need_comma[w->depth + 1] = 0;
}
static void
json_object_end(struct json_writer *w)
{
json_puts(w, "}");
--w->depth;
}
static void
json_array_start(struct json_writer *w)
{
json_comma(w);
json_puts(w, "[");
++w->depth;
w->need_comma[w->depth + 1] = 0;
}
static void
json_array_end(struct json_writer *w)
{
json_puts(w, "]");
--w->depth;
}
static void
json_key(struct json_writer *w, const char *key)
{
json_comma(w);
json_printf(w, "\"%s\":", key);
}
static void
json_string(struct json_writer *w, const char *s)
{
json_comma(w);
json_puts(w, "\"");
for (; *s; s++) {
switch (*s) {
case '"': json_puts(w, "\\\""); break;
case '\\': json_puts(w, "\\\\"); break;
case '\n': json_puts(w, "\\n"); break;
case '\r': json_puts(w, "\\r"); break;
case '\t': json_puts(w, "\\t"); break;
default:
if ((unsigned char)*s < 0x20)
json_printf(w, "\\u%04x",
(unsigned char)*s);
else
json_raw(w, s, 1);
break;
}
}
json_puts(w, "\"");
}
static void
json_integer(struct json_writer *w, int64_t val)
{
json_comma(w);
json_printf(w, "%" PRId64, val);
}
static void
json_bool(struct json_writer *w, int val)
{
json_comma(w);
json_puts(w, val ? "true" : "false");
}
static void
json_float(struct json_writer *w, double val)
{
json_comma(w);
json_printf(w, "%.2f", val);
}
#define json_key_string(w, k, v) do { \
json_key(w, k); json_string(w, v); } while(0)
#define json_key_int(w, k, v) do { \
json_key(w, k); json_integer(w, v); } while(0)
#define json_key_bool(w, k, v) do { \
json_key(w, k); json_bool(w, v); } while(0)
#define json_key_float(w, k, v) do { \
json_key(w, k); json_float(w, v); } while(0)
/* ------------------------------------------------------------------ */
/* Helper: tag index (0-based) -> workspace name */
/* ------------------------------------------------------------------ */
static const char *
ipc_tag_name(int idx)
{
static const char *names[] = {
"1", "2", "3", "4", "5", "6", "7", "8", "9",
};
if (idx >= 0 && idx < (int)LENGTH(names))
return names[idx];
return "?";
}
/* ------------------------------------------------------------------ */
/* Send a raw message (header + payload) to a client fd */
/* ------------------------------------------------------------------ */
static int
ipc_send_raw(int fd, uint32_t type, const char *payload, size_t len)
{
uint8_t hdr[IPC_HEADER_SIZE];
uint32_t nlen = (uint32_t)len;
memcpy(hdr, IPC_MAGIC, IPC_MAGIC_LEN);
memcpy(hdr + IPC_MAGIC_LEN, &nlen, sizeof(nlen));
memcpy(hdr + IPC_MAGIC_LEN + 4, &type, sizeof(type));
struct iovec iov[2];
iov[0].iov_base = hdr;
iov[0].iov_len = IPC_HEADER_SIZE;
iov[1].iov_base = (void *)payload;
iov[1].iov_len = len;
struct msghdr msg = {0};
msg.msg_iov = iov;
msg.msg_iovlen = 2;
ssize_t ret;
do {
ret = sendmsg(fd, &msg, MSG_NOSIGNAL);
} while (ret < 0 && errno == EINTR);
if (ret < 0 && errno != EAGAIN && errno != EWOULDBLOCK)
return -1;
return 0;
}
static int
ipc_send_json(int fd, uint32_t type, struct json_writer *w)
{
return ipc_send_raw(fd, type, w->buf, w->len);
}
/* ------------------------------------------------------------------ */
/* Broadcast an event to all subscribed clients */
/* ------------------------------------------------------------------ */
static void
ipc_broadcast(uint32_t event_type, struct json_writer *w)
{
struct ipc_client *c;
uint32_t flag;
switch (event_type) {
case IPC_EVENT_WORKSPACE: flag = IPC_SUB_WORKSPACE; break;
case IPC_EVENT_OUTPUT: flag = IPC_SUB_OUTPUT; break;
case IPC_EVENT_WINDOW: flag = IPC_SUB_WINDOW; break;
case IPC_EVENT_MODE: flag = IPC_SUB_MODE; break;
default: flag = ~0u; break;
}
wl_list_for_each(c, &ipc_clients, link) {
if (c->subscribed & flag)
ipc_send_json(c->fd, event_type, w);
}
}
/* ------------------------------------------------------------------ */
/* JSON response builders */
/* ------------------------------------------------------------------ */
static void
ipc_build_workspace(struct json_writer *w, Monitor *m, uint32_t tag_bit,
int tag_idx)
{
Client *c;
int is_urgent = 0;
int visible = !!(m->tagset[m->seltags] & tag_bit);
int num = 0;
/* compute tag number (1-9) */
{
uint32_t t = tag_bit;
while (t) { num++; t >>= 1; }
}
wl_list_for_each(c, &clients, link) {
if (c->mon != m)
continue;
if (c->tags & tag_bit) {
if (c->isurgent)
is_urgent = 1;
}
}
json_object_start(w);
json_key_int(w, "id", (int64_t)tag_bit +
((int64_t)(uintptr_t)m << 32));
json_key_int(w, "num", num);
json_key_string(w, "name", ipc_tag_name(tag_idx));
json_key_bool(w, "visible", visible);
json_key_bool(w, "focused", visible && m == selmon);
json_key_bool(w, "urgent", !!is_urgent);
json_key(w, "rect");
json_object_start(w);
json_key_int(w, "x", m->m.x);
json_key_int(w, "y", m->m.y);
json_key_int(w, "width", m->m.width);
json_key_int(w, "height", m->m.height);
json_object_end(w);
json_key_string(w, "output", m->wlr_output->name);
json_key_string(w, "layout", m->ltsymbol);
json_key_string(w, "representation", "");
json_key_string(w, "type", "workspace");
json_key(w, "focus");
json_array_start(w);
c = focustop(m);
if (c && (c->tags & tag_bit))
json_integer(w, (int64_t)(uintptr_t)c);
json_array_end(w);
json_key(w, "floating_nodes");
json_array_start(w);
wl_list_for_each(c, &clients, link) {
if (c->mon == m && (c->tags & tag_bit) && c->isfloating &&
!c->isfullscreen)
json_integer(w, (int64_t)(uintptr_t)c);
}
json_array_end(w);
json_key(w, "nodes");
json_array_start(w);
wl_list_for_each(c, &clients, link) {
if (c->mon == m && (c->tags & tag_bit) && !c->isfloating &&
!c->isfullscreen)
json_integer(w, (int64_t)(uintptr_t)c);
}
json_array_end(w);
json_object_end(w);
}
static void
ipc_build_output(struct json_writer *w, Monitor *m)
{
struct wlr_output *o = m->wlr_output;
struct wlr_output_mode *mode = NULL;
int ti;
if (o->current_mode)
mode = o->current_mode;
else
mode = wlr_output_preferred_mode(o);
/* Determine current workspace name = first visible tag name */
const char *ws_name = "";
for (ti = 0; ti < TAGCOUNT; ti++) {
if (m->tagset[m->seltags] & (1u << ti)) {
ws_name = ipc_tag_name(ti);
break;
}
}
json_object_start(w);
json_key_string(w, "name", o->name ? o->name : "");
json_key_string(w, "make", o->make ? o->make : "");
json_key_string(w, "model", o->model ? o->model : "");
json_key_string(w, "serial", o->serial ? o->serial : "");
json_key_bool(w, "active", o->enabled);
json_key_bool(w, "primary",
m == wl_container_of(mons.next, m, link));
json_key_float(w, "scale", o->scale);
json_key_string(w, "subpixel_hinting", "rgb");
json_key_string(w, "transform",
o->transform == WL_OUTPUT_TRANSFORM_NORMAL ?
"normal" : "90");
json_key_string(w, "current_workspace", ws_name);
json_key(w, "modes");
json_array_start(w);
if (mode) {
json_object_start(w);
json_key_int(w, "width", mode->width);
json_key_int(w, "height", mode->height);
json_key_int(w, "refresh", mode->refresh);
json_object_end(w);
}
json_array_end(w);
if (mode) {
json_key(w, "current_mode");
json_object_start(w);
json_key_int(w, "width", mode->width);
json_key_int(w, "height", mode->height);
json_key_int(w, "refresh", mode->refresh);
json_object_end(w);
}
json_key(w, "rect");
json_object_start(w);
json_key_int(w, "x", m->m.x);
json_key_int(w, "y", m->m.y);
json_key_int(w, "width", m->m.width);
json_key_int(w, "height", m->m.height);
json_object_end(w);
json_object_end(w);
}
static void
ipc_build_client(struct json_writer *w, Client *c)
{
int floating = c->isfloating ||
!c->mon->lt[c->mon->sellt]->arrange;
json_object_start(w);
json_key_int(w, "id", (int64_t)(uintptr_t)c);
json_key_string(w, "type", "con");
json_key_string(w, "orientation", "none");
json_key_int(w, "percent", -1);
json_key_bool(w, "urgent", !!c->isurgent);
json_key_bool(w, "focused", c == focustop(selmon));
json_key_string(w, "layout", "none");
json_key_string(w, "app_id", client_get_appid(c));
json_key_string(w, "name", client_get_title(c));
json_key_string(w, "title", client_get_title(c));
json_key_bool(w, "fullscreen_mode", !!c->isfullscreen);
json_key_bool(w, "floating", !!floating);
json_key(w, "window_properties");
json_object_start(w);
json_key_string(w, "class", client_get_appid(c));
json_key_string(w, "instance", client_get_appid(c));
json_key_string(w, "title", client_get_title(c));
json_key_bool(w, "transient", 0);
json_object_end(w);
json_key(w, "marks");
json_array_start(w);
json_array_end(w);
json_key(w, "rect");
json_object_start(w);
json_key_int(w, "x", c->geom.x);
json_key_int(w, "y", c->geom.y);
json_key_int(w, "width", c->geom.width);
json_key_int(w, "height", c->geom.height);
json_object_end(w);
json_key(w, "window_rect");
json_object_start(w);
json_key_int(w, "x", (int)c->bw);
json_key_int(w, "y", (int)c->bw);
json_key_int(w, "width",
c->geom.width - 2 * (int)c->bw);
json_key_int(w, "height",
c->geom.height - 2 * (int)c->bw);
json_object_end(w);
json_key(w, "deco_rect");
json_object_start(w);
json_key_int(w, "x", 0);
json_key_int(w, "y", 0);
json_key_int(w, "width", 0);
json_key_int(w, "height", 0);
json_object_end(w);
json_key(w, "geometry");
json_object_start(w);
json_key_int(w, "x", c->geom.x);
json_key_int(w, "y", c->geom.y);
json_key_int(w, "width", c->geom.width);
json_key_int(w, "height", c->geom.height);
json_object_end(w);
json_key(w, "border");
json_object_start(w);
json_key_int(w, "top", (int)c->bw);
json_key_int(w, "bottom", (int)c->bw);
json_key_int(w, "right", (int)c->bw);
json_key_int(w, "left", (int)c->bw);
json_object_end(w);
json_key_int(w, "current_border_width", (int64_t)c->bw);
json_key_int(w, "workspace", current_tag_idx(c->mon) + 1);
json_key(w, "nodes");
json_array_start(w);
json_array_end(w);
json_key(w, "floating_nodes");
json_array_start(w);
json_array_end(w);
json_object_end(w);
}
/* ------------------------------------------------------------------ */
/* Command handlers */
/* ------------------------------------------------------------------ */
static void
ipc_handle_get_workspaces(struct ipc_client *client)
{
struct json_writer w;
Monitor *m;
int i;
json_init(&w);
json_array_start(&w);
wl_list_for_each(m, &mons, link) {
for (i = 0; i < TAGCOUNT; i++) {
uint32_t tag_bit = 1u << i;
if (m->tagset[m->seltags] & tag_bit)
ipc_build_workspace(&w, m, tag_bit, i);
}
}
json_array_end(&w);
ipc_send_json(client->fd, IPC_GET_WORKSPACES, &w);
json_finish(&w);
}
static void
ipc_handle_get_outputs(struct ipc_client *client)
{
struct json_writer w;
Monitor *m;
json_init(&w);
json_array_start(&w);
wl_list_for_each(m, &mons, link) {
ipc_build_output(&w, m);
}
json_array_end(&w);
ipc_send_json(client->fd, IPC_GET_OUTPUTS, &w);
json_finish(&w);
}
static void
ipc_handle_get_version(struct ipc_client *client)
{
struct json_writer w;
json_init(&w);
json_object_start(&w);
json_key_string(&w, "variant", "peachwm");
json_key_string(&w, "human_readable",
"peachwm " VERSION);
json_key_int(&w, "major", 0);
json_key_int(&w, "minor", 2);
json_key_int(&w, "patch", 0);
json_key_string(&w, "loaded_config_file_name", "");
json_object_end(&w);
ipc_send_json(client->fd, IPC_GET_VERSION, &w);
json_finish(&w);
}
static void
ipc_handle_get_tree(struct ipc_client *client)
{
struct json_writer w;
Monitor *m;
Client *c;
int i;
json_init(&w);
/* root node */
json_object_start(&w);
json_key_string(&w, "type", "root");
json_key_string(&w, "name", "root");
json_key(&w, "nodes");
json_array_start(&w);
wl_list_for_each(m, &mons, link) {
/* output node */
json_object_start(&w);
json_key_string(&w, "type", "output");
json_key_string(&w, "name", m->wlr_output->name);
json_key_bool(&w, "active", m->wlr_output->enabled);
json_key(&w, "rect");
json_object_start(&w);
json_key_int(&w, "x", m->m.x);
json_key_int(&w, "y", m->m.y);
json_key_int(&w, "width", m->m.width);
json_key_int(&w, "height", m->m.height);
json_object_end(&w);
json_key(&w, "nodes");
json_array_start(&w);
for (i = 0; i < TAGCOUNT; i++) {
uint32_t tag_bit = 1u << i;
if (!(m->tagset[m->seltags] & tag_bit))
continue;
/* workspace node */
json_object_start(&w);
json_key_string(&w, "type", "workspace");
json_key_string(&w, "name",
ipc_tag_name(i));
json_key_string(&w, "layout", m->ltsymbol);
json_key(&w, "rect");
json_object_start(&w);
json_key_int(&w, "x", m->m.x);
json_key_int(&w, "y", m->m.y);
json_key_int(&w, "width", m->m.width);
json_key_int(&w, "height", m->m.height);
json_object_end(&w);
/* tiled nodes */
json_key(&w, "nodes");
json_array_start(&w);
wl_list_for_each(c, &clients, link) {
if (c->mon == m &&
(c->tags & tag_bit) &&
!c->isfloating &&
!c->isfullscreen)
ipc_build_client(&w, c);
}
json_array_end(&w);
/* floating nodes */
json_key(&w, "floating_nodes");
json_array_start(&w);
wl_list_for_each(c, &clients, link) {
if (c->mon == m &&
(c->tags & tag_bit) &&
(c->isfloating ||
c->isfullscreen))
ipc_build_client(&w, c);
}
json_array_end(&w);
json_object_end(&w); /* workspace */
}
json_array_end(&w); /* output nodes */
json_key(&w, "floating_nodes");
json_array_start(&w);
json_array_end(&w);
json_object_end(&w); /* output */
}
json_array_end(&w); /* root nodes */
json_key(&w, "floating_nodes");
json_array_start(&w);
json_array_end(&w);
json_object_end(&w); /* root */
ipc_send_json(client->fd, IPC_GET_TREE, &w);
json_finish(&w);
}
static void
ipc_handle_command(struct ipc_client *client, const char *payload,
size_t len)
{
struct json_writer w;
int success = 0;
char *cmd = NULL;
if (len > 0) {
cmd = ecalloc(1, len + 1);
memcpy(cmd, payload, len);
cmd[len] = '\0';
}
if (cmd) {
if (!strncmp(cmd, "focus workspace", 15)) {
const char *ws = cmd + 15;
while (*ws == ' ') ws++;
if (*ws) {
int num = atoi(ws);
if (num >= 1 && num <= TAGCOUNT) {
view(&(Arg){.ui =
1u << (num - 1)});
success = 1;
}
}
} else if (!strcmp(cmd, "focus left")) {
focusdir(&(Arg){.i = WLR_DIRECTION_LEFT});
success = 1;
} else if (!strcmp(cmd, "focus right")) {
focusdir(&(Arg){.i = WLR_DIRECTION_RIGHT});
success = 1;
} else if (!strcmp(cmd, "focus up")) {
focusdir(&(Arg){.i = WLR_DIRECTION_UP});
success = 1;
} else if (!strcmp(cmd, "focus down")) {
focusdir(&(Arg){.i = WLR_DIRECTION_DOWN});
success = 1;
} else if (!strncmp(cmd, "move workspace", 14)) {
const char *ws = cmd + 14;
while (*ws == ' ') ws++;
if (*ws) {
int num = atoi(ws);
if (num >= 1 && num <= TAGCOUNT) {
tag(&(Arg){.ui =
1u << (num - 1)});
success = 1;
}
}
} else if (!strcmp(cmd, "floating toggle") ||
!strcmp(cmd, "floating enable") ||
!strcmp(cmd, "floating disable")) {
togglefloating(NULL);
success = 1;
} else if (!strcmp(cmd, "fullscreen toggle") ||
!strcmp(cmd, "fullscreen enable") ||
!strcmp(cmd, "fullscreen disable")) {
togglefullscreen(NULL);
success = 1;
} else if (!strcmp(cmd, "kill") ||
!strcmp(cmd, "close")) {
killclient(NULL);
success = 1;
} else if (!strncmp(cmd, "workspace", 9)) {
const char *ws = cmd + 9;
while (*ws == ' ') ws++;
if (*ws) {
int num = atoi(ws);
if (num >= 1 && num <= TAGCOUNT) {
view(&(Arg){.ui =
1u << (num - 1)});
success = 1;
}
}
} else if (!strcmp(cmd, "exit") ||
!strcmp(cmd, "quit")) {
quit(NULL);
success = 1;
} else if (!strcmp(cmd, "reload")) {
success = 1;
}
}
free(cmd);
json_init(&w);
json_array_start(&w);
json_object_start(&w);
json_key_bool(&w, "success", success);
if (!success)
json_key_string(&w, "error",
"Unknown or unsupported command");
json_object_end(&w);
json_array_end(&w);
ipc_send_json(client->fd, IPC_COMMAND, &w);
json_finish(&w);
}
static void
ipc_handle_subscribe(struct ipc_client *client, const char *payload,
size_t len)
{
struct json_writer w;
(void)len;
client->subscribed = 0;
if (strstr(payload, "\"workspace\""))
client->subscribed |= IPC_SUB_WORKSPACE;
if (strstr(payload, "\"window\""))
client->subscribed |= IPC_SUB_WINDOW;
if (strstr(payload, "\"output\""))
client->subscribed |= IPC_SUB_OUTPUT;
if (strstr(payload, "\"mode\""))
client->subscribed |= IPC_SUB_MODE;
json_init(&w);
json_array_start(&w);
json_object_start(&w);
json_key_bool(&w, "success", 1);
json_object_end(&w);
json_array_end(&w);
ipc_send_json(client->fd, IPC_SUBSCRIBE, &w);
json_finish(&w);
}
/* ------------------------------------------------------------------ */
/* Client I/O */
/* ------------------------------------------------------------------ */
static void
ipc_client_close(struct ipc_client *c)
{
wl_list_remove(&c->link);
if (c->src) {
wl_event_source_remove(c->src);
c->src = NULL;
}
close(c->fd);
free(c->payload);
free(c);
}
static int
ipc_client_handle_readable(int fd, uint32_t mask, void *data)
{
struct ipc_client *c = data;
ssize_t n;
size_t want;
(void)fd;
if (mask & (WL_EVENT_ERROR | WL_EVENT_HANGUP)) {
ipc_client_close(c);
return 0;
}
if (!(mask & WL_EVENT_READABLE))
return 0;
if (!c->header_done) {
want = IPC_HEADER_SIZE - c->hdr_len;
n = read(c->fd, c->hdr_buf + c->hdr_len, want);
if (n <= 0) {
if (n == 0 ||
(errno != EAGAIN && errno != EWOULDBLOCK)) {
ipc_client_close(c);
return 0;
}
return 0;
}
c->hdr_len += (size_t)n;
if (c->hdr_len < IPC_HEADER_SIZE)
return 0;
/* Validate magic */
if (memcmp(c->hdr_buf, IPC_MAGIC, IPC_MAGIC_LEN) != 0) {
ipc_client_close(c);
return 0;
}
/* Parse header */
memcpy(&c->payload_size,
c->hdr_buf + IPC_MAGIC_LEN, sizeof(uint32_t));
memcpy(&c->msg_type,
c->hdr_buf + IPC_MAGIC_LEN + 4, sizeof(uint32_t));
if (c->payload_size > 0) {
if (c->payload_size > c->payload_alloc) {
free(c->payload);
c->payload_alloc = c->payload_size + 1;
c->payload = ecalloc(1, c->payload_alloc);
}
c->payload_len = 0;
}
c->header_done = 1;
}
/* Read payload */
if (c->header_done && c->payload_size > 0) {
want = c->payload_size - c->payload_len;
if (want > 0) {
n = read(c->fd,
c->payload + c->payload_len, want);
if (n <= 0) {
if (n == 0 ||
(errno != EAGAIN &&
errno != EWOULDBLOCK)) {
ipc_client_close(c);
return 0;
}
return 0;
}
c->payload_len += (size_t)n;
}
if (c->payload_len < c->payload_size)
return 0;
}
/* Full message received – dispatch */
switch (c->msg_type) {
case IPC_COMMAND:
ipc_handle_command(c, c->payload, c->payload_size);
break;
case IPC_GET_WORKSPACES:
ipc_handle_get_workspaces(c);
break;
case IPC_GET_OUTPUTS:
ipc_handle_get_outputs(c);
break;
case IPC_GET_VERSION:
ipc_handle_get_version(c);
break;
case IPC_GET_TREE:
ipc_handle_get_tree(c);
break;
case IPC_SUBSCRIBE:
ipc_handle_subscribe(c, c->payload, c->payload_size);
break;
default:
ipc_send_raw(c->fd, c->msg_type, "{}", 2);
break;
}
/* Reset for next message */
c->hdr_len = 0;
c->header_done = 0;
c->payload_len = 0;
c->payload_size = 0;
return 0;
}
/* ------------------------------------------------------------------ */
/* Accept new connections */
/* ------------------------------------------------------------------ */
static int
ipc_handle_accept(int fd, uint32_t mask, void *data)
{
struct sockaddr_un un;
socklen_t len = sizeof(un);
int cfd;
struct ipc_client *c;
struct wl_event_source *src;
int flags;
(void)data;
if (!(mask & WL_EVENT_READABLE))
return 0;
cfd = accept(fd, (struct sockaddr *)&un, &len);
if (cfd < 0) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
wlr_log(WLR_ERROR, "ipc: accept: %s",
strerror(errno));
return 0;
}
/* Set non-blocking and close-on-exec */
flags = fcntl(cfd, F_GETFL);
if (flags < 0 || fcntl(cfd, F_SETFL, flags | O_NONBLOCK) < 0) {
close(cfd);
return 0;
}
c = ecalloc(1, sizeof(*c));
c->fd = cfd;
c->hdr_len = 0;
c->header_done = 0;
c->payload_size = 0;
c->payload_len = 0;
c->payload_alloc = 0;
c->payload = NULL;
c->subscribed = 0;
src = wl_event_loop_add_fd(ipc_event_loop, cfd,
WL_EVENT_READABLE,
ipc_client_handle_readable, c);
if (!src) {
close(cfd);
free(c);
return 0;
}
c->src = src;
wl_list_insert(&ipc_clients, &c->link);
wlr_log(WLR_DEBUG, "ipc: client connected (fd %d)", cfd);
return 0;
}
/* ------------------------------------------------------------------ */
/* Public API */
/* ------------------------------------------------------------------ */
static void
ipc_socket_init(void)
{
const char *runtime_dir;
struct sockaddr_un un;
int fd, flags;
char sock_path[256];
struct passwd *pw;
wl_list_init(&ipc_clients);
ipc_event_loop = event_loop;
runtime_dir = getenv("XDG_RUNTIME_DIR");
if (runtime_dir && runtime_dir[0]) {
snprintf(sock_path, sizeof(sock_path),
"%s/peachwm.sock", runtime_dir);
} else {
pw = getpwuid(getuid());
if (pw)
snprintf(sock_path, sizeof(sock_path),
"/tmp/peachwm-%s.sock", pw->pw_name);
else
snprintf(sock_path, sizeof(sock_path),
"/tmp/peachwm-%d.sock", getuid());
}
unlink(sock_path);
fd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
if (fd < 0) {
wlr_log(WLR_ERROR, "ipc: socket: %s", strerror(errno));
return;
}
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strncpy(un.sun_path, sock_path, sizeof(un.sun_path) - 1);
if (bind(fd, (struct sockaddr *)&un, sizeof(un)) < 0) {
wlr_log(WLR_ERROR, "ipc: bind(%s): %s",
sock_path, strerror(errno));
close(fd);
return;
}
chmod(sock_path, 0700);
if (listen(fd, 8) < 0) {
wlr_log(WLR_ERROR, "ipc: listen: %s", strerror(errno));
close(fd);
unlink(sock_path);
return;
}
flags = fcntl(fd, F_GETFL);
if (flags < 0 ||
fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
wlr_log(WLR_ERROR, "ipc: fcntl(O_NONBLOCK): %s",
strerror(errno));
close(fd);
unlink(sock_path);
return;
}
ipc_listen_fd = fd;
strncpy(ipc_socket_path, sock_path,
sizeof(ipc_socket_path) - 1);
ipc_socket_path[sizeof(ipc_socket_path) - 1] = '\0';
ipc_listen_src = wl_event_loop_add_fd(
ipc_event_loop, fd, WL_EVENT_READABLE,
ipc_handle_accept, NULL);
if (!ipc_listen_src) {
wlr_log(WLR_ERROR,
"ipc: wl_event_loop_add_fd failed");
close(fd);
unlink(sock_path);
ipc_listen_fd = -1;
ipc_socket_path[0] = '\0';
return;
}
wlr_log(WLR_DEBUG, "ipc: listening on %s", sock_path);
}
static void
ipc_socket_finish(void)
{
struct ipc_client *c, *tmp;
wl_list_for_each_safe(c, tmp, &ipc_clients, link)
ipc_client_close(c);
if (ipc_listen_src) {
wl_event_source_remove(ipc_listen_src);
ipc_listen_src = NULL;
}
if (ipc_listen_fd >= 0) {
close(ipc_listen_fd);
if (ipc_socket_path[0])
unlink(ipc_socket_path);
ipc_listen_fd = -1;
ipc_socket_path[0] = '\0';
}
}
/* ------------------------------------------------------------------ */
/* Event helpers called from peachwm.c */
/* ------------------------------------------------------------------ */
static void
ipc_socket_send_workspace_event(const char *change)
{
struct json_writer w;
if (wl_list_empty(&ipc_clients))
return;
json_init(&w);
json_object_start(&w);
json_key_string(&w, "change", change ? change : "focus");
if (selmon) {
int ti = current_tag_idx(selmon);
uint32_t tag_bit = 1u << ti;
if (selmon->tagset[selmon->seltags] & tag_bit) {
json_key(&w, "current");
ipc_build_workspace(&w, selmon, tag_bit, ti);
}
}
json_object_end(&w);
ipc_broadcast((uint32_t)IPC_EVENT_WORKSPACE, &w);
json_finish(&w);
}
static void
ipc_socket_send_window_event(const char *change)
{
struct json_writer w;
if (wl_list_empty(&ipc_clients))
return;
json_init(&w);
json_object_start(&w);
json_key_string(&w, "change", change ? change : "focus");
if (selmon) {
Client *c = focustop(selmon);
if (c) {
json_key(&w, "container");
ipc_build_client(&w, c);
}
}
json_object_end(&w);
ipc_broadcast((uint32_t)IPC_EVENT_WINDOW, &w);
json_finish(&w);
}
static void
ipc_socket_send_output_event(void)
{
struct json_writer w;
if (wl_list_empty(&ipc_clients))
return;
json_init(&w);
json_object_start(&w);
json_key_string(&w, "change", "unspecified");
json_object_end(&w);
ipc_broadcast((uint32_t)IPC_EVENT_OUTPUT, &w);
json_finish(&w);
}