#include #include #include #include #include #include #include "dwm.h" #include "log.h" #define IPC_MAGIC "DWM-IPC" #define IPC_MAGIC_LEN 7 typedef struct { uint8_t magic[IPC_MAGIC_LEN]; uint32_t size; uint8_t type; } __attribute__((packed)) DwmIpcHeader; struct DwmIpc { GSocket *socket; GSource *source; State *state; Color color; Color text_color; Color selected; Button *tags[9]; Button *title; bool change_title; bool hidden_title; }; typedef enum { IPC_TYPE_RUN_COMMAND = 0, IPC_TYPE_GET_MONITORS = 1, IPC_TYPE_GET_TAGS = 2, IPC_TYPE_GET_LAYOUTS = 3, IPC_TYPE_GET_DWM_CLIENT = 4, IPC_TYPE_SUBSCRIBE = 5, IPC_TYPE_EVENT = 6 } DwmIpcMessage; #define IPC_EVENT_TAG_CHANGE "tag_change_event" #define IPC_EVENT_CLIENT_FOCUS_CHANGE "client_focus_change_event" #define IPC_EVENT_LAYOUT_CHANGE "layout_change_event" #define IPC_EVENT_MONITOR_FOCUS_CHANGE "monitor_focus_change_event" #define IPC_EVENT_FOCUSED_TITLE_CHANGE "focused_title_change_event" #define IPC_EVENT_FOCUSED_STATE_CHANGE "focused_state_change_event" static void ipc_send(DwmIpc *dwm, DwmIpcMessage msg_type, uint8_t *msg, uint32_t msg_size) { DwmIpcHeader header; memcpy(header.magic, IPC_MAGIC, IPC_MAGIC_LEN); header.size = msg_size; header.type = msg_type; GError *error = NULL; gssize size = 0; while (size < sizeof(DwmIpcHeader)) { gssize sent = g_socket_send(dwm->socket, (void *)&header + size, sizeof(DwmIpcHeader) - size, NULL, &error); if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { g_error_free(error); continue; } g_assert(sent != -1); size += sent; } size = 0; while (size < msg_size) { gssize sent = g_socket_send(dwm->socket, msg + size, msg_size - size, NULL, &error); if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { g_error_free(error); continue; } g_assert(sent != -1); size += sent; } log_debug("Sent ipc message [type=%d, size=%d]", msg_type, msg_size); } static void ipc_subscribe(DwmIpc *dwm, const char *event) { // { // "event": "", // "action": "subscribe" // } JsonBuilder *builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "event"); json_builder_add_string_value(builder, event); json_builder_set_member_name(builder, "action"); json_builder_add_string_value(builder, "subscribe"); json_builder_end_object(builder); JsonNode *root = json_builder_get_root(builder); JsonGenerator *gen = json_generator_new(); json_generator_set_root(gen, root); gsize msg_len = 0; gchar *msg = json_generator_to_data(gen, &msg_len); log_debug("Subscribing to dwm ipc: %s", msg); ipc_send(dwm, IPC_TYPE_SUBSCRIBE, msg, msg_len); json_node_free(root); g_object_unref(gen); g_object_unref(builder); g_free(msg); } static void ipc_run_command(DwmIpc *dwm, const char *cmd, JsonNode **args) { // { // "command": "", // "args": [ ... ] // } JsonBuilder *builder = json_builder_new(); json_builder_begin_object(builder); json_builder_set_member_name(builder, "command"); json_builder_add_string_value(builder, cmd); json_builder_set_member_name(builder, "args"); json_builder_begin_array(builder); while (*args != NULL) { json_builder_add_value(builder, *args++); } json_builder_end_array(builder); json_builder_end_object(builder); JsonNode *root = json_builder_get_root(builder); JsonGenerator *gen = json_generator_new(); json_generator_set_root(gen, root); gsize msg_len = 0; gchar *msg = json_generator_to_data(gen, &msg_len); log_debug("Sending command to dwm ipc: %s", msg); ipc_send(dwm, IPC_TYPE_RUN_COMMAND, msg, msg_len); json_node_free(root); g_object_unref(gen); g_object_unref(builder); g_free(msg); } #define JSON_FIELD(reader, member) (json_reader_end_member(reader), json_reader_read_member(reader, member)) static void ipc_handle(DwmIpc *dwm, DwmIpcMessage type, char *reply, uint32_t reply_size) { GError *error = NULL; JsonParser *parser = json_parser_new(); if (!json_parser_load_from_data(parser, reply, reply_size - 1, &error)) { log_warning("Failed to parse dwm ipc reply: %s", error->message); g_error_free(error); return; } JsonReader *reader = json_reader_new(json_parser_get_root(parser)); if (type == IPC_TYPE_EVENT) { if (json_reader_read_member(reader, IPC_EVENT_TAG_CHANGE)) { log_debug("Handling dwm ipc event [event=%s]", IPC_EVENT_TAG_CHANGE); json_reader_read_member(reader, "new_state"); json_reader_read_member(reader, "selected"); long selected = json_reader_get_int_value(reader); json_reader_end_member(reader); json_reader_end_member(reader); json_reader_end_member(reader); log_debug("Selected tag mask: %ld", selected); if (dwm->tags[0] != NULL) { for (int i = 0; i < 9; i++) dwm->tags[i]->color = selected & (1 << i) ? dwm->selected : dwm->color; state_redraw(dwm->state, false); } } else if (JSON_FIELD(reader, IPC_EVENT_CLIENT_FOCUS_CHANGE)) { log_debug("Handling dwm ipc event [event=%s]", IPC_EVENT_CLIENT_FOCUS_CHANGE); json_reader_read_member(reader, "new_win_id"); long win_id = json_reader_get_int_value(reader); json_reader_end_member(reader); json_reader_end_member(reader); char *msg = g_strdup_printf("{ \"client_window_id\": %ld }\n", win_id); dwm->change_title = true; ipc_send(dwm, IPC_TYPE_GET_DWM_CLIENT, msg, strlen(msg)); g_free(msg); } else if (JSON_FIELD(reader, IPC_EVENT_LAYOUT_CHANGE)) { log_debug("Ignoring dwm ipc event [event=%s]", IPC_EVENT_LAYOUT_CHANGE); // TODO } else if (JSON_FIELD(reader, IPC_EVENT_MONITOR_FOCUS_CHANGE)) { log_debug("Ignoring dwm ipc event [event=%s]", IPC_EVENT_MONITOR_FOCUS_CHANGE); // TODO } else if (JSON_FIELD(reader, IPC_EVENT_FOCUSED_TITLE_CHANGE)) { log_debug("Ignoring dwm ipc event [event=%s]", IPC_EVENT_FOCUSED_TITLE_CHANGE); // TODO } else if (JSON_FIELD(reader, IPC_EVENT_FOCUSED_STATE_CHANGE)) { log_debug("Ignoring dwm ipc event [event=%s]", IPC_EVENT_FOCUSED_STATE_CHANGE); // TODO } else { const char *event = "?"; if (json_reader_read_element(reader, 0)) event = json_reader_get_string_value(reader); log_warning("Unrecognized dwm ipc event: %s", event); } } else if (type == IPC_TYPE_GET_DWM_CLIENT) { log_debug("Received dwm client information message [type=%d]", type); if (dwm->change_title) { json_reader_read_member(reader, "name"); const char *title = json_reader_get_string_value(reader); log_debug("Changing current program title: %s", title ? title : "?"); if (title == NULL || strlen(title) == 0) { state_remove_button(dwm->state, dwm->title); dwm->hidden_title = true; } else { button_simple_set_text(dwm->title, g_strdup(title), dwm->text_color); if (dwm->hidden_title) { state_add_button(dwm->state, dwm->title); dwm->hidden_title = false; } } dwm->change_title = false; state_redraw(dwm->state, true); json_reader_end_member(reader); } } else { log_debug("Ignoring dwm ipc message [type=%d]", type); //log_debug("%s", reply); } g_object_unref(reader); g_object_unref(parser); } static gboolean socket_callback(GSocket *socket, GIOCondition condition, gpointer data) { if (condition & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) return G_SOURCE_REMOVE; GError *error = NULL; DwmIpcHeader header; gssize size = 0; while (size < sizeof(DwmIpcHeader)) { gssize read = g_socket_receive(socket, (void *)&header + size, sizeof(DwmIpcHeader) - size, NULL, &error); if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { g_error_free(error); continue; } g_assert(read != -1); size += read; } if (memcmp(&header, IPC_MAGIC, IPC_MAGIC_LEN) != 0) { log_warning("Malformed dwm ipc message"); return G_SOURCE_CONTINUE;; } uint32_t reply_size; memcpy(&reply_size, (void *)&header + IPC_MAGIC_LEN, sizeof(uint32_t)); uint8_t msg_type; memcpy(&msg_type, (void *)&header + IPC_MAGIC_LEN + sizeof(uint32_t), sizeof(uint8_t)); char *reply = g_malloc(reply_size); size = 0; while (size < reply_size) { gssize read = g_socket_receive(socket, reply + size, reply_size - size, NULL, NULL); if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_WOULD_BLOCK)) { g_error_free(error); continue; } g_assert(read != -1); size += read; } //log_debug("Received dwm ipc response: %s", reply); ipc_handle(data, msg_type, reply, reply_size); return G_SOURCE_CONTINUE; } DwmIpc *dwm_create(State *state, const char *socket) { DwmIpc *dwm = g_malloc0(sizeof(DwmIpc)); dwm->state = state; GError *error = NULL; dwm->socket = g_socket_new(G_SOCKET_FAMILY_UNIX, G_SOCKET_TYPE_STREAM, 0, &error); g_assert_nonnull(dwm->socket); GSocketAddress *addr = g_unix_socket_address_new(socket); if (!g_socket_connect(dwm->socket, addr, NULL, &error)) { g_assert_nonnull(error); log_debug("Failed to connect socket %s: %s", socket, error->message); log_info("Skipped dwm ipc [socket=%s]", socket); g_error_free(error); g_object_unref(dwm->socket); g_free(dwm); return NULL; } dwm->source = g_socket_create_source(dwm->socket, G_IO_IN, NULL); g_source_set_static_name(dwm->source, "DwmIpcSource"); g_source_set_callback(dwm->source, (GSourceFunc)socket_callback, dwm, NULL); const char *events[] = { IPC_EVENT_TAG_CHANGE, IPC_EVENT_CLIENT_FOCUS_CHANGE, IPC_EVENT_LAYOUT_CHANGE, IPC_EVENT_MONITOR_FOCUS_CHANGE, IPC_EVENT_FOCUSED_TITLE_CHANGE, IPC_EVENT_FOCUSED_STATE_CHANGE, }; for (int i = 0; i < G_N_ELEMENTS(events); ++i) ipc_subscribe(dwm, events[i]); g_source_attach(dwm->source, NULL); log_info("Attached dwm ipc source [socket=%s]", socket); return dwm; } static void change_tag_action(Button *btn) { ButtonSimple *sbtn = CAST(btn, ButtonSimple); DwmIpc *dwm = sbtn->action_data; int n = sbtn->text[0] - '0'; g_assert(n >= 1 && n <= 9); log_info("Toggled tag %d", n); JsonNode *args[] = { json_node_init_int(json_node_alloc(), 1 << (n - 1)), NULL }; ipc_run_command(dwm, "view", args); } void dwm_register_tags(DwmIpc *dwm, Color color, Color selected, Color text_color, Color line_color) { if (dwm == NULL) return; dwm->color = color; dwm->text_color = text_color; dwm->selected = selected; // Tags button for (int i = 0; i < 9; ++i) { char text[] = { '1' + i, '\0' }; dwm->tags[i] = button_simple_create(PANGO_ALIGN_LEFT, color, line_color); button_simple_set_text(dwm->tags[i], g_strdup(text), text_color); button_simple_set_action(dwm->tags[i], change_tag_action, dwm); state_add_button(dwm->state, dwm->tags[i]); } dwm->title = button_simple_create(PANGO_ALIGN_CENTER, color, line_color); dwm->hidden_title = true; } void dwm_destroy(DwmIpc *dwm) { if (dwm == NULL) return; g_source_destroy(dwm->source); g_source_unref(dwm->source); g_object_unref(dwm->socket); g_free(dwm); } // vim: ts=4 sw=4 et