diff --git a/modules/i3.c b/modules/i3.c index 7869693..14cb8b8 100644 --- a/modules/i3.c +++ b/modules/i3.c @@ -7,8 +7,11 @@ #include +#include +#include #include #include +#include #if defined(ENABLE_X11) #include @@ -45,6 +48,12 @@ struct workspace { bool visible; bool focused; bool urgent; + + struct { + char *title; + char *application; + pid_t pid; + } window; }; struct private { @@ -108,6 +117,7 @@ workspace_from_json(const struct json_object *json, struct workspace *ws) .visible = json_object_get_boolean(visible), .focused = json_object_get_boolean(focused), .urgent = json_object_get_boolean(urgent), + .window = {.title = NULL, .pid = -1}, }; return true; @@ -118,6 +128,8 @@ workspace_free(struct workspace ws) { free(ws.name); free(ws.output); + free(ws.window.title); + free(ws.window.application); } static void @@ -377,6 +389,96 @@ handle_workspace_event(struct private *m, const struct json_object *json) return true; } +static bool +handle_window_event(struct private *m, const struct json_object *json) +{ + struct workspace *ws = NULL; + size_t focused = 0; + for (size_t i = 0; i < m->workspaces.count; i++) { + if (m->workspaces.v[i].focused) { + ws = &m->workspaces.v[i]; + focused++; + } + } + + assert(focused == 1); + assert(ws != NULL); + + if (!json_object_is_type(json, json_type_object)) { + LOG_ERR("'window' event is not of type 'object'"); + return false; + } + + struct json_object *change = json_object_object_get(json, "change"); + if (change == NULL || !json_object_is_type(change, json_type_string)) { + LOG_ERR("'window' event did not contain a 'change' string value"); + return false; + } + + const char *change_str = json_object_get_string(change); + + if (strcmp(change_str, "close") == 0 || strcmp(change_str, "new") == 0) { + free(ws->window.title); + free(ws->window.application); + + ws->window.title = ws->window.application = NULL; + ws->window.pid = -1; + return true; + } + + const struct json_object *container = json_object_object_get(json, "container"); + if (container == NULL || !json_object_is_type(container, json_type_object)) { + LOG_ERR("'window' event (%s) did not contain a 'container' object", change_str); + return false; + } + + struct json_object *name = json_object_object_get(container, "name"); + if (name == NULL || !json_object_is_type(name, json_type_string)) { + LOG_ERR( + "'window' event (%s) did not contain a 'container.name' string value", + change_str); + return false; + } + + free(ws->window.title); + ws->window.title = strdup(json_object_get_string(name)); + + const struct json_object *pid_node = json_object_object_get(container, "pid"); + if (pid_node == NULL || !json_object_is_type(pid_node, json_type_int)) { + LOG_ERR( + "'window' event (%s) did not contain a 'container.pid' integer value", + change_str); + return false; + } + + /* If PID has changed, update application name from /proc//comm */ + pid_t pid = json_object_get_int(pid_node); + if (ws->window.pid != pid) { + ws->window.pid = pid; + + char path[64]; + snprintf(path, sizeof(path), "/proc/%u/comm", ws->window.pid); + + int fd = open(path, O_RDONLY); + if (fd == -1) { + /* Application may simply have terminated */ + free(ws->window.application); ws->window.application = NULL; + ws->window.pid = -1; + return true; + } + + char application[128]; + ssize_t bytes = read(fd, application, sizeof(application)); + assert(bytes >= 0); + + application[bytes - 1] = '\0'; + ws->window.application = strdup(application); + close(fd); + } + + return true; +} + #if defined(ENABLE_X11) static bool get_socket_address_x11(struct sockaddr_un *addr) @@ -435,11 +537,14 @@ get_socket_address(struct sockaddr_un *addr) 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); + return get_socket_address_x11(addr); #else - return false; + return false; #endif + } } strncpy(addr->sun_path, sway_sock, sizeof(addr->sun_path) - 1); @@ -469,8 +574,8 @@ run(struct module *mod) } send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_VERSION, NULL); + send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\", \"window\"]"); send_pkg(sock, I3_IPC_MESSAGE_TYPE_GET_WORKSPACES, NULL); - send_pkg(sock, I3_IPC_MESSAGE_TYPE_SUBSCRIBE, "[\"workspace\"]"); /* Initial reply typically requires a couple of KB. But we often * need more later. For example, switching workspaces can result @@ -580,9 +685,13 @@ run(struct module *mod) 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_WINDOW: case I3_IPC_EVENT_BARCONFIG_UPDATE: case I3_IPC_EVENT_BINDING: case I3_IPC_EVENT_SHUTDOWN: @@ -651,9 +760,10 @@ content(struct module *mod) mtx_lock(&mod->lock); - struct exposable *particles[m->workspaces.count]; - size_t particle_count = 0; + struct exposable *particles[m->workspaces.count + 1]; + struct exposable *current = NULL; + for (size_t i = 0; i < m->workspaces.count; i++) { const struct workspace *ws = &m->workspaces.v[i]; const struct ws_content *template = NULL; @@ -667,11 +777,6 @@ content(struct module *mod) template = ws_content_for_name(m, ""); } - if (template == NULL) { - LOG_WARN("no ws template for %s, and no default template available", ws->name); - continue; - } - const char *state = ws->urgent ? "urgent" : ws->visible ? ws->focused ? "focused" : "unfocused" : @@ -684,16 +789,33 @@ content(struct module *mod) tag_new_bool(mod, "focused", ws->focused), tag_new_bool(mod, "urgent", ws->urgent), tag_new_string(mod, "state", state), + + tag_new_string(mod, "application", ws->window.application), + tag_new_string(mod, "title", ws->window.title), }, - .count = 5, + .count = 7, }; - particles[particle_count++] = template->content->instantiate( - template->content, &tags); + if (ws->focused) { + const struct ws_content *cur = ws_content_for_name(m, "current"); + current = cur->content->instantiate(cur->content, &tags); + } + + if (template == NULL) { + LOG_WARN( + "no ws template for %s, and no default template available", + ws->name); + } else { + particles[particle_count++] = template->content->instantiate( + template->content, &tags); + } tag_set_destroy(&tags); } + if (current != NULL) + particles[particle_count++] = current; + mtx_unlock(&mod->lock); return dynlist_exposable_new( particles, particle_count, m->left_spacing, m->right_spacing);