diff --git a/common/pango.c b/common/pango.c index fc3d06886..4768d18ab 100644 --- a/common/pango.c +++ b/common/pango.c @@ -136,3 +136,37 @@ void pango_printf(cairo_t *cairo, const char *font, g_object_unref(layout); free(buf); } + +void pango_printf_ellipsize(cairo_t *cairo, + const char *font, + double scale, + bool markup, + double width, + const char *fmt, + ...) { + va_list args; + va_start(args, fmt); + // Add one since vsnprintf excludes null terminator. + int length = vsnprintf(NULL, 0, fmt, args) + 1; + va_end(args); + + char *buf = malloc(length); + if (buf == NULL) { + sway_log(SWAY_ERROR, "Failed to allocate memory"); + return; + } + va_start(args, fmt); + vsnprintf(buf, length, fmt, args); + va_end(args); + PangoLayout *layout = get_pango_layout(cairo, font, buf, scale, markup); + pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END); + pango_layout_set_width(layout, width * PANGO_SCALE); + cairo_font_options_t *fo = cairo_font_options_create(); + cairo_get_font_options(cairo, fo); + pango_cairo_context_set_font_options(pango_layout_get_context(layout), fo); + cairo_font_options_destroy(fo); + pango_cairo_update_layout(cairo, layout); + pango_cairo_show_layout(cairo, layout); + g_object_unref(layout); + free(buf); +} diff --git a/include/pango.h b/include/pango.h index 6ab83c167..6634df143 100644 --- a/include/pango.h +++ b/include/pango.h @@ -19,5 +19,12 @@ void get_text_size(cairo_t *cairo, const char *font, int *width, int *height, int *baseline, double scale, bool markup, const char *fmt, ...); void pango_printf(cairo_t *cairo, const char *font, double scale, bool markup, const char *fmt, ...); +void pango_printf_ellipsize(cairo_t *cairo, + const char *font, + double scale, + bool markup, + double width, + const char *fmt, + ...); #endif diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h index 545a66a8b..449987423 100644 --- a/include/swaybar/bar.h +++ b/include/swaybar/bar.h @@ -13,6 +13,7 @@ struct swaybar_output; struct swaybar_tray; #endif struct swaybar_workspace; +struct swaybar_window; struct loop; struct swaybar { @@ -44,6 +45,11 @@ struct swaybar { struct wl_list unused_outputs; // swaybar_output::link struct wl_list seats; // swaybar_seat::link + struct swaybar_window *focused_window; + + // TOOD: Better name + bool workspace_changed; + #if HAVE_TRAY struct swaybar_tray *tray; #endif @@ -89,6 +95,12 @@ struct swaybar_workspace { bool urgent; }; +struct swaybar_window { + char *name; + char *icon_name; + cairo_surface_t *icon; +}; + bool bar_setup(struct swaybar *bar, const char *socket_path); void bar_run(struct swaybar *bar); void bar_teardown(struct swaybar *bar); @@ -109,6 +121,7 @@ void set_bar_dirty(struct swaybar *bar); */ bool determine_bar_visibility(struct swaybar *bar, bool moving_layer); void free_workspaces(struct wl_list *list); +void free_window(struct swaybar_window *window); void status_in(int fd, short mask, void *data); diff --git a/include/swaybar/ipc.h b/include/swaybar/ipc.h index d8cd0c761..e803f48a9 100644 --- a/include/swaybar/ipc.h +++ b/include/swaybar/ipc.h @@ -6,6 +6,7 @@ bool ipc_initialize(struct swaybar *bar); bool handle_ipc_readable(struct swaybar *bar); bool ipc_get_workspaces(struct swaybar *bar); +bool ipc_set_focused_window(struct swaybar *bar); void ipc_send_workspace_command(struct swaybar *bar, const char *ws); void ipc_execute_binding(struct swaybar *bar, struct swaybar_binding *bind); diff --git a/include/swaybar/tray/icon.h b/include/swaybar/tray/icon.h index 3673674b9..51f6aecdc 100644 --- a/include/swaybar/tray/icon.h +++ b/include/swaybar/tray/icon.h @@ -27,10 +27,12 @@ struct icon_theme { char *dir; list_t *subdirs; // struct icon_theme_subdir * }; - +list_t *get_basedirs(void); void init_themes(list_t **themes, list_t **basedirs); void finish_themes(list_t *themes, list_t *basedirs); +char *append_path_safe(const char *base_path, const char *append_path); + /* * Finds an icon of a specified size given a list of themes and base directories. * If the icon is found, the pointers min_size & max_size are set to minimum & diff --git a/sway/desktop/xdg_shell.c b/sway/desktop/xdg_shell.c index 843ff90a0..d34654fd4 100644 --- a/sway/desktop/xdg_shell.c +++ b/sway/desktop/xdg_shell.c @@ -358,7 +358,8 @@ static void handle_request_fullscreen(struct wl_listener *listener, void *data) if (e->fullscreen && e->output && e->output->data) { struct sway_output *output = e->output->data; struct sway_workspace *ws = output_get_active_workspace(output); - if (ws && !container_is_scratchpad_hidden(container)) { + if (ws && !container_is_scratchpad_hidden(container) && + container->pending.workspace != ws) { if (container_is_floating(container)) { workspace_add_floating(ws, container); } else { diff --git a/swaybar/bar.c b/swaybar/bar.c index 74c1924f1..c94949080 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -40,6 +40,17 @@ void free_workspaces(struct wl_list *list) { } } +void free_window(struct swaybar_window *window) { + if (window->icon_name) { + free(window->icon_name); + } + if (window->icon) { + cairo_surface_destroy(window->icon); + } + free(window->name); + free(window); +} + static void swaybar_output_free(struct swaybar_output *output) { if (!output) { return; @@ -455,6 +466,7 @@ bool bar_setup(struct swaybar *bar, const char *socket_path) { if (bar->config->workspace_buttons) { ipc_get_workspaces(bar); } + ipc_set_focused_window(bar); determine_bar_visibility(bar, false); return true; } diff --git a/swaybar/desktop.c b/swaybar/desktop.c new file mode 100644 index 000000000..e0631ab2d --- /dev/null +++ b/swaybar/desktop.c @@ -0,0 +1,96 @@ +#define _POSIX_C_SOURCE 200809 +#include +#include +#include +#include +#include +#include +#include + +#include "swaybar/tray/icon.h" +#include "desktop.h" +#include "log.h" + +static list_t *get_desktop_files_basedirs() { + list_t *basedirs = create_list(); + // TODO: Get correct list of directories + list_add(basedirs, "/usr/share/applications"); + return basedirs; +} + +static char *load_desktop_entry(const char *app_name, list_t *basedirs) { + assert(app_name); + assert(basedirs); + + size_t desktop_filename_len = snprintf(NULL, 0, "%s.desktop", app_name) + 1; + char *desktop_filename = malloc(desktop_filename_len); + snprintf(desktop_filename, desktop_filename_len, "%s.desktop", app_name); + + for (int i = 0; i < basedirs->length; ++i) { + const char *basedir = basedirs->items[i]; + + DIR *d; + struct dirent *dir; + d = opendir(basedir); + if (d) { + while ((dir = readdir(d)) != NULL) { + if (strcmp(desktop_filename, dir->d_name) == 0) { + char *buf = append_path_safe(basedir, desktop_filename); + + FILE *f = fopen(buf, "rb"); + assert(f); + fseek(f, 0, SEEK_END); + long fsize = ftell(f); + fseek(f, 0, SEEK_SET); /* same as rewind(f); */ + + char *string = malloc(fsize + 1); + fread(string, 1, fsize, f); + fclose(f); + + string[fsize] = 0; + closedir(d); + + return string; + } + } + closedir(d); + } + } + + return NULL; +} + +char *load_desktop_entry_from_xdgdirs(const char *app_name) { + list_t *basedirs = get_desktop_files_basedirs(); + return load_desktop_entry(app_name, basedirs); +} + +char *get_icon_name_from_desktop_entry(const char *desktop_entry) { + if (!desktop_entry) { + return NULL; + } + + char *desktop_entry_start = strdup(desktop_entry); + char *cur_line = desktop_entry_start; + char *icon_name = NULL; + while (cur_line) { + char *next_line = strchr(cur_line, '\n'); + if (next_line) { + *next_line = 0; + } + + if (strncmp(cur_line, "Icon=", 5) == 0) { + const char *icon_name_start = strchr(cur_line, '=') + 1; + icon_name = strdup(icon_name_start); + break; + } + + if (next_line) { + *next_line = '\n'; + } + cur_line = next_line ? (next_line + 1) : NULL; + } + free(desktop_entry_start); + + return icon_name; +} diff --git a/swaybar/desktop.h b/swaybar/desktop.h new file mode 100644 index 000000000..4eaac1d4a --- /dev/null +++ b/swaybar/desktop.h @@ -0,0 +1,8 @@ +#ifndef _SWAYBAR_DESKTOP_H +#define _SWAYBAR_DESKTOP_H + +char *load_desktop_entry_from_xdgdirs(const char *app_name); + +char *get_icon_name_from_desktop_entry(const char *desktop_entry); + +#endif diff --git a/swaybar/ipc.c b/swaybar/ipc.c index a64aa1abf..275cc2c45 100644 --- a/swaybar/ipc.c +++ b/swaybar/ipc.c @@ -5,14 +5,18 @@ #include #include #include +#include #include "swaybar/config.h" #include "swaybar/ipc.h" #include "swaybar/status_line.h" #if HAVE_TRAY #include "swaybar/tray/tray.h" #endif +#include "desktop.h" #include "config.h" +#include "swaybar/tray/icon.h" #include "ipc-client.h" +#include "background-image.h" #include "list.h" #include "log.h" #include "loop.h" @@ -427,7 +431,7 @@ bool ipc_initialize(struct swaybar *bar) { struct swaybar_config *config = bar->config; char subscribe[128]; // suitably large buffer len = snprintf(subscribe, 128, - "[ \"barconfig_update\" , \"bar_state_update\" %s %s ]", + "[ \"barconfig_update\" , \"bar_state_update\", \"window\" %s %s ]", config->binding_mode_indicator ? ", \"mode\"" : "", config->workspace_buttons ? ", \"workspace\"" : ""); free(ipc_single_command(bar->ipc_event_socketfd, @@ -541,6 +545,112 @@ static bool handle_barconfig_update(struct swaybar *bar, const char *payload, return true; } +static const char *get_app_name_from_node(json_object *json_node) { + assert(json_node); + + json_object *json_app_id; + json_object_object_get_ex(json_node, "app_id", &json_app_id); + if (!json_app_id) { + // If no app_id is found, take the instance property from window_properties + json_object *json_window_properties; + json_object_object_get_ex( + json_node, "window_properties", &json_window_properties); + assert(json_window_properties); + + json_object *json_instance; + json_object_object_get_ex( + json_window_properties, "instance", &json_instance); + assert(json_instance); + + json_app_id = json_instance; + } + + const char *app_id = json_object_get_string(json_app_id); + assert(app_id); + + return app_id; +} + +static struct swaybar_window *get_focused_window_from_nodes( + json_object *json_nodes) { + assert(json_nodes); + + size_t json_nodes_length = json_object_array_length(json_nodes); + for (size_t i = 0; i < json_nodes_length; ++i) { + json_object *json_node; + json_node = json_object_array_get_idx(json_nodes, i); + struct json_object *json_focused; + json_object_object_get_ex(json_node, "focused", &json_focused); + assert(json_focused); + bool focused = json_object_get_boolean(json_focused); + struct json_object *json_type; + json_object_object_get_ex(json_node, "type", &json_type); + assert(json_type); + const char* type = json_object_get_string(json_type); + assert(type); + + if (focused && ((strcmp(type, "con") == 0) || + (strcmp(type, "floating_con") == 0))) { + json_object *json_name; + json_object_object_get_ex(json_node, "name", &json_name); + assert(json_name); + const char *name = json_object_get_string(json_name); + assert(name); + + const char *app_name = get_app_name_from_node(json_node); + char *desktop_entry = load_desktop_entry_from_xdgdirs(app_name); + char *icon_name = get_icon_name_from_desktop_entry(desktop_entry); + free(desktop_entry); + + struct swaybar_window *window = + calloc(1, sizeof(struct swaybar_window)); + window->name = strdup(name); + window->icon_name = icon_name; + + return window; + } + + struct json_object *json_inner_nodes; + json_object_object_get_ex(json_node, "nodes", &json_inner_nodes); + if (json_nodes) { + struct swaybar_window *window = + get_focused_window_from_nodes(json_inner_nodes); + if (window) { + return window; + } + } + } + + return NULL; +} + +bool ipc_set_focused_window(struct swaybar *bar) { + uint32_t len = 0; + char *res = ipc_single_command(bar->ipc_socketfd, + IPC_GET_TREE, NULL, &len); + json_object *results = json_tokener_parse(res); + if (!results) { + free(res); + return false; + } + + json_object *json_nodes; + json_object_object_get_ex(results, "nodes", &json_nodes); + assert(json_nodes); + struct swaybar_window *window = get_focused_window_from_nodes(json_nodes); + if (bar->focused_window) { + free_window(bar->focused_window); + bar->focused_window = NULL; + } + if (window) { + bar->focused_window = window; + } + + bar->workspace_changed = false; + // TODO: cache + return true; +} + bool handle_ipc_readable(struct swaybar *bar) { struct ipc_response *resp = ipc_recv_response(bar->ipc_event_socketfd); if (!resp) { @@ -571,7 +681,10 @@ bool handle_ipc_readable(struct swaybar *bar) { bool bar_is_dirty = true; switch (resp->type) { case IPC_EVENT_WORKSPACE: + bar->workspace_changed = true; bar_is_dirty = ipc_get_workspaces(bar); + const bool focused_window_change = ipc_set_focused_window(bar); + bar_is_dirty = bar_is_dirty ? true : focused_window_change; break; case IPC_EVENT_MODE: { json_object *json_change, *json_pango_markup; @@ -598,6 +711,9 @@ bool handle_ipc_readable(struct swaybar *bar) { case IPC_EVENT_BAR_STATE_UPDATE: bar_is_dirty = handle_bar_state_update(bar, result); break; + case IPC_EVENT_WINDOW: + bar_is_dirty = ipc_set_focused_window(bar); + break; default: bar_is_dirty = false; break; diff --git a/swaybar/meson.build b/swaybar/meson.build index 9feb3cd2d..ad6b6ffb9 100644 --- a/swaybar/meson.build +++ b/swaybar/meson.build @@ -32,6 +32,7 @@ executable( 'main.c', 'render.c', 'status_line.c', + 'desktop.c', tray_files ], include_directories: [sway_inc], diff --git a/swaybar/render.c b/swaybar/render.c index ebe127a58..666f8cac9 100644 --- a/swaybar/render.c +++ b/swaybar/render.c @@ -14,6 +14,9 @@ #include "swaybar/ipc.h" #include "swaybar/render.h" #include "swaybar/status_line.h" +#include "swaybar/tray/icon.h" +#include "log.h" +#include "background-image.h" #if HAVE_TRAY #include "swaybar/tray/tray.h" #endif @@ -121,6 +124,51 @@ static uint32_t render_status_line_text(struct render_context *ctx, double *x) { return output->height; } +static uint32_t render_focused_window_title(struct render_context *ctx, double *x, double max_width) { + struct swaybar_output *output = ctx->output; + const char *text = output->bar->focused_window ? output->bar->focused_window->name : ""; + if (!text) { + return 0; + } + + cairo_t *cairo = ctx->cairo; + struct swaybar_config *config = output->bar->config; + uint32_t fontcolor = output->focused ? + config->colors.focused_statusline : config->colors.statusline; + cairo_set_source_u32(cairo, fontcolor); + + int text_width, text_height; + get_text_size(cairo, + config->font, + &text_width, + &text_height, + NULL, + output->scale, + config->pango_markup, + "%s", + text); + + double ws_vertical_padding = config->status_padding * output->scale; + int margin = 6 * output->scale; + + uint32_t ideal_height = text_height + ws_vertical_padding * 2; + uint32_t ideal_surface_height = ideal_height / output->scale; + if (!output->bar->config->height && + output->height < ideal_surface_height) { + return ideal_surface_height; + } + + /* *x += margin; */ + uint32_t height = output->height * output->scale; + double text_y = height / 2.0 - text_height / 2.0; + cairo_move_to(cairo, *x, (int)floor(text_y)); + choose_text_aa_mode(ctx, fontcolor); + pango_printf_ellipsize(cairo, config->font, output->scale, + config->pango_markup, max_width, "%s", text); + *x += margin; + return output->height; +} + static void render_sharp_rectangle(cairo_t *cairo, uint32_t color, double x, double y, double width, double height) { cairo_save(cairo); @@ -539,7 +587,7 @@ static uint32_t render_status_line(struct render_context *ctx, double *x) { } static uint32_t render_binding_mode_indicator(struct render_context *ctx, - double x) { + double *x) { struct swaybar_output *output = ctx->output; const char *mode = output->bar->mode; if (!mode) { @@ -573,25 +621,28 @@ static uint32_t render_binding_mode_indicator(struct render_context *ctx, cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE); cairo_set_source_u32(cairo, config->colors.binding_mode.background); ctx->background_color = config->colors.binding_mode.background; - cairo_rectangle(cairo, x, 0, width, height); + cairo_rectangle(cairo, *x, 0, width, height); cairo_fill(cairo); cairo_set_source_u32(cairo, config->colors.binding_mode.border); - cairo_rectangle(cairo, x, 0, width, border_width); + cairo_rectangle(cairo, *x, 0, width, border_width); cairo_fill(cairo); - cairo_rectangle(cairo, x, 0, border_width, height); + cairo_rectangle(cairo, *x, 0, border_width, height); cairo_fill(cairo); - cairo_rectangle(cairo, x + width - border_width, 0, border_width, height); + cairo_rectangle(cairo, *x + width - border_width, 0, border_width, height); cairo_fill(cairo); - cairo_rectangle(cairo, x, height - border_width, width, border_width); + cairo_rectangle(cairo, *x, height - border_width, width, border_width); cairo_fill(cairo); double text_y = height / 2.0 - text_height / 2.0; cairo_set_source_u32(cairo, config->colors.binding_mode.text); - cairo_move_to(cairo, x + width / 2 - text_width / 2, (int)floor(text_y)); + cairo_move_to(cairo, *x + width / 2 - text_width / 2, (int)floor(text_y)); choose_text_aa_mode(ctx, config->colors.binding_mode.text); pango_printf(cairo, config->font, output->scale, output->bar->mode_pango_markup, "%s", mode); + + *x += width; + return output->height; } @@ -681,6 +732,76 @@ static uint32_t render_workspace_button(struct render_context *ctx, return output->height; } +uint32_t render_focused_window_icon(cairo_t *cairo, + struct swaybar_output *output, + double *x) { + assert(output); + assert(output->bar); + + if (!output->bar->focused_window) { + return output->height; + } + + uint32_t height = output->height * output->scale; + int padding = 4; + int target_size = height - 2 * padding; + + char *icon_name = output->bar->focused_window->icon_name; + cairo_surface_t *icon = NULL; + if (icon_name) { + char *icon_theme = output->bar->config->icon_theme; + list_t *basedirs = get_basedirs(); + int min_size = 0; + int max_size = 0; + + list_t *themes = create_list(); + // TODO: Load correct theme + list_add(themes, "Adwaita"); + + assert(output->bar->tray); + assert(output->bar->tray->themes); + char *icon_path = find_icon(output->bar->tray->themes, + basedirs, + icon_name, + target_size, + icon_theme, + &min_size, + &max_size); + icon = load_background_image(icon_path); + } else { + // TODO: Generate a image on the fly + icon = load_background_image( + "/usr/share/icons/Adwaita/16x16/apps/" + "utilities-terminal-symbolic.symbolic.png"); + } + assert(icon); + if (!icon) { + return output->height; + } + + int icon_size; + int actual_size = cairo_image_surface_get_height(icon); + icon_size = actual_size < target_size ? + actual_size*(target_size/actual_size) : target_size; + icon = cairo_image_surface_scale(icon, icon_size, icon_size); + + int padded_size = icon_size + 2 * padding; + int y = floor((height - padded_size) / 2.0); + + cairo_operator_t op = cairo_get_operator(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cairo, icon, *x + padding, y + padding); + cairo_rectangle(cairo, *x, y, padded_size, padded_size); + cairo_fill(cairo); + cairo_set_operator(cairo, op); + + *x += padded_size; + + cairo_surface_destroy(icon); + + return output->height; +} + static uint32_t render_to_cairo(struct render_context *ctx) { cairo_t *cairo = ctx->cairo; struct swaybar_output *output = ctx->output; @@ -717,6 +838,8 @@ static uint32_t render_to_cairo(struct render_context *ctx) { uint32_t h = render_status_line(ctx, &x); max_height = h > max_height ? h : max_height; } + + double old_x = x; x = 0; if (config->workspace_buttons) { struct swaybar_workspace *ws; @@ -726,7 +849,15 @@ static uint32_t render_to_cairo(struct render_context *ctx) { } } if (config->binding_mode_indicator) { - uint32_t h = render_binding_mode_indicator(ctx, x); + uint32_t h = render_binding_mode_indicator(ctx, &x); + max_height = h > max_height ? h : max_height; + } + + if (!bar->workspace_changed && output->focused) { + uint32_t h = render_focused_window_icon(cairo, output, &x); + max_height = h > max_height ? h : max_height; + old_x -= x; + h = render_focused_window_title(ctx, &x, old_x); max_height = h > max_height ? h : max_height; } diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c index c426c3d4a..a1c2969f3 100644 --- a/swaybar/tray/icon.c +++ b/swaybar/tray/icon.c @@ -1,5 +1,6 @@ #define _POSIX_C_SOURCE 200809L #include +#include #include #include #include @@ -23,7 +24,25 @@ static bool dir_exists(char *path) { return stat(path, &sb) == 0 && S_ISDIR(sb.st_mode); } -static list_t *get_basedirs(void) { +char* append_path_safe(const char * base_path, const char * append_path) { + assert(base_path); + assert(append_path); + + size_t base_path_len = strlen(base_path); + if (base_path[base_path_len - 1] == '/') { + size_t path_len = snprintf(NULL, 0, "%s%s", base_path, append_path) + 1; + char *path = malloc(path_len); + snprintf(path, path_len, "%s%s", base_path, append_path); + return path; + } + + size_t path_len = snprintf(NULL, 0, "%s/%s", base_path, append_path) + 1; + char *path = malloc(path_len); + snprintf(path, path_len, "%s/%s", base_path, append_path); + return path; +} + +list_t *get_basedirs(void) { list_t *basedirs = create_list(); list_add(basedirs, strdup("$HOME/.icons")); // deprecated @@ -40,9 +59,7 @@ static list_t *get_basedirs(void) { data_dirs = strdup(data_dirs); char *dir = strtok(data_dirs, ":"); do { - size_t path_len = snprintf(NULL, 0, "%s/icons", dir) + 1; - char *path = malloc(path_len); - snprintf(path, path_len, "%s/icons", dir); + char *path = append_path_safe(dir, "icons"); list_add(basedirs, path); } while ((dir = strtok(NULL, ":"))); free(data_dirs);