forked from external/yambar
Merge branch 'i3-split'
This commit is contained in:
commit
e710152248
4 changed files with 433 additions and 270 deletions
|
@ -37,8 +37,10 @@ find_file(I3_IPC_H i3/ipc.h)
|
||||||
if (NOT I3_IPC_H)
|
if (NOT I3_IPC_H)
|
||||||
message(FATAL_ERROR "cannot find header file: i3/ipc.h")
|
message(FATAL_ERROR "cannot find header file: i3/ipc.h")
|
||||||
endif ()
|
endif ()
|
||||||
|
add_library(i3-common STATIC EXCLUDE_FROM_ALL i3-common.c i3-common.h)
|
||||||
|
|
||||||
add_library(i3 ${lib_type} i3.c)
|
add_library(i3 ${lib_type} i3.c)
|
||||||
target_link_libraries(i3 module-sdk dynlist PkgConfig::json)
|
target_link_libraries(i3 module-sdk i3-common dynlist PkgConfig::json)
|
||||||
if (ENABLE_X11)
|
if (ENABLE_X11)
|
||||||
target_link_libraries(i3 xcb-stuff)
|
target_link_libraries(i3 xcb-stuff)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
311
modules/i3-common.c
Normal file
311
modules/i3-common.c
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
#include "i3-common.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <poll.h>
|
||||||
|
|
||||||
|
#if defined(ENABLE_X11)
|
||||||
|
#include <xcb/xcb.h>
|
||||||
|
#include <xcb/xcb_aux.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <i3/ipc.h>
|
||||||
|
#include <json-c/json_tokener.h>
|
||||||
|
|
||||||
|
#define LOG_MODULE "i3:common"
|
||||||
|
#include "../log.h"
|
||||||
|
|
||||||
|
#if defined(ENABLE_X11)
|
||||||
|
#include "../xcb.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ENABLE_X11)
|
||||||
|
static bool
|
||||||
|
get_socket_address_x11(struct sockaddr_un *addr)
|
||||||
|
{
|
||||||
|
int default_screen;
|
||||||
|
xcb_connection_t *conn = xcb_connect(NULL, &default_screen);
|
||||||
|
if (xcb_connection_has_error(conn) > 0) {
|
||||||
|
LOG_ERR("failed to connect to X");
|
||||||
|
xcb_disconnect(conn);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
xcb_screen_t *screen = xcb_aux_get_screen(conn, default_screen);
|
||||||
|
|
||||||
|
xcb_atom_t atom = get_atom(conn, "I3_SOCKET_PATH");
|
||||||
|
assert(atom != XCB_ATOM_NONE);
|
||||||
|
|
||||||
|
xcb_get_property_cookie_t cookie
|
||||||
|
= xcb_get_property_unchecked(
|
||||||
|
conn, false, screen->root, atom,
|
||||||
|
XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path));
|
||||||
|
|
||||||
|
xcb_generic_error_t *err = NULL;
|
||||||
|
xcb_get_property_reply_t *reply =
|
||||||
|
xcb_get_property_reply(conn, cookie, &err);
|
||||||
|
bool ret = false;
|
||||||
|
|
||||||
|
if (err != NULL) {
|
||||||
|
LOG_ERR("failed to get i3 socket path: %s", xcb_error(err));
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int len = xcb_get_property_value_length(reply);
|
||||||
|
assert(len < sizeof(addr->sun_path));
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
LOG_ERR("failed to get i3 socket path: empty reply");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(addr->sun_path, xcb_get_property_value(reply), len);
|
||||||
|
addr->sun_path[len] = '\0';
|
||||||
|
|
||||||
|
ret = true;
|
||||||
|
|
||||||
|
err:
|
||||||
|
free(err);
|
||||||
|
free(reply);
|
||||||
|
xcb_disconnect(conn);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool
|
||||||
|
i3_get_socket_address(struct sockaddr_un *addr)
|
||||||
|
{
|
||||||
|
*addr = (struct sockaddr_un){.sun_family = AF_UNIX};
|
||||||
|
|
||||||
|
const char *sway_sock = getenv("SWAYSOCK");
|
||||||
|
if (sway_sock == NULL) {
|
||||||
|
sway_sock = getenv("I3SOCK");
|
||||||
|
if (sway_sock == NULL) {
|
||||||
|
#if defined(ENABLE_X11)
|
||||||
|
return get_socket_address_x11(addr);
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strncpy(addr->sun_path, sway_sock, sizeof(addr->sun_path) - 1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
i3_send_pkg(int sock, int cmd, char *data)
|
||||||
|
{
|
||||||
|
const size_t size = data != NULL ? strlen(data) : 0;
|
||||||
|
const i3_ipc_header_t hdr = {
|
||||||
|
.magic = I3_IPC_MAGIC,
|
||||||
|
.size = size,
|
||||||
|
.type = cmd
|
||||||
|
};
|
||||||
|
|
||||||
|
if (write(sock, &hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (data != NULL) {
|
||||||
|
if (write(sock, data, size) != (ssize_t)size)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
i3_receive_loop(int abort_fd, int sock,
|
||||||
|
const struct i3_ipc_callbacks *cbs, void *data)
|
||||||
|
{
|
||||||
|
/* Initial reply typically requires a couple of KB. But we often
|
||||||
|
* need more later. For example, switching workspaces can result
|
||||||
|
* in quite big notification messages. */
|
||||||
|
size_t reply_buf_size = 4096;
|
||||||
|
char *buf = malloc(reply_buf_size);
|
||||||
|
size_t buf_idx = 0;
|
||||||
|
|
||||||
|
bool err = false;
|
||||||
|
|
||||||
|
while (!err) {
|
||||||
|
struct pollfd fds[] = {
|
||||||
|
{.fd = abort_fd, .events = POLLIN},
|
||||||
|
{.fd = sock, .events = POLLIN}
|
||||||
|
};
|
||||||
|
|
||||||
|
int res = poll(fds, 2, -1);
|
||||||
|
if (res <= 0) {
|
||||||
|
LOG_ERRNO("failed to poll()");
|
||||||
|
err = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds[0].revents & POLLIN) {
|
||||||
|
LOG_DBG("aborted");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fds[1].revents & POLLHUP) {
|
||||||
|
LOG_DBG("disconnected");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(fds[1].revents & POLLIN);
|
||||||
|
|
||||||
|
/* Grow receive buffer, if necessary */
|
||||||
|
if (buf_idx == reply_buf_size) {
|
||||||
|
LOG_DBG("growing reply buffer: %zu -> %zu",
|
||||||
|
reply_buf_size, reply_buf_size * 2);
|
||||||
|
|
||||||
|
char *new_buf = realloc(buf, reply_buf_size * 2);
|
||||||
|
if (new_buf == NULL) {
|
||||||
|
LOG_ERR("failed to grow reply buffer from %zu to %zu bytes",
|
||||||
|
reply_buf_size, reply_buf_size * 2);
|
||||||
|
err = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf = new_buf;
|
||||||
|
reply_buf_size *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(reply_buf_size > buf_idx);
|
||||||
|
|
||||||
|
ssize_t bytes = read(sock, &buf[buf_idx], reply_buf_size - buf_idx);
|
||||||
|
if (bytes < 0) {
|
||||||
|
LOG_ERRNO("failed to read from i3's socket");
|
||||||
|
err = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf_idx += bytes;
|
||||||
|
|
||||||
|
while (!err && buf_idx >= sizeof(i3_ipc_header_t)) {
|
||||||
|
const i3_ipc_header_t *hdr = (const i3_ipc_header_t *)buf;
|
||||||
|
if (strncmp(hdr->magic, I3_IPC_MAGIC, sizeof(hdr->magic)) != 0) {
|
||||||
|
LOG_ERR(
|
||||||
|
"i3 IPC header magic mismatch: expected \"%.*s\", got \"%.*s\"",
|
||||||
|
(int)sizeof(hdr->magic), I3_IPC_MAGIC,
|
||||||
|
(int)sizeof(hdr->magic), hdr->magic);
|
||||||
|
|
||||||
|
err = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t total_size = sizeof(i3_ipc_header_t) + hdr->size;
|
||||||
|
|
||||||
|
if (total_size > buf_idx) {
|
||||||
|
LOG_DBG("got %zd bytes, need %zu", bytes, total_size);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Json-c expects a NULL-terminated string */
|
||||||
|
char json_str[hdr->size + 1];
|
||||||
|
memcpy(json_str, &buf[sizeof(*hdr)], hdr->size);
|
||||||
|
json_str[hdr->size] = '\0';
|
||||||
|
//printf("raw: %s\n", json_str);
|
||||||
|
LOG_DBG("raw: %s\n", json_str);
|
||||||
|
|
||||||
|
//json_tokener *tokener = json_tokener_new();
|
||||||
|
struct json_object *json = json_tokener_parse(json_str);
|
||||||
|
if (json == NULL) {
|
||||||
|
LOG_ERR("failed to parse json");
|
||||||
|
err = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//err = pkt_handler(hdr, json, data);
|
||||||
|
i3_ipc_callback_t pkt_handler = NULL;
|
||||||
|
switch (hdr->type) {
|
||||||
|
case I3_IPC_REPLY_TYPE_COMMAND:
|
||||||
|
pkt_handler = cbs->reply_command;
|
||||||
|
break;
|
||||||
|
case I3_IPC_REPLY_TYPE_WORKSPACES:
|
||||||
|
pkt_handler = cbs->reply_workspaces;
|
||||||
|
break;
|
||||||
|
case I3_IPC_REPLY_TYPE_SUBSCRIBE:
|
||||||
|
pkt_handler = cbs->reply_subscribe;
|
||||||
|
break;
|
||||||
|
case I3_IPC_REPLY_TYPE_OUTPUTS:
|
||||||
|
pkt_handler = cbs->reply_outputs;
|
||||||
|
break;
|
||||||
|
case I3_IPC_REPLY_TYPE_TREE:
|
||||||
|
pkt_handler = cbs->reply_tree;
|
||||||
|
break;
|
||||||
|
case I3_IPC_REPLY_TYPE_MARKS:
|
||||||
|
pkt_handler = cbs->reply_marks;
|
||||||
|
break;
|
||||||
|
case I3_IPC_REPLY_TYPE_BAR_CONFIG:
|
||||||
|
pkt_handler = cbs->reply_bar_config;
|
||||||
|
break;
|
||||||
|
case I3_IPC_REPLY_TYPE_VERSION:
|
||||||
|
pkt_handler = cbs->reply_version;
|
||||||
|
break;
|
||||||
|
case I3_IPC_REPLY_TYPE_BINDING_MODES:
|
||||||
|
pkt_handler = cbs->reply_binding_modes;
|
||||||
|
break;
|
||||||
|
case I3_IPC_REPLY_TYPE_CONFIG:
|
||||||
|
pkt_handler = cbs->reply_config;
|
||||||
|
break;
|
||||||
|
case I3_IPC_REPLY_TYPE_TICK:
|
||||||
|
pkt_handler = cbs->reply_tick;
|
||||||
|
break;
|
||||||
|
#if defined(I3_IPC_REPLY_TYPE_SYNC)
|
||||||
|
case I3_IPC_REPLY_TYPE_SYNC:
|
||||||
|
pkt_handler = cbs->reply_sync;
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case I3_IPC_EVENT_WORKSPACE:
|
||||||
|
pkt_handler = cbs->event_workspace;
|
||||||
|
break;
|
||||||
|
case I3_IPC_EVENT_OUTPUT:
|
||||||
|
pkt_handler = cbs->event_output;
|
||||||
|
break;
|
||||||
|
case I3_IPC_EVENT_MODE:
|
||||||
|
pkt_handler = cbs->event_mode;
|
||||||
|
break;
|
||||||
|
case I3_IPC_EVENT_WINDOW:
|
||||||
|
pkt_handler = cbs->event_window;
|
||||||
|
break;
|
||||||
|
case I3_IPC_EVENT_BARCONFIG_UPDATE:
|
||||||
|
pkt_handler = cbs->event_barconfig_update;
|
||||||
|
break;
|
||||||
|
case I3_IPC_EVENT_BINDING:
|
||||||
|
pkt_handler = cbs->event_binding;
|
||||||
|
break;
|
||||||
|
case I3_IPC_EVENT_SHUTDOWN:
|
||||||
|
pkt_handler = cbs->event_shutdown;
|
||||||
|
break;
|
||||||
|
case I3_IPC_EVENT_TICK:
|
||||||
|
pkt_handler = cbs->event_tick;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
LOG_ERR("unimplemented IPC reply type: %d", hdr->type);
|
||||||
|
pkt_handler = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pkt_handler != NULL)
|
||||||
|
err = !pkt_handler(hdr->type, json, data);
|
||||||
|
else
|
||||||
|
LOG_DBG("no handler for reply/event %d; ignoring", hdr->type);
|
||||||
|
|
||||||
|
json_object_put(json);
|
||||||
|
|
||||||
|
assert(total_size <= buf_idx);
|
||||||
|
memmove(buf, &buf[total_size], buf_idx - total_size);
|
||||||
|
buf_idx -= total_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cbs->burst_done != NULL)
|
||||||
|
cbs->burst_done(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(buf);
|
||||||
|
return !err;
|
||||||
|
}
|
45
modules/i3-common.h
Normal file
45
modules/i3-common.h
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/un.h>
|
||||||
|
|
||||||
|
//#include <i3/ipc.h>
|
||||||
|
#include <json-c/json_util.h>
|
||||||
|
|
||||||
|
bool i3_get_socket_address(struct sockaddr_un *addr);
|
||||||
|
bool i3_send_pkg(int sock, int cmd, char *data);
|
||||||
|
|
||||||
|
typedef bool (*i3_ipc_callback_t)(int type, const struct json_object *json, void *data);
|
||||||
|
|
||||||
|
struct i3_ipc_callbacks {
|
||||||
|
void (*burst_done)(void *data);
|
||||||
|
|
||||||
|
i3_ipc_callback_t reply_command;
|
||||||
|
i3_ipc_callback_t reply_workspaces;
|
||||||
|
i3_ipc_callback_t reply_subscribe;
|
||||||
|
i3_ipc_callback_t reply_outputs;
|
||||||
|
i3_ipc_callback_t reply_tree;
|
||||||
|
i3_ipc_callback_t reply_marks;
|
||||||
|
i3_ipc_callback_t reply_bar_config;
|
||||||
|
i3_ipc_callback_t reply_version;
|
||||||
|
i3_ipc_callback_t reply_binding_modes;
|
||||||
|
i3_ipc_callback_t reply_config;
|
||||||
|
i3_ipc_callback_t reply_tick;
|
||||||
|
i3_ipc_callback_t reply_sync;
|
||||||
|
|
||||||
|
i3_ipc_callback_t event_workspace;
|
||||||
|
i3_ipc_callback_t event_output;
|
||||||
|
i3_ipc_callback_t event_mode;
|
||||||
|
i3_ipc_callback_t event_window;
|
||||||
|
i3_ipc_callback_t event_barconfig_update;
|
||||||
|
i3_ipc_callback_t event_binding;
|
||||||
|
i3_ipc_callback_t event_shutdown;
|
||||||
|
i3_ipc_callback_t event_tick;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool i3_receive_loop(
|
||||||
|
int abort_fd, int sock,
|
||||||
|
const struct i3_ipc_callbacks *callbacks, void *data);
|
343
modules/i3.c
343
modules/i3.c
|
@ -1,29 +1,14 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdio.h>
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <threads.h>
|
#include <threads.h>
|
||||||
|
|
||||||
#include <poll.h>
|
|
||||||
|
|
||||||
#include <sys/types.h>
|
#include <sys/types.h>
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <sys/un.h>
|
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
#if defined(ENABLE_X11)
|
|
||||||
#include <xcb/xcb.h>
|
|
||||||
#include <xcb/xcb_aux.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <i3/ipc.h>
|
#include <i3/ipc.h>
|
||||||
|
|
||||||
#include <json-c/json_tokener.h>
|
|
||||||
#include <json-c/json_util.h>
|
|
||||||
#include <json-c/linkhash.h>
|
|
||||||
|
|
||||||
#define LOG_MODULE "i3"
|
#define LOG_MODULE "i3"
|
||||||
#define LOG_ENABLE_DBG 0
|
#define LOG_ENABLE_DBG 0
|
||||||
#include "../log.h"
|
#include "../log.h"
|
||||||
|
@ -33,9 +18,7 @@
|
||||||
#include "../particles/dynlist.h"
|
#include "../particles/dynlist.h"
|
||||||
#include "../plugin.h"
|
#include "../plugin.h"
|
||||||
|
|
||||||
#if defined(ENABLE_X11)
|
#include "i3-common.h"
|
||||||
#include "../xcb.h"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
struct ws_content {
|
struct ws_content {
|
||||||
char *name;
|
char *name;
|
||||||
|
@ -60,6 +43,8 @@ struct private {
|
||||||
int left_spacing;
|
int left_spacing;
|
||||||
int right_spacing;
|
int right_spacing;
|
||||||
|
|
||||||
|
bool dirty;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
struct ws_content *v;
|
struct ws_content *v;
|
||||||
size_t count;
|
size_t count;
|
||||||
|
@ -185,29 +170,9 @@ workspace_lookup(struct private *m, const char *name)
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
|
||||||
send_pkg(int sock, int cmd, char *data)
|
|
||||||
{
|
|
||||||
size_t size = data != NULL ? strlen(data) : 0;
|
|
||||||
i3_ipc_header_t hdr = {
|
|
||||||
.magic = I3_IPC_MAGIC,
|
|
||||||
.size = size,
|
|
||||||
.type = cmd
|
|
||||||
};
|
|
||||||
|
|
||||||
if (write(sock, &hdr, sizeof(hdr)) != (ssize_t)sizeof(hdr))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (data != NULL) {
|
|
||||||
if (write(sock, data, size) != (ssize_t)size)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
handle_get_version_reply(struct private *m, const struct json_object *json)
|
handle_get_version_reply(int type, const struct json_object *json, void *_m)
|
||||||
{
|
{
|
||||||
if (!json_object_is_type(json, json_type_object)) {
|
if (!json_object_is_type(json, json_type_object)) {
|
||||||
LOG_ERR("'version' reply is not of type 'object'");
|
LOG_ERR("'version' reply is not of type 'object'");
|
||||||
|
@ -226,7 +191,7 @@ handle_get_version_reply(struct private *m, const struct json_object *json)
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
handle_subscribe_reply(struct private *m, const struct json_object *json)
|
handle_subscribe_reply(int type, const struct json_object *json, void *_m)
|
||||||
{
|
{
|
||||||
if (!json_object_is_type(json, json_type_object)) {
|
if (!json_object_is_type(json, json_type_object)) {
|
||||||
LOG_ERR("'subscribe' reply is not of type 'object'");
|
LOG_ERR("'subscribe' reply is not of type 'object'");
|
||||||
|
@ -249,14 +214,20 @@ handle_subscribe_reply(struct private *m, const struct json_object *json)
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
handle_get_workspaces_reply(struct private *m, const struct json_object *json)
|
handle_get_workspaces_reply(int type, const struct json_object *json, void *_mod)
|
||||||
{
|
{
|
||||||
|
struct module *mod = _mod;
|
||||||
|
struct private *m = mod->private;
|
||||||
|
|
||||||
if (!json_object_is_type(json, json_type_array)) {
|
if (!json_object_is_type(json, json_type_array)) {
|
||||||
LOG_ERR("'workspaces' reply is not of type 'array'");
|
LOG_ERR("'workspaces' reply is not of type 'array'");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mtx_lock(&mod->lock);
|
||||||
|
|
||||||
workspaces_free(m);
|
workspaces_free(m);
|
||||||
|
m->dirty = true;
|
||||||
|
|
||||||
size_t count = json_object_array_length(json);
|
size_t count = json_object_array_length(json);
|
||||||
m->workspaces.count = count;
|
m->workspaces.count = count;
|
||||||
|
@ -266,18 +237,23 @@ handle_get_workspaces_reply(struct private *m, const struct json_object *json)
|
||||||
if (!workspace_from_json(
|
if (!workspace_from_json(
|
||||||
json_object_array_get_idx(json, i), &m->workspaces.v[i])) {
|
json_object_array_get_idx(json, i), &m->workspaces.v[i])) {
|
||||||
workspaces_free(m);
|
workspaces_free(m);
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DBG("#%zu: %s", i, m->workspaces.v[i].name);
|
LOG_DBG("#%zu: %s", i, m->workspaces.v[i].name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
handle_workspace_event(struct private *m, const struct json_object *json)
|
handle_workspace_event(int type, const struct json_object *json, void *_mod)
|
||||||
{
|
{
|
||||||
|
struct module *mod = _mod;
|
||||||
|
struct private *m = mod->private;
|
||||||
|
|
||||||
if (!json_object_is_type(json, json_type_object)) {
|
if (!json_object_is_type(json, json_type_object)) {
|
||||||
LOG_ERR("'workspace' event is not of type 'object'");
|
LOG_ERR("'workspace' event is not of type 'object'");
|
||||||
return false;
|
return false;
|
||||||
|
@ -312,12 +288,14 @@ handle_workspace_event(struct private *m, const struct json_object *json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mtx_lock(&mod->lock);
|
||||||
|
|
||||||
if (strcmp(change_str, "init") == 0) {
|
if (strcmp(change_str, "init") == 0) {
|
||||||
assert(workspace_lookup(m, json_object_get_string(current_name)) == NULL);
|
assert(workspace_lookup(m, json_object_get_string(current_name)) == NULL);
|
||||||
|
|
||||||
struct workspace ws;
|
struct workspace ws;
|
||||||
if (!workspace_from_json(current, &ws))
|
if (!workspace_from_json(current, &ws))
|
||||||
return false;
|
goto err;
|
||||||
|
|
||||||
workspace_add(m, ws);
|
workspace_add(m, ws);
|
||||||
}
|
}
|
||||||
|
@ -330,14 +308,14 @@ handle_workspace_event(struct private *m, const struct json_object *json)
|
||||||
else if (strcmp(change_str, "focus") == 0) {
|
else if (strcmp(change_str, "focus") == 0) {
|
||||||
if (old == NULL || !json_object_is_type(old, json_type_object)) {
|
if (old == NULL || !json_object_is_type(old, json_type_object)) {
|
||||||
LOG_ERR("'workspace' event did not contain a 'old' object value");
|
LOG_ERR("'workspace' event did not contain a 'old' object value");
|
||||||
return false;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct json_object *old_name = json_object_object_get(old, "name");
|
struct json_object *old_name = json_object_object_get(old, "name");
|
||||||
if (old_name == NULL || !json_object_is_type(old_name, json_type_string)) {
|
if (old_name == NULL || !json_object_is_type(old_name, json_type_string)) {
|
||||||
LOG_ERR("'workspace' event's 'old' object did not "
|
LOG_ERR("'workspace' event's 'old' object did not "
|
||||||
"contain a 'name' string value");
|
"contain a 'name' string value");
|
||||||
return false;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct workspace *w = workspace_lookup(
|
struct workspace *w = workspace_lookup(
|
||||||
|
@ -356,7 +334,7 @@ handle_workspace_event(struct private *m, const struct json_object *json)
|
||||||
if (urgent == NULL || !json_object_is_type(urgent, json_type_boolean)) {
|
if (urgent == NULL || !json_object_is_type(urgent, json_type_boolean)) {
|
||||||
LOG_ERR("'workspace' event's 'current' object did not "
|
LOG_ERR("'workspace' event's 'current' object did not "
|
||||||
"contain a 'urgent' boolean value");
|
"contain a 'urgent' boolean value");
|
||||||
return false;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
w->urgent = json_object_get_boolean(urgent);
|
w->urgent = json_object_get_boolean(urgent);
|
||||||
|
@ -375,7 +353,7 @@ handle_workspace_event(struct private *m, const struct json_object *json)
|
||||||
if (urgent == NULL || !json_object_is_type(urgent, json_type_boolean)) {
|
if (urgent == NULL || !json_object_is_type(urgent, json_type_boolean)) {
|
||||||
LOG_ERR("'workspace' event's 'current' object did not "
|
LOG_ERR("'workspace' event's 'current' object did not "
|
||||||
"contain a 'urgent' boolean value");
|
"contain a 'urgent' boolean value");
|
||||||
return false;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct workspace *w = workspace_lookup(
|
struct workspace *w = workspace_lookup(
|
||||||
|
@ -386,12 +364,23 @@ handle_workspace_event(struct private *m, const struct json_object *json)
|
||||||
else if (strcmp(change_str, "reload") == 0)
|
else if (strcmp(change_str, "reload") == 0)
|
||||||
LOG_WARN("unimplemented: 'reload' event");
|
LOG_WARN("unimplemented: 'reload' event");
|
||||||
|
|
||||||
|
m->dirty = true;
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
err:
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
handle_window_event(struct private *m, const struct json_object *json)
|
handle_window_event(int type, const struct json_object *json, void *_mod)
|
||||||
{
|
{
|
||||||
|
struct module *mod = _mod;
|
||||||
|
struct private *m = mod->private;
|
||||||
|
|
||||||
|
mtx_lock(&mod->lock);
|
||||||
|
|
||||||
struct workspace *ws = NULL;
|
struct workspace *ws = NULL;
|
||||||
size_t focused = 0;
|
size_t focused = 0;
|
||||||
for (size_t i = 0; i < m->workspaces.count; i++) {
|
for (size_t i = 0; i < m->workspaces.count; i++) {
|
||||||
|
@ -406,15 +395,16 @@ handle_window_event(struct private *m, const struct json_object *json)
|
||||||
|
|
||||||
if (!json_object_is_type(json, json_type_object)) {
|
if (!json_object_is_type(json, json_type_object)) {
|
||||||
LOG_ERR("'window' event is not of type 'object'");
|
LOG_ERR("'window' event is not of type 'object'");
|
||||||
return false;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct json_object *change = json_object_object_get(json, "change");
|
struct json_object *change = json_object_object_get(json, "change");
|
||||||
if (change == NULL || !json_object_is_type(change, json_type_string)) {
|
if (change == NULL || !json_object_is_type(change, json_type_string)) {
|
||||||
LOG_ERR("'window' event did not contain a 'change' string value");
|
LOG_ERR("'window' event did not contain a 'change' string value");
|
||||||
return false;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m->dirty = true;
|
||||||
const char *change_str = json_object_get_string(change);
|
const char *change_str = json_object_get_string(change);
|
||||||
|
|
||||||
if (strcmp(change_str, "close") == 0 || strcmp(change_str, "new") == 0) {
|
if (strcmp(change_str, "close") == 0 || strcmp(change_str, "new") == 0) {
|
||||||
|
@ -423,13 +413,15 @@ handle_window_event(struct private *m, const struct json_object *json)
|
||||||
|
|
||||||
ws->window.title = ws->window.application = NULL;
|
ws->window.title = ws->window.application = NULL;
|
||||||
ws->window.pid = -1;
|
ws->window.pid = -1;
|
||||||
|
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const struct json_object *container = json_object_object_get(json, "container");
|
const struct json_object *container = json_object_object_get(json, "container");
|
||||||
if (container == NULL || !json_object_is_type(container, json_type_object)) {
|
if (container == NULL || !json_object_is_type(container, json_type_object)) {
|
||||||
LOG_ERR("'window' event (%s) did not contain a 'container' object", change_str);
|
LOG_ERR("'window' event (%s) did not contain a 'container' object", change_str);
|
||||||
return false;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct json_object *name = json_object_object_get(container, "name");
|
struct json_object *name = json_object_object_get(container, "name");
|
||||||
|
@ -437,7 +429,7 @@ handle_window_event(struct private *m, const struct json_object *json)
|
||||||
LOG_ERR(
|
LOG_ERR(
|
||||||
"'window' event (%s) did not contain a 'container.name' string value",
|
"'window' event (%s) did not contain a 'container.name' string value",
|
||||||
change_str);
|
change_str);
|
||||||
return false;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
free(ws->window.title);
|
free(ws->window.title);
|
||||||
|
@ -448,7 +440,7 @@ handle_window_event(struct private *m, const struct json_object *json)
|
||||||
LOG_ERR(
|
LOG_ERR(
|
||||||
"'window' event (%s) did not contain a 'container.pid' integer value",
|
"'window' event (%s) did not contain a 'container.pid' integer value",
|
||||||
change_str);
|
change_str);
|
||||||
return false;
|
goto err;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If PID has changed, update application name from /proc/<pid>/comm */
|
/* If PID has changed, update application name from /proc/<pid>/comm */
|
||||||
|
@ -464,6 +456,8 @@ handle_window_event(struct private *m, const struct json_object *json)
|
||||||
/* Application may simply have terminated */
|
/* Application may simply have terminated */
|
||||||
free(ws->window.application); ws->window.application = NULL;
|
free(ws->window.application); ws->window.application = NULL;
|
||||||
ws->window.pid = -1;
|
ws->window.pid = -1;
|
||||||
|
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,88 +470,31 @@ handle_window_event(struct private *m, const struct json_object *json)
|
||||||
close(fd);
|
close(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
err:
|
||||||
|
mtx_unlock(&mod->lock);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(ENABLE_X11)
|
static void
|
||||||
static bool
|
burst_done(void *_mod)
|
||||||
get_socket_address_x11(struct sockaddr_un *addr)
|
|
||||||
{
|
{
|
||||||
int default_screen;
|
struct module *mod = _mod;
|
||||||
xcb_connection_t *conn = xcb_connect(NULL, &default_screen);
|
struct private *m = mod->private;
|
||||||
if (xcb_connection_has_error(conn) > 0) {
|
|
||||||
LOG_ERR("failed to connect to X");
|
if (m->dirty) {
|
||||||
xcb_disconnect(conn);
|
m->dirty = false;
|
||||||
return false;
|
mod->bar->refresh(mod->bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
xcb_screen_t *screen = xcb_aux_get_screen(conn, default_screen);
|
|
||||||
|
|
||||||
xcb_atom_t atom = get_atom(conn, "I3_SOCKET_PATH");
|
|
||||||
assert(atom != XCB_ATOM_NONE);
|
|
||||||
|
|
||||||
xcb_get_property_cookie_t cookie
|
|
||||||
= xcb_get_property_unchecked(
|
|
||||||
conn, false, screen->root, atom,
|
|
||||||
XCB_GET_PROPERTY_TYPE_ANY, 0, sizeof(addr->sun_path));
|
|
||||||
|
|
||||||
xcb_generic_error_t *err;
|
|
||||||
xcb_get_property_reply_t *reply =
|
|
||||||
xcb_get_property_reply(conn, cookie, &err);
|
|
||||||
|
|
||||||
if (err != NULL) {
|
|
||||||
LOG_ERR("failed to get i3 socket path: %s", xcb_error(err));
|
|
||||||
free(err);
|
|
||||||
free(reply);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int len = xcb_get_property_value_length(reply);
|
|
||||||
assert(len < sizeof(addr->sun_path));
|
|
||||||
|
|
||||||
if (len == 0) {
|
|
||||||
LOG_ERR("failed to get i3 socket path: empty reply");
|
|
||||||
free(reply);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memcpy(addr->sun_path, xcb_get_property_value(reply), len);
|
|
||||||
addr->sun_path[len] = '\0';
|
|
||||||
|
|
||||||
free(reply);
|
|
||||||
xcb_disconnect(conn);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static bool
|
|
||||||
get_socket_address(struct sockaddr_un *addr)
|
|
||||||
{
|
|
||||||
*addr = (struct sockaddr_un){.sun_family = AF_UNIX};
|
|
||||||
|
|
||||||
const char *sway_sock = getenv("SWAYSOCK");
|
|
||||||
if (sway_sock == NULL) {
|
|
||||||
sway_sock = getenv("I3SOCK");
|
|
||||||
if (sway_sock == NULL) {
|
|
||||||
#if defined(ENABLE_X11)
|
|
||||||
return get_socket_address_x11(addr);
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
strncpy(addr->sun_path, sway_sock, sizeof(addr->sun_path) - 1);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
run(struct module *mod)
|
run(struct module *mod)
|
||||||
{
|
{
|
||||||
struct private *m = mod->private;
|
|
||||||
|
|
||||||
struct sockaddr_un addr;
|
struct sockaddr_un addr;
|
||||||
if (!get_socket_address(&addr))
|
if (!i3_get_socket_address(&addr))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
int sock = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||||
|
@ -573,154 +510,22 @@ run(struct module *mod)
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_VERSION, NULL);
|
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_VERSION, NULL);
|
||||||
send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\", \"window\"]");
|
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\", \"window\"]");
|
||||||
send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
i3_send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL);
|
||||||
|
|
||||||
/* Initial reply typically requires a couple of KB. But we often
|
static const struct i3_ipc_callbacks callbacks = {
|
||||||
* need more later. For example, switching workspaces can result
|
.burst_done = &burst_done,
|
||||||
* in quite big notification messages. */
|
.reply_version = &handle_get_version_reply,
|
||||||
size_t reply_buf_size = 4096;
|
.reply_subscribe = &handle_subscribe_reply,
|
||||||
char *buf = malloc(reply_buf_size);
|
.reply_workspaces = &handle_get_workspaces_reply,
|
||||||
size_t buf_idx = 0;
|
.event_workspace = &handle_workspace_event,
|
||||||
|
.event_window = &handle_window_event,
|
||||||
|
};
|
||||||
|
|
||||||
while (true) {
|
bool ret = i3_receive_loop(mod->abort_fd, sock, &callbacks, mod);
|
||||||
struct pollfd fds[] = {
|
|
||||||
{.fd = mod->abort_fd, .events = POLLIN},
|
|
||||||
{.fd = sock, .events = POLLIN}
|
|
||||||
};
|
|
||||||
|
|
||||||
int res = poll(fds, 2, -1);
|
|
||||||
if (res <= 0) {
|
|
||||||
LOG_ERRNO("failed to poll()");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fds[0].revents & POLLIN)
|
|
||||||
break;
|
|
||||||
|
|
||||||
assert(fds[1].revents & POLLIN);
|
|
||||||
|
|
||||||
/* Grow receive buffer, if necessary */
|
|
||||||
if (buf_idx == reply_buf_size) {
|
|
||||||
LOG_DBG("growing reply buffer: %zu -> %zu",
|
|
||||||
reply_buf_size, reply_buf_size * 2);
|
|
||||||
|
|
||||||
char *new_buf = realloc(buf, reply_buf_size * 2);
|
|
||||||
if (new_buf == NULL) {
|
|
||||||
LOG_ERR("failed to grow reply buffer from %zu to %zu bytes",
|
|
||||||
reply_buf_size, reply_buf_size * 2);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf = new_buf;
|
|
||||||
reply_buf_size *= 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(reply_buf_size > buf_idx);
|
|
||||||
|
|
||||||
ssize_t bytes = read(sock, &buf[buf_idx], reply_buf_size - buf_idx);
|
|
||||||
if (bytes < 0) {
|
|
||||||
LOG_ERRNO("failed to read from i3's socket");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
buf_idx += bytes;
|
|
||||||
|
|
||||||
bool err = false;
|
|
||||||
bool need_bar_refresh = false;
|
|
||||||
|
|
||||||
while (!err && buf_idx >= sizeof(i3_ipc_header_t)) {
|
|
||||||
const i3_ipc_header_t *hdr = (const i3_ipc_header_t *)buf;
|
|
||||||
if (strncmp(hdr->magic, I3_IPC_MAGIC, sizeof(hdr->magic)) != 0) {
|
|
||||||
LOG_ERR(
|
|
||||||
"i3 IPC header magic mismatch: expected \"%.*s\", got \"%.*s\"",
|
|
||||||
(int)sizeof(hdr->magic), I3_IPC_MAGIC,
|
|
||||||
(int)sizeof(hdr->magic), hdr->magic);
|
|
||||||
|
|
||||||
err = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t total_size = sizeof(i3_ipc_header_t) + hdr->size;
|
|
||||||
|
|
||||||
if (total_size > buf_idx) {
|
|
||||||
LOG_DBG("got %zd bytes, need %zu", bytes, total_size);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Json-c expects a NULL-terminated string */
|
|
||||||
char json_str[hdr->size + 1];
|
|
||||||
memcpy(json_str, &buf[sizeof(*hdr)], hdr->size);
|
|
||||||
json_str[hdr->size] = '\0';
|
|
||||||
//printf("raw: %s\n", json_str);
|
|
||||||
LOG_DBG("raw: %s\n", json_str);
|
|
||||||
|
|
||||||
json_tokener *tokener = json_tokener_new();
|
|
||||||
struct json_object *json = json_tokener_parse(json_str);
|
|
||||||
if (json == NULL) {
|
|
||||||
LOG_ERR("failed to parse json");
|
|
||||||
err = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
mtx_lock(&mod->lock);
|
|
||||||
|
|
||||||
switch (hdr->type) {
|
|
||||||
case I3_IPC_REPLY_TYPE_VERSION:
|
|
||||||
handle_get_version_reply(m, json);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case I3_IPC_REPLY_TYPE_SUBSCRIBE:
|
|
||||||
handle_subscribe_reply(m, json);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case I3_IPC_REPLY_TYPE_WORKSPACES:
|
|
||||||
handle_get_workspaces_reply(m, json);
|
|
||||||
need_bar_refresh = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case I3_IPC_EVENT_WORKSPACE:
|
|
||||||
handle_workspace_event(m, json);
|
|
||||||
need_bar_refresh = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case I3_IPC_EVENT_WINDOW:
|
|
||||||
handle_window_event(m, json);
|
|
||||||
need_bar_refresh = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case I3_IPC_EVENT_OUTPUT:
|
|
||||||
case I3_IPC_EVENT_MODE:
|
|
||||||
case I3_IPC_EVENT_BARCONFIG_UPDATE:
|
|
||||||
case I3_IPC_EVENT_BINDING:
|
|
||||||
case I3_IPC_EVENT_SHUTDOWN:
|
|
||||||
case I3_IPC_EVENT_TICK:
|
|
||||||
break;
|
|
||||||
|
|
||||||
default: assert(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
mtx_unlock(&mod->lock);
|
|
||||||
|
|
||||||
json_object_put(json);
|
|
||||||
json_tokener_free(tokener);
|
|
||||||
|
|
||||||
assert(total_size <= buf_idx);
|
|
||||||
memmove(buf, &buf[total_size], buf_idx - total_size);
|
|
||||||
buf_idx -= total_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err)
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (need_bar_refresh)
|
|
||||||
mod->bar->refresh(mod->bar);
|
|
||||||
}
|
|
||||||
|
|
||||||
free(buf);
|
|
||||||
close(sock);
|
close(sock);
|
||||||
return 0;
|
return ret ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|
Loading…
Add table
Reference in a new issue