Files
peachwm/src/ipc_socket.c
T

1228 lines
28 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
*
* Function implementations for the IPC socket.
*/
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.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>
#include <wayland-server-core.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_output_layout.h>
#include <wlr/util/log.h>
#include "client.h"
#include "ipc.h"
#include "ipc_socket.h"
#include "util.h"
#include "common.h"
/* ------------------------------------------------------------------ */
/* Functions from peachwm.c called by IPC command handler */
/* ------------------------------------------------------------------ */
void view(const Arg *arg);
void focusdir(const Arg *arg);
void tag(const Arg *arg);
void togglefloating(const Arg *arg);
void togglefullscreen(const Arg *arg);
void killclient(const Arg *arg);
void quit(const Arg *arg);
void do_reload(void);
/* ------------------------------------------------------------------ */
/* 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;
char *newbuf = realloc(w->buf, w->cap);
if (!newbuf) {
fprintf(stderr, "peachwm: json_grow realloc failed\n");
return;
}
w->buf = newbuf;
}
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));
}
[[gnu::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)
{
if (w->depth + 1 >= 32)
return;
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)
{
if (w->depth + 1 >= 32)
return;
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;
size_t total = IPC_HEADER_SIZE + len;
size_t sent = 0;
while (sent < total) {
ssize_t ret = sendmsg(fd, &msg, MSG_NOSIGNAL);
if (ret < 0) {
if (errno == EINTR)
continue;
if (errno == EAGAIN || errno == EWOULDBLOCK)
return 0;
return -1;
}
sent += (size_t)ret;
/* Advance iovecs past what was sent */
size_t remain = sent;
while (remain > 0 && msg.msg_iovlen > 0) {
if (remain < msg.msg_iov[0].iov_len) {
msg.msg_iov[0].iov_base =
(void *)((uintptr_t)msg.msg_iov[0].iov_base
+ remain);
msg.msg_iov[0].iov_len -= remain;
remain = 0;
} else {
remain -= msg.msg_iov[0].iov_len;
msg.msg_iov++;
msg.msg_iovlen--;
}
}
}
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[tag_idx]);
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 = nullptr;
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 ||
!curlayout(c->mon)->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[i]);
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 = nullptr;
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(nullptr);
success = 1;
} else if (!strcmp(cmd, "fullscreen toggle") ||
!strcmp(cmd, "fullscreen enable") ||
!strcmp(cmd, "fullscreen disable")) {
togglefullscreen(nullptr);
success = 1;
} else if (!strcmp(cmd, "kill") ||
!strcmp(cmd, "close")) {
killclient(nullptr);
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(nullptr);
success = 1;
} else if (!strcmp(cmd, "reload")) {
do_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 = nullptr;
}
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 = nullptr;
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 */
/* ------------------------------------------------------------------ */
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 = wl_display_get_event_loop(dpy);
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);
un.sun_path[sizeof(un.sun_path) - 1] = '\0';
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, nullptr);
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);
}
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 = nullptr;
}
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 */
/* ------------------------------------------------------------------ */
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);
}
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);
}
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);
}