diff options
58 files changed, 2929 insertions, 138 deletions
diff --git a/CMake/FindDBus.cmake b/CMake/FindDBus.cmake new file mode 100644 index 00000000..4a1a1805 --- /dev/null +++ b/CMake/FindDBus.cmake @@ -0,0 +1,59 @@ +# - Try to find DBus +# Once done, this will define +# +# DBUS_FOUND - system has DBus +# DBUS_INCLUDE_DIRS - the DBus include directories +# DBUS_LIBRARIES - link these to use DBus +# +# Copyright (C) 2012 Raphael Kubo da Costa <rakuco@webkit.org> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND ITS CONTRIBUTORS ``AS +# IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR ITS +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +# OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +# OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +# ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +FIND_PACKAGE(PkgConfig) +PKG_CHECK_MODULES(PC_DBUS QUIET dbus-1) + +FIND_LIBRARY(DBUS_LIBRARIES + NAMES dbus-1 + HINTS ${PC_DBUS_LIBDIR} + ${PC_DBUS_LIBRARY_DIRS} +) + +FIND_PATH(DBUS_INCLUDE_DIR + NAMES dbus/dbus.h + HINTS ${PC_DBUS_INCLUDEDIR} + ${PC_DBUS_INCLUDE_DIRS} +) + +GET_FILENAME_COMPONENT(_DBUS_LIBRARY_DIR ${DBUS_LIBRARIES} PATH) +FIND_PATH(DBUS_ARCH_INCLUDE_DIR + NAMES dbus/dbus-arch-deps.h + HINTS ${PC_DBUS_INCLUDEDIR} + ${PC_DBUS_INCLUDE_DIRS} + ${_DBUS_LIBRARY_DIR} + ${DBUS_INCLUDE_DIR} + PATH_SUFFIXES include +) + +SET(DBUS_INCLUDE_DIRS ${DBUS_INCLUDE_DIR} ${DBUS_ARCH_INCLUDE_DIR}) + +INCLUDE(FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS(DBUS REQUIRED_VARS DBUS_INCLUDE_DIRS DBUS_LIBRARIES) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ed45830..c80f6361 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ option(enable-swaybar "Enables the swaybar utility" YES) option(enable-swaygrab "Enables the swaygrab utility" YES) option(enable-swaymsg "Enables the swaymsg utility" YES) option(enable-gdk-pixbuf "Use Pixbuf to support more image formats" YES) +option(enable-tray "Enables the swaybar tray" YES) option(zsh-completions "Zsh shell completions" NO) option(default-wallpaper "Installs the default wallpaper" YES) option(LD_LIBRARY_PATH "Configure sway's default LD_LIBRARY_PATH") @@ -64,9 +65,13 @@ find_package(Cairo REQUIRED) find_package(Pango REQUIRED) find_package(GdkPixbuf) find_package(PAM) +find_package(DBus) find_package(LibInput REQUIRED) -find_package(Libcap REQUIRED) + +if (CMAKE_SYSTEM_NAME STREQUAL Linux) + find_package(Libcap REQUIRED) +endif (CMAKE_SYSTEM_NAME STREQUAL Linux) if (CMAKE_SYSTEM_NAME STREQUAL FreeBSD) find_package(EpollShim REQUIRED) @@ -87,6 +92,17 @@ else() message(STATUS "Building without gdk-pixbuf, only png images supported.") endif() +if (enable-tray) + if (DBUS_FOUND) + set(ENABLE_TRAY) + add_definitions(-DENABLE_TRAY) + else() + message(WARNING "Tray required but DBus was not found. Tray will not be included") + endif() +else() + message(STATUS "Building without the tray.") +endif() + include_directories(include) add_subdirectory(protocols) diff --git a/HACKING.md b/HACKING.md new file mode 100644 index 00000000..cf18dbb0 --- /dev/null +++ b/HACKING.md @@ -0,0 +1,35 @@ +## Logging + +Use `sway_log(importance, fmt, ...)` to log. The following importances are +available: + +* `L_DEBUG`: Debug messages, only shows with `sway -d` +* `L_INFO`: Informational messages +* `L_ERROR`: Error messages + +`sway_log` is a macro that calls `_sway_log` with the current filename and line +number, which are written into the log with your message. + +## Assertions + +In the compositor, assertions *must not* be fatal. All error cases must be +handled as gracefully as possible - crashing the compositor will make the user +lose all of their work. + +Use `sway_assert(condition, fmt, ...)` to perform an assertion. This returns +`condition`, which you must handle if false. An error will be logged if the +assertion fails. + +Outside of the compositor (swaymsg, swaybar, etc), using `assert.h` is +permitted. + +## Building against a local wlc + +1. Build wlc as described [here](https://github.com/Cloudef/wlc#building) +2. Inside your sway source folder, tell `cmake` to use your local version of wlc: + +```bash +cmake \ + -DWLC_LIBRARIES=path/to/wlc/target/src/libwlc.so \ + -DWLC_INCLUDE_DIRS=path/to/wlc/include . +``` @@ -8,7 +8,12 @@ irc.freenode.net). [![](https://sr.ht/ICd5.png)](https://sr.ht/ICd5.png) -[More screenshots](https://github.com/SirCmpwn/sway/wiki/Screenshots-of-Sway) +If you'd like to support Sway development, you can contribute to [my Patreon +page](https://patreon.com/sircmpwn) or you can contribute to +[bounties](https://github.com/SirCmpwn/sway/issues/986) for specific features. +Anyone is welcome to claim a bounty and you can make a bounty for any feature +you wish, and Patreon is more useful for supporting the overall health and +maintenance of Sway. ## Release Signatures @@ -23,8 +28,6 @@ and published [on GitHub](https://github.com/SirCmpwn/sway/releases). - [i3-gaps feature support](https://github.com/SirCmpwn/sway/issues/307) - [security features](https://github.com/SirCmpwn/sway/issues/984) -[Bounties](https://github.com/SirCmpwn/sway/issues/986): sponsor features or get paid to write them - ## Installation ### From Packages diff --git a/common/log.c b/common/log.c index 61937495..af1bdc3f 100644 --- a/common/log.c +++ b/common/log.c @@ -1,4 +1,4 @@ -#define _POSIX_C_SOURCE 1 +#define _POSIX_C_SOURCE 199506L #include <errno.h> #include <libgen.h> #include <signal.h> diff --git a/common/stringop.c b/common/stringop.c index 99e9636d..4a37543d 100644 --- a/common/stringop.c +++ b/common/stringop.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #include <stdlib.h> #include <stdio.h> #include <string.h> diff --git a/common/util.c b/common/util.c index a9e6a9c2..d6369853 100644 --- a/common/util.c +++ b/common/util.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h> @@ -124,38 +124,38 @@ uint32_t parse_color(const char *color) { } char* resolve_path(const char* path) { - struct stat sb; - ssize_t r; - int i; - char *current = NULL; - char *resolved = NULL; - - if(!(current = strdup(path))) { - return NULL; - } - for (i = 0; i < 16; ++i) { - if (lstat(current, &sb) == -1) { - goto failed; - } - if((sb.st_mode & S_IFMT) != S_IFLNK) { - return current; - } - if (!(resolved = malloc(sb.st_size + 1))) { - goto failed; - } - r = readlink(current, resolved, sb.st_size); - if (r == -1 || r > sb.st_size) { - goto failed; - } - resolved[r] = '\0'; - free(current); - current = strdup(resolved); - free(resolved); - resolved = NULL; - } + struct stat sb; + ssize_t r; + int i; + char *current = NULL; + char *resolved = NULL; + + if(!(current = strdup(path))) { + return NULL; + } + for (i = 0; i < 16; ++i) { + if (lstat(current, &sb) == -1) { + goto failed; + } + if((sb.st_mode & S_IFMT) != S_IFLNK) { + return current; + } + if (!(resolved = malloc(sb.st_size + 1))) { + goto failed; + } + r = readlink(current, resolved, sb.st_size); + if (r == -1 || r > sb.st_size) { + goto failed; + } + resolved[r] = '\0'; + free(current); + current = strdup(resolved); + free(resolved); + resolved = NULL; + } failed: - free(resolved); - free(current); - return NULL; -}
\ No newline at end of file + free(resolved); + free(current); + return NULL; +} @@ -41,7 +41,6 @@ output * bg __DATADIR__/sway/Sway_Wallpaper_Blue_1920x1080.png fill # } # # You can get the names of your inputs by running: swaymsg -t get_inputs -# The escape symbol "\" has to be removed. # Read `man 5 sway-input` for more information about this section. ### Key bindings diff --git a/include/client/cairo.h b/include/client/cairo.h index 46c53566..e7ef7c7e 100644 --- a/include/client/cairo.h +++ b/include/client/cairo.h @@ -6,6 +6,8 @@ void cairo_set_source_u32(cairo_t *cairo, uint32_t color); +cairo_surface_t *cairo_image_surface_scale(cairo_surface_t *image, int width, int height); + #ifdef WITH_GDK_PIXBUF #include <gdk-pixbuf/gdk-pixbuf.h> diff --git a/include/sway/commands.h b/include/sway/commands.h index 078652e7..f67df10f 100644 --- a/include/sway/commands.h +++ b/include/sway/commands.h @@ -157,17 +157,21 @@ sway_cmd cmd_workspace; sway_cmd cmd_ws_auto_back_and_forth; sway_cmd cmd_workspace_layout; +sway_cmd bar_cmd_activate_button; sway_cmd bar_cmd_binding_mode_indicator; sway_cmd bar_cmd_bindsym; sway_cmd bar_cmd_colors; +sway_cmd bar_cmd_context_button; sway_cmd bar_cmd_font; sway_cmd bar_cmd_mode; sway_cmd bar_cmd_modifier; sway_cmd bar_cmd_output; sway_cmd bar_cmd_height; sway_cmd bar_cmd_hidden_state; +sway_cmd bar_cmd_icon_theme; sway_cmd bar_cmd_id; sway_cmd bar_cmd_position; +sway_cmd bar_cmd_secondary_button; sway_cmd bar_cmd_separator_symbol; sway_cmd bar_cmd_status_command; sway_cmd bar_cmd_pango_markup; diff --git a/include/sway/config.h b/include/sway/config.h index 35f8d5f7..999a471a 100644 --- a/include/sway/config.h +++ b/include/sway/config.h @@ -133,7 +133,17 @@ struct bar_config { char *swaybar_command; char *font; int height; // -1 not defined - int tray_padding; + +#ifdef ENABLE_TRAY + // Tray + char *tray_output; + char *icon_theme; + uint32_t tray_padding; + uint32_t activate_button; + uint32_t context_button; + uint32_t secondary_button; +#endif + bool workspace_buttons; bool wrap_scroll; char *separator_symbol; diff --git a/include/swaybar/bar.h b/include/swaybar/bar.h index 697a48c2..010e1f84 100644 --- a/include/swaybar/bar.h +++ b/include/swaybar/bar.h @@ -21,6 +21,9 @@ struct output { struct window *window; struct registry *registry; list_t *workspaces; +#ifdef ENABLE_TRAY + list_t *items; +#endif char *name; int idx; bool focused; @@ -37,6 +40,9 @@ struct workspace { /** Global bar state */ extern struct bar swaybar; +/** True if sway needs to render */ +extern bool dirty; + /** * Setup bar. */ diff --git a/include/swaybar/config.h b/include/swaybar/config.h index 04b12cd4..651f0ee3 100644 --- a/include/swaybar/config.h +++ b/include/swaybar/config.h @@ -33,6 +33,17 @@ struct config { bool all_outputs; list_t *outputs; +#ifdef ENABLE_TRAY + // Tray + char *tray_output; + char *icon_theme; + + uint32_t tray_padding; + uint32_t activate_button; + uint32_t context_button; + uint32_t secondary_button; +#endif + int height; struct { diff --git a/include/swaybar/event_loop.h b/include/swaybar/event_loop.h new file mode 100644 index 00000000..a0cde07f --- /dev/null +++ b/include/swaybar/event_loop.h @@ -0,0 +1,26 @@ +#ifndef _SWAYBAR_EVENT_LOOP_H +#define _SWAYBAR_EVENT_LOOP_H + +#include <stdbool.h> +#include <time.h> + +void add_event(int fd, short mask, + void(*cb)(int fd, short mask, void *data), + void *data); + +// Not guaranteed to notify cb immediately +void add_timer(timer_t timer, + void(*cb)(timer_t timer, void *data), + void *data); + +// Returns false if nothing exists, true otherwise +bool remove_event(int fd); + +// Returns false if nothing exists, true otherwise +bool remove_timer(timer_t timer); + +// Blocks and returns after sending callbacks +void event_loop_poll(); + +void init_event_loop(); +#endif /*_SWAYBAR_EVENT_LOOP_H */ diff --git a/include/swaybar/tray/dbus.h b/include/swaybar/tray/dbus.h new file mode 100644 index 00000000..eb9cfea7 --- /dev/null +++ b/include/swaybar/tray/dbus.h @@ -0,0 +1,18 @@ +#ifndef _SWAYBAR_DBUS_H +#define _SWAYBAR_DBUS_H + +#include <stdbool.h> +#include <dbus/dbus.h> +extern DBusConnection *conn; + +/** + * Should be called in main loop to dispatch events + */ +void dispatch_dbus(); + +/** + * Initializes async dbus communication + */ +int dbus_init(); + +#endif /* _SWAYBAR_DBUS_H */ diff --git a/include/swaybar/tray/icon.h b/include/swaybar/tray/icon.h new file mode 100644 index 00000000..1cc6ff9c --- /dev/null +++ b/include/swaybar/tray/icon.h @@ -0,0 +1,16 @@ +#ifndef _SWAYBAR_ICON_H +#define _SWAYBAR_ICON_H + +#include <stdint.h> +#include <stdbool.h> +#include <client/cairo.h> + +/** + * Returns the image found by `name` that is closest to `size` + */ +cairo_surface_t *find_icon(const char *name, int size); + +/* Struct used internally only */ +struct subdir; + +#endif /* _SWAYBAR_ICON_H */ diff --git a/include/swaybar/tray/sni.h b/include/swaybar/tray/sni.h new file mode 100644 index 00000000..83809b2d --- /dev/null +++ b/include/swaybar/tray/sni.h @@ -0,0 +1,81 @@ +#ifndef _SWAYBAR_SNI_H +#define _SWAYBAR_SNI_H + +#include <stdbool.h> +#include <client/cairo.h> + +struct StatusNotifierItem { + /* Name registered to sni watcher */ + char *name; + /* Unique bus name, needed for determining signal origins */ + char *unique_name; + bool kde_special_snowflake; + + cairo_surface_t *image; + bool dirty; +}; + +/* Each output holds an sni_icon_ref of each item to render */ +struct sni_icon_ref { + cairo_surface_t *icon; + struct StatusNotifierItem *ref; +}; + +struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item, + int height); + +void sni_icon_ref_free(struct sni_icon_ref *sni_ref); + +/** + * Will return a new item and get its icon. (see warning below) + */ +struct StatusNotifierItem *sni_create(const char *name); + +/** + * `item` must be a struct StatusNotifierItem * + * `str` must be a NUL terminated char * + * + * Returns 0 if `item` has a name of `str` + */ +int sni_str_cmp(const void *item, const void *str); + +/** + * Returns 0 if `item` has a unique name of `str` or if + * `item->unique_name == NULL` + */ +int sni_uniq_cmp(const void *item, const void *str); + +/** + * Gets an icon for the given item if found. + * + * XXX + * This function keeps a reference to the item until it gets responses, make + * sure that the reference and item are valid during this time. + */ +void get_icon(struct StatusNotifierItem *item); + +/** + * Calls the "activate" method on the given StatusNotifierItem + * + * x and y should be where the item was clicked + */ +void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y); + +/** + * Asks the item to draw a context menu at the given x and y coords + */ +void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y); + +/** + * Calls the "secondary activate" method on the given StatusNotifierItem + * + * x and y should be where the item was clicked + */ +void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y); + +/** + * Deconstructs `item` + */ +void sni_free(struct StatusNotifierItem *item); + +#endif /* _SWAYBAR_SNI_H */ diff --git a/include/swaybar/tray/sni_watcher.h b/include/swaybar/tray/sni_watcher.h new file mode 100644 index 00000000..25ddfcd2 --- /dev/null +++ b/include/swaybar/tray/sni_watcher.h @@ -0,0 +1,10 @@ +#ifndef _SWAYBAR_SNI_WATCHER_H +#define _SWAYBAR_SNI_WATCHER_H + +/** + * Starts the sni_watcher, the watcher is practically a black box and should + * only be accessed though functions described in its spec + */ +int init_sni_watcher(); + +#endif /* _SWAYBAR_SNI_WATCHER_H */ diff --git a/include/swaybar/tray/tray.h b/include/swaybar/tray/tray.h new file mode 100644 index 00000000..b718e555 --- /dev/null +++ b/include/swaybar/tray/tray.h @@ -0,0 +1,32 @@ +#ifndef _SWAYBAR_TRAY_H +#define _SWAYBAR_TRAY_H + +#include <stdint.h> +#include <stdbool.h> +#include "swaybar/tray/dbus.h" +#include "swaybar/tray/sni.h" +#include "swaybar/bar.h" +#include "list.h" + +extern struct tray *tray; + +struct tray { + list_t *items; +}; + +/** + * Processes a mouse event on the bar + */ +void tray_mouse_event(struct output *output, int x, int y, + uint32_t button, uint32_t state); + +uint32_t tray_render(struct output *output, struct config *config); + +void tray_upkeep(struct bar *bar); + +/** + * Initializes the tray with D-Bus + */ +void init_tray(); + +#endif /* _SWAYBAR_TRAY_H */ diff --git a/sway/border.c b/sway/border.c index 10ad92c2..df0022ce 100644 --- a/sway/border.c +++ b/sway/border.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #include <wlc/wlc-render.h> #include <cairo/cairo.h> #include <pango/pangocairo.h> diff --git a/sway/commands.c b/sway/commands.c index 01e5e6b5..f83b5287 100644 --- a/sway/commands.c +++ b/sway/commands.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon-names.h> #include <wlc/wlc.h> @@ -221,18 +221,22 @@ static struct cmd_handler handlers[] = { }; static struct cmd_handler bar_handlers[] = { + { "activate_button", bar_cmd_activate_button }, { "binding_mode_indicator", bar_cmd_binding_mode_indicator }, { "bindsym", bar_cmd_bindsym }, { "colors", bar_cmd_colors }, + { "context_button", bar_cmd_context_button }, { "font", bar_cmd_font }, { "height", bar_cmd_height }, { "hidden_state", bar_cmd_hidden_state }, + { "icon_theme", bar_cmd_icon_theme }, { "id", bar_cmd_id }, { "mode", bar_cmd_mode }, { "modifier", bar_cmd_modifier }, { "output", bar_cmd_output }, { "pango_markup", bar_cmd_pango_markup }, { "position", bar_cmd_position }, + { "secondary_button", bar_cmd_secondary_button }, { "separator_symbol", bar_cmd_separator_symbol }, { "status_command", bar_cmd_status_command }, { "strip_workspace_numbers", bar_cmd_strip_workspace_numbers }, @@ -250,7 +254,7 @@ static struct cmd_handler bar_handlers[] = { */ struct cmd_results *add_color(const char *name, char *buffer, const char *color) { int len = strlen(color); - if (len != 7 && len != 9 ) { + if (len != 7 && len != 9) { return cmd_results_new(CMD_INVALID, name, "Invalid color definition %s", color); } diff --git a/sway/commands/assign.c b/sway/commands/assign.c index ec262bb8..c3b03bbc 100644 --- a/sway/commands/assign.c +++ b/sway/commands/assign.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #include <stdio.h> #include <string.h> #include "sway/commands.h" @@ -55,4 +55,3 @@ struct cmd_results *cmd_assign(int argc, char **argv) { } return error ? error : cmd_results_new(CMD_SUCCESS, NULL, NULL); } - diff --git a/sway/commands/bar/activate_button.c b/sway/commands/bar/activate_button.c new file mode 100644 index 00000000..32a1d3e5 --- /dev/null +++ b/sway/commands/bar/activate_button.c @@ -0,0 +1,26 @@ +#include <stdlib.h> +#include "sway/commands.h" +#include "log.h" + +struct cmd_results *bar_cmd_activate_button(int argc, char **argv) { + const char *cmd_name = "activate_button"; +#ifndef ENABLE_TRAY + return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command " + "%s called, but sway was compiled without tray support", + cmd_name, cmd_name); +#else + struct cmd_results *error = NULL; + if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) { + return error; + } + + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined."); + } + + // User should be able to prefix with 0x or whatever they want + config->current_bar->secondary_button = strtoul(argv[0], NULL, 0); + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +#endif +} diff --git a/sway/commands/bar/context_button.c b/sway/commands/bar/context_button.c new file mode 100644 index 00000000..6d7d7aec --- /dev/null +++ b/sway/commands/bar/context_button.c @@ -0,0 +1,26 @@ +#include <stdlib.h> +#include "sway/commands.h" +#include "log.h" + +struct cmd_results *bar_cmd_context_button(int argc, char **argv) { + const char *cmd_name = "context_button"; +#ifndef ENABLE_TRAY + return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command " + "%s called, but sway was compiled without tray support", + cmd_name, cmd_name); +#else + struct cmd_results *error = NULL; + if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) { + return error; + } + + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined."); + } + + // User should be able to prefix with 0x or whatever they want + config->current_bar->context_button = strtoul(argv[0], NULL, 0); + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +#endif +} diff --git a/sway/commands/bar/icon_theme.c b/sway/commands/bar/icon_theme.c new file mode 100644 index 00000000..cbfc0be5 --- /dev/null +++ b/sway/commands/bar/icon_theme.c @@ -0,0 +1,25 @@ +#define _XOPEN_SOURCE 500 +#include <string.h> +#include "sway/commands.h" + +struct cmd_results *bar_cmd_icon_theme(int argc, char **argv) { + const char *cmd_name = "tray_output"; +#ifndef ENABLE_TRAY + return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command " + "%s called, but sway was compiled without tray support", + cmd_name, cmd_name); +#else + struct cmd_results *error = NULL; + if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) { + return error; + } + + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined."); + } + + config->current_bar->icon_theme = strdup(argv[0]); + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +#endif +} diff --git a/sway/commands/bar/secondary_button.c b/sway/commands/bar/secondary_button.c new file mode 100644 index 00000000..745045c5 --- /dev/null +++ b/sway/commands/bar/secondary_button.c @@ -0,0 +1,26 @@ +#include <stdlib.h> +#include "sway/commands.h" +#include "log.h" + +struct cmd_results *bar_cmd_secondary_button(int argc, char **argv) { + const char *cmd_name = "secondary_button"; +#ifndef ENABLE_TRAY + return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command " + "%s called, but sway was compiled without tray support", + cmd_name, cmd_name); +#else + struct cmd_results *error = NULL; + if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) { + return error; + } + + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined."); + } + + // User should be able to prefix with 0x or whatever they want + config->current_bar->secondary_button = strtoul(argv[0], NULL, 0); + + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +#endif +} diff --git a/sway/commands/bar/tray_output.c b/sway/commands/bar/tray_output.c index 8a1b5d35..012304a9 100644 --- a/sway/commands/bar/tray_output.c +++ b/sway/commands/bar/tray_output.c @@ -1,7 +1,29 @@ +#define _XOPEN_SOURCE 500 +#include <string.h> #include "sway/commands.h" -#include "log.h" struct cmd_results *bar_cmd_tray_output(int argc, char **argv) { - sway_log(L_ERROR, "Warning: tray_output is not supported on wayland"); + const char *cmd_name = "tray_output"; +#ifndef ENABLE_TRAY + return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command " + "%s called, but sway was compiled without tray support", + cmd_name, cmd_name); +#else + struct cmd_results *error = NULL; + if ((error = checkarg(argc, cmd_name, EXPECTED_EQUAL_TO, 1))) { + return error; + } + + if (!config->current_bar) { + return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined."); + } + + if (strcmp(argv[0], "all") == 0) { + // Default behaviour + return cmd_results_new(CMD_SUCCESS, NULL, NULL); + } + config->current_bar->tray_output = strdup(argv[0]); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); +#endif } diff --git a/sway/commands/bar/tray_padding.c b/sway/commands/bar/tray_padding.c index 8c559f65..ac0572ce 100644 --- a/sway/commands/bar/tray_padding.c +++ b/sway/commands/bar/tray_padding.c @@ -1,30 +1,34 @@ #include <stdlib.h> -#include <string.h> #include <strings.h> #include "sway/commands.h" #include "log.h" struct cmd_results *bar_cmd_tray_padding(int argc, char **argv) { + const char *cmd_name = "tray_padding"; +#ifndef ENABLE_TRAY + return cmd_results_new(CMD_INVALID, cmd_name, "Invalid %s command" + "%s called, but sway was compiled without tray support", + cmd_name, cmd_name); +#else struct cmd_results *error = NULL; - if ((error = checkarg(argc, "tray_padding", EXPECTED_AT_LEAST, 1))) { + if ((error = checkarg(argc, cmd_name, EXPECTED_AT_LEAST, 1))) { return error; } if (!config->current_bar) { - return cmd_results_new(CMD_FAILURE, "tray_padding", "No bar defined."); + return cmd_results_new(CMD_FAILURE, cmd_name, "No bar defined."); } - int padding = atoi(argv[0]); - if (padding < 0) { - return cmd_results_new(CMD_INVALID, "tray_padding", - "Invalid padding value %s, minimum is 0", argv[0]); + if (argc == 1 || (argc == 2 && strcasecmp("px", argv[1]) == 0)) { + char *inv; + uint32_t padding = strtoul(argv[0], &inv, 10); + if (*inv == '\0' || strcasecmp(inv, "px") == 0) { + config->current_bar->tray_padding = padding; + sway_log(L_DEBUG, "Enabling tray padding of %d px on bar: %s", padding, config->current_bar->id); + return cmd_results_new(CMD_SUCCESS, NULL, NULL); + } } - - if (argc > 1 && strcasecmp("px", argv[1]) != 0) { - return cmd_results_new(CMD_INVALID, "tray_padding", - "Unknown unit %s", argv[1]); - } - config->current_bar->tray_padding = padding; - sway_log(L_DEBUG, "Enabling tray padding of %d px on bar: %s", padding, config->current_bar->id); - return cmd_results_new(CMD_SUCCESS, NULL, NULL); + return cmd_results_new(CMD_FAILURE, cmd_name, + "Expected 'tray_padding <padding>[px]'"); +#endif } diff --git a/sway/commands/bind.c b/sway/commands/bind.c index 8282277b..af5a01e5 100644 --- a/sway/commands/bind.c +++ b/sway/commands/bind.c @@ -1,7 +1,9 @@ #include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon-names.h> +#include <strings.h> #include "sway/commands.h" #include "sway/config.h" +#include "sway/input_state.h" #include "list.h" #include "log.h" #include "stringop.h" @@ -52,6 +54,12 @@ struct cmd_results *cmd_bindsym(int argc, char **argv) { // Check for xkb key xkb_keysym_t sym = xkb_keysym_from_name(split->items[i], XKB_KEYSYM_CASE_INSENSITIVE); + + // Check for mouse binding + if (strncasecmp(split->items[i], "button", strlen("button")) == 0 && + strlen(split->items[i]) == strlen("button0")) { + sym = ((char *)split->items[i])[strlen("button")] - '1' + M_LEFT_CLICK; + } if (!sym) { free_sway_binding(binding); free_flat_list(split); diff --git a/sway/commands/client.c b/sway/commands/client.c index 7954f670..f3d879cd 100644 --- a/sway/commands/client.c +++ b/sway/commands/client.c @@ -4,26 +4,30 @@ static struct cmd_results *parse_border_color(struct border_colors *border_colors, const char *cmd_name, int argc, char **argv) { struct cmd_results *error = NULL; - if (argc != 5) { - return cmd_results_new(CMD_INVALID, cmd_name, "Requires exactly five color values"); + if (argc < 3 || argc > 5) { + return cmd_results_new(CMD_INVALID, cmd_name, "Requires between three and five color values"); } - uint32_t colors[5]; + uint32_t *colors[5] = { + &border_colors->border, + &border_colors->background, + &border_colors->text, + &border_colors->indicator, + &border_colors->child_border + }; int i; - for (i = 0; i < 5; i++) { + for (i = 0; i < argc; i++) { char buffer[10]; error = add_color(cmd_name, buffer, argv[i]); if (error) { return error; } - colors[i] = strtoul(buffer+1, NULL, 16); + *colors[i] = strtoul(buffer + 1, NULL, 16); } - border_colors->border = colors[0]; - border_colors->background = colors[1]; - border_colors->text = colors[2]; - border_colors->indicator = colors[3]; - border_colors->child_border = colors[4]; + if (argc < 5) { + border_colors->child_border = border_colors->background; + } return cmd_results_new(CMD_SUCCESS, NULL, NULL); } diff --git a/sway/commands/layout.c b/sway/commands/layout.c index 40ebd590..57a86565 100644 --- a/sway/commands/layout.c +++ b/sway/commands/layout.c @@ -85,6 +85,10 @@ static struct cmd_results *cmd_layout_auto(swayc_t *container, int argc, char ** enum swayc_layouts old_layout = container->layout; enum swayc_layouts layout = old_layout; + if ((error = checkarg(argc, "layout auto", EXPECTED_MORE_THAN, 1))) { + return error; + } + if (strcasecmp(argv[1], "left") == 0) { layout = L_AUTO_LEFT; } else if (strcasecmp(argv[1], "right") == 0) { diff --git a/sway/commands/set.c b/sway/commands/set.c index 1d6bce04..e3d08dd3 100644 --- a/sway/commands/set.c +++ b/sway/commands/set.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #include <stdio.h> #include <string.h> #include <strings.h> diff --git a/sway/config.c b/sway/config.c index 19b1882f..e0b65615 100644 --- a/sway/config.c +++ b/sway/config.c @@ -1,5 +1,5 @@ #define _POSIX_C_SOURCE 200809L -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> @@ -69,6 +69,10 @@ static void free_bar(struct bar_config *bar) { } free(bar->mode); free(bar->hidden_state); +#ifdef ENABLE_TRAY + free(bar->tray_output); + free(bar->icon_theme); +#endif free(bar->status_command); free(bar->font); free(bar->separator_symbol); @@ -527,7 +531,7 @@ bool load_main_config(const char *file, bool is_active) { list_add(config->config_chain, path); config->reading = true; - + // Read security configs bool success = true; DIR *dir = opendir(SYSCONFDIR "/sway/security.d"); @@ -1386,7 +1390,14 @@ struct bar_config *default_bar_config(void) { bar->separator_symbol = NULL; bar->strip_workspace_numbers = false; bar->binding_mode_indicator = true; +#ifdef ENABLE_TRAY + bar->tray_output = NULL; + bar->icon_theme = NULL; bar->tray_padding = 2; + bar->activate_button = 0x110; /* BTN_LEFT */ + bar->context_button = 0x111; /* BTN_RIGHT */ + bar->secondary_button = 0x112; /* BTN_MIDDLE */ +#endif bar->verbose = false; bar->pid = 0; // set default colors diff --git a/sway/container.c b/sway/container.c index 08aa77a8..358ba767 100644 --- a/sway/container.c +++ b/sway/container.c @@ -699,12 +699,12 @@ static bool pointer_test(swayc_t *view, void *_origin) { swayc_t *container_under_pointer(void) { // root.output->workspace - if (!root_container.focused || !root_container.focused->focused) { + if (!root_container.focused) { return NULL; } - swayc_t *lookup = root_container.focused->focused; + swayc_t *lookup = root_container.focused; // Case of empty workspace - if (lookup->children == 0) { + if (lookup->children && !lookup->unmanaged) { return NULL; } struct wlc_point origin; @@ -712,6 +712,17 @@ swayc_t *container_under_pointer(void) { while (lookup && lookup->type != C_VIEW) { int i; int len; + for (int _i = 0; lookup->unmanaged && _i < lookup->unmanaged->length; ++_i) { + wlc_handle *handle = lookup->unmanaged->items[_i]; + const struct wlc_geometry *geo = wlc_view_get_geometry(*handle); + if (origin.x >= geo->origin.x && origin.y >= geo->origin.y + && origin.x < geo->origin.x + (int)geo->size.w + && origin.y < geo->origin.y + (int)geo->size.h) { + // Hack: we force focus upon unmanaged views here + wlc_view_focus(*handle); + return NULL; + } + } // if tabbed/stacked go directly to focused container, otherwise search // children if (lookup->layout == L_TABBED || lookup->layout == L_STACKED) { diff --git a/sway/criteria.c b/sway/criteria.c index 1ea8311e..04683f66 100644 --- a/sway/criteria.c +++ b/sway/criteria.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #include <stdlib.h> #include <stdio.h> #include <stdbool.h> diff --git a/sway/handlers.c b/sway/handlers.c index 0aa35c26..39261e3d 100644 --- a/sway/handlers.c +++ b/sway/handlers.c @@ -609,6 +609,8 @@ static void handle_view_geometry_request(wlc_handle handle, const struct wlc_geo view->y = geometry->origin.y; update_geometry(view); } + } else { + wlc_view_set_geometry(handle, 0, geometry); } } @@ -927,6 +929,26 @@ static bool handle_pointer_button(wlc_handle view, uint32_t time, const struct w // Update view pointer is on pointer_state.view = container_under_pointer(); + struct sway_mode *mode = config->current_mode; + // handle bindings + for (int i = 0; i < mode->bindings->length; ++i) { + struct sway_binding *binding = mode->bindings->items[i]; + if ((modifiers->mods ^ binding->modifiers) == 0) { + switch (state) { + case WLC_BUTTON_STATE_PRESSED: { + if (!binding->release && handle_bindsym(binding, button, 0)) { + return EVENT_HANDLED; + } + } + case WLC_BUTTON_STATE_RELEASED: + if (binding->release && handle_bindsym(binding, button, 0)) { + return EVENT_HANDLED; + } + break; + } + } + } + // Update pointer_state switch (button) { case M_LEFT_CLICK: diff --git a/sway/input.c b/sway/input.c index 4f1ef760..6263f79f 100644 --- a/sway/input.c +++ b/sway/input.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #include <ctype.h> #include <float.h> #include <limits.h> @@ -60,7 +60,7 @@ char *libinput_dev_unique_id(struct libinput_device *device) { } const char *fmt = "%d:%d:%s"; - snprintf(identifier, len, fmt, vendor, product, name); + snprintf(identifier, len, fmt, vendor, product, name); free(name); return identifier; } diff --git a/sway/ipc-json.c b/sway/ipc-json.c index 512144a4..31de53f0 100644 --- a/sway/ipc-json.c +++ b/sway/ipc-json.c @@ -327,7 +327,22 @@ json_object *ipc_json_describe_bar_config(struct bar_config *bar) { json_object *json = json_object_new_object(); json_object_object_add(json, "id", json_object_new_string(bar->id)); - json_object_object_add(json, "tray_output", NULL); +#ifdef ENABLE_TRAY + if (bar->tray_output) { + json_object_object_add(json, "tray_output", json_object_new_string(bar->tray_output)); + } else { + json_object_object_add(json, "tray_output", NULL); + } + if (bar->icon_theme) { + json_object_object_add(json, "icon_theme", json_object_new_string(bar->icon_theme)); + } else { + json_object_object_add(json, "icon_theme", NULL); + } + json_object_object_add(json, "tray_padding", json_object_new_int(bar->tray_padding)); + json_object_object_add(json, "activate_button", json_object_new_int(bar->activate_button)); + json_object_object_add(json, "context_button", json_object_new_int(bar->context_button)); + json_object_object_add(json, "secondary_button", json_object_new_int(bar->secondary_button)); +#endif json_object_object_add(json, "mode", json_object_new_string(bar->mode)); json_object_object_add(json, "hidden_state", json_object_new_string(bar->hidden_state)); json_object_object_add(json, "modifier", json_object_new_string(get_modifier_name_by_mask(bar->modifier))); diff --git a/sway/main.c b/sway/main.c index b9549b12..82375e0b 100644 --- a/sway/main.c +++ b/sway/main.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #define _POSIX_C_SOURCE 200112L #include <stdio.h> #include <stdlib.h> @@ -27,6 +27,7 @@ #include "stringop.h" #include "sway.h" #include "log.h" +#include "util.h" static bool terminate_request = false; static int exit_value = 0; @@ -209,6 +210,27 @@ static void security_sanity_check() { #endif } +static void executable_sanity_check() { +#ifdef __linux__ + struct stat sb; + char *exe = realpath("/proc/self/exe", NULL); + stat(exe, &sb); + // We assume that cap_get_file returning NULL implies ENODATA + if (sb.st_mode & (S_ISUID|S_ISGID) && cap_get_file(exe)) { + sway_log(L_ERROR, + "sway executable has both the s(g)uid bit AND file caps set."); + sway_log(L_ERROR, + "This is strongly discouraged (and completely broken)."); + sway_log(L_ERROR, + "Please clear one of them (either the suid bit, or the file caps)."); + sway_log(L_ERROR, + "If unsure, strip the file caps."); + exit(EXIT_FAILURE); + } + free(exe); +#endif +} + int main(int argc, char **argv) { static int verbose = 0, debug = 0, validate = 0; @@ -288,6 +310,15 @@ int main(int argc, char **argv) { } } + // we need to setup logging before wlc_init in case it fails. + if (debug) { + init_log(L_DEBUG); + } else if (verbose || validate) { + init_log(L_INFO); + } else { + init_log(L_ERROR); + } + if (optind < argc) { // Behave as IPC client if(optind != 1) { sway_log(L_ERROR, "Don't use options with the IPC client"); @@ -317,6 +348,7 @@ int main(int argc, char **argv) { return 0; } + executable_sanity_check(); #ifdef __linux__ bool suid = false; if (getuid() != geteuid() || getgid() != getegid()) { @@ -329,14 +361,6 @@ int main(int argc, char **argv) { } #endif - // we need to setup logging before wlc_init in case it fails. - if (debug) { - init_log(L_DEBUG); - } else if (verbose || validate) { - init_log(L_INFO); - } else { - init_log(L_ERROR); - } wlc_log_set_handler(wlc_log_handler); log_kernel(); log_distro(); @@ -409,4 +433,3 @@ int main(int argc, char **argv) { return exit_value; } - diff --git a/sway/security.c b/sway/security.c index 8eab6126..92de06c1 100644 --- a/sway/security.c +++ b/sway/security.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #include <sys/types.h> #include <sys/stat.h> #include <string.h> diff --git a/sway/sway-bar.5.txt b/sway/sway-bar.5.txt index 5a52e7db..29487662 100644 --- a/sway/sway-bar.5.txt +++ b/sway/sway-bar.5.txt @@ -65,6 +65,42 @@ Commands **height** <height>:: Sets the height of the bar. Default height will match the font size. +Tray +---- + +Swaybar provides a system tray where programs such as NetworkManager, VLC, +Pidgin, etc. can place little icons. The following commands configure +interaction with the tray or individual icons. +The _button_ argument in all following commands is a Linux input event code as +defined in linux/input-event-codes.h. This is because wayland defines button +codes in this manner. + +**activate_button** <button>:: + Sets the button to be used for the _activate_ (primary click) tray item + event. By default is BTN_LEFT (0x110). + +**context_button** <button>:: + Sets the button to be used for the _context menu_ (right click) tray item + event. By default is BTN_RIGHT (0x111). + +**secondary_button** <button>:: + Sets the button to be used for the _secondary_ (middle click) tray item + event. By default is BTN_MIDDLE (0x112). + +**tray_output** none|all|<name>:: + Sets the output that the tray will appear on or none. Unlike i3bar, swaybar + should be able to show icons on any number of bars and outputs without + races. Because of this, the default value for this is _all_. + +**tray_padding** <px> [px]:: + Sets the pixel padding of the system tray. This padding will surround the + tray on all sides and between each item. The default value for _px_ is 2. + +**icon_theme** <name>:: + Sets the icon theme that sway will look for item icons in. This option has + no default value, because sway will always default to the fallback theme, + hicolor. + Colors ------ diff --git a/sway/sway-input.5.txt b/sway/sway-input.5.txt index e92b523a..f44a3179 100644 --- a/sway/sway-input.5.txt +++ b/sway/sway-input.5.txt @@ -30,7 +30,7 @@ Commands **input** <identifier> dwt <enabled|disabled>:: Enables or disables disable-while-typing for the specified input device. -**input** <identifier> events <enable|disabled>:: +**input** <identifier> events <enabled|disabled|disabled_on_external_mouse>:: Enables or disables send_events for specified input device. (Disabling send_events disables the input device) diff --git a/sway/sway.5.txt b/sway/sway.5.txt index 1fa0b856..131780e2 100644 --- a/sway/sway.5.txt +++ b/sway/sway.5.txt @@ -276,6 +276,11 @@ The default colors are: **focus_follows_mouse** <yes|no>:: If set to _yes_, moving your mouse over a window will focus that window. +**font** <font>:: + Sets font for use in title bars. Generally the format is something like "Name + Style Size" e.g. "Deja Vu Sans Book 12". You can also use Pango font + descriptions with "pango:font". + **for_window** <criteria> <command>:: Whenever a window that matches _criteria_ appears, run list of commands. See **Criteria** section below. diff --git a/swaybar/CMakeLists.txt b/swaybar/CMakeLists.txt index f59a48fd..373719de 100644 --- a/swaybar/CMakeLists.txt +++ b/swaybar/CMakeLists.txt @@ -5,7 +5,13 @@ include_directories( ${PANGO_INCLUDE_DIRS} ${JSONC_INCLUDE_DIRS} ${XKBCOMMON_INCLUDE_DIRS} + ${DBUS_INCLUDE_DIRS} ) +if (enable-tray) + file(GLOB tray + tray/*.c + ) +endif() add_executable(swaybar main.c @@ -14,6 +20,8 @@ add_executable(swaybar bar.c status_line.c ipc.c + event_loop.c + ${tray} ) target_link_libraries(swaybar @@ -24,6 +32,7 @@ target_link_libraries(swaybar ${CAIRO_LIBRARIES} ${PANGO_LIBRARIES} ${JSONC_LIBRARIES} + ${DBUS_LIBRARIES} ) if (WITH_GDK_PIXBUF) @@ -32,6 +41,8 @@ if (WITH_GDK_PIXBUF) ) endif() +target_link_libraries(swaybar rt) + install( TARGETS swaybar RUNTIME diff --git a/swaybar/bar.c b/swaybar/bar.c index abde1cc9..3412ff29 100644 --- a/swaybar/bar.c +++ b/swaybar/bar.c @@ -7,10 +7,17 @@ #include <sys/wait.h> #include <signal.h> #include <poll.h> +#ifdef ENABLE_TRAY +#include <dbus/dbus.h> +#include "swaybar/tray/sni_watcher.h" +#include "swaybar/tray/tray.h" +#include "swaybar/tray/sni.h" +#endif #include "swaybar/ipc.h" #include "swaybar/render.h" #include "swaybar/config.h" #include "swaybar/status_line.h" +#include "swaybar/event_loop.h" #include "swaybar/bar.h" #include "ipc-client.h" #include "list.h" @@ -34,6 +41,7 @@ static void spawn_status_cmd_proc(struct bar *bar) { close(pipefd[0]); dup2(pipefd[1], STDOUT_FILENO); close(pipefd[1]); + setpgid(bar->status_command_pid, 0); char *const cmd[] = { "sh", "-c", @@ -56,12 +64,15 @@ struct output *new_output(const char *name) { output->window = NULL; output->registry = NULL; output->workspaces = create_list(); +#ifdef ENABLE_TRAY + output->items = create_list(); +#endif return output; } static void mouse_button_notify(struct window *window, int x, int y, uint32_t button, uint32_t state_w) { - sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d\n", button, x, y, state_w); + sway_log(L_DEBUG, "Mouse button %d clicked at %d %d %d", button, x, y, state_w); if (!state_w) { return; } @@ -92,6 +103,10 @@ static void mouse_button_notify(struct window *window, int x, int y, break; } } + +#ifdef ENABLE_TRAY + tray_mouse_event(clicked_output, x, y, button, state_w); +#endif } static void mouse_scroll_notify(struct window *window, enum scroll_direction direction) { @@ -136,6 +151,9 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) { /* initialize bar with default values */ bar_init(bar); + /* Initialize event loop lists */ + init_event_loop(); + /* connect to sway ipc */ bar->ipc_socketfd = ipc_open_socket(socket_path); bar->ipc_event_socketfd = ipc_open_socket(socket_path); @@ -178,23 +196,41 @@ void bar_setup(struct bar *bar, const char *socket_path, const char *bar_id) { } /* spawn status command */ spawn_status_cmd_proc(bar); + +#ifdef ENABLE_TRAY + init_tray(bar); +#endif } -void bar_run(struct bar *bar) { - int pfds = bar->outputs->length + 2; - struct pollfd *pfd = malloc(pfds * sizeof(struct pollfd)); - bool dirty = true; +bool dirty = true; + +static void respond_ipc(int fd, short mask, void *_bar) { + struct bar *bar = (struct bar *)_bar; + sway_log(L_DEBUG, "Got IPC event."); + dirty = handle_ipc_event(bar); +} + +static void respond_command(int fd, short mask, void *_bar) { + struct bar *bar = (struct bar *)_bar; + dirty = handle_status_line(bar); +} + +static void respond_output(int fd, short mask, void *_output) { + struct output *output = (struct output *)_output; + if (wl_display_dispatch(output->registry->display) == -1) { + sway_log(L_ERROR, "failed to dispatch wl: %d", errno); + } +} - pfd[0].fd = bar->ipc_event_socketfd; - pfd[0].events = POLLIN; - pfd[1].fd = bar->status_read_fd; - pfd[1].events = POLLIN; +void bar_run(struct bar *bar) { + add_event(bar->ipc_event_socketfd, POLLIN, respond_ipc, bar); + add_event(bar->status_read_fd, POLLIN, respond_command, bar); int i; for (i = 0; i < bar->outputs->length; ++i) { struct output *output = bar->outputs->items[i]; - pfd[i+2].fd = wl_display_get_fd(output->registry->display); - pfd[i+2].events = POLLIN; + add_event(wl_display_get_fd(output->registry->display), + POLLIN, respond_output, output); } while (1) { @@ -212,29 +248,10 @@ void bar_run(struct bar *bar) { dirty = false; - poll(pfd, pfds, -1); - - if (pfd[0].revents & POLLIN) { - sway_log(L_DEBUG, "Got IPC event."); - dirty = handle_ipc_event(bar); - } - - if (bar->config->status_command && pfd[1].revents & POLLIN) { - sway_log(L_DEBUG, "Got update from status command."); - dirty = handle_status_line(bar); - } - - // dispatch wl_display events - for (i = 0; i < bar->outputs->length; ++i) { - struct output *output = bar->outputs->items[i]; - if (pfd[i+2].revents & POLLIN) { - if (wl_display_dispatch(output->registry->display) == -1) { - sway_log(L_ERROR, "failed to dispatch wl: %d", errno); - } - } else { - wl_display_dispatch_pending(output->registry->display); - } - } + event_loop_poll(); +#ifdef ENABLE_TRAY + dispatch_dbus(); +#endif } } @@ -274,7 +291,7 @@ static void free_outputs(list_t *outputs) { static void terminate_status_command(pid_t pid) { if (pid) { // terminate status_command process - int ret = kill(pid, SIGTERM); + int ret = killpg(pid, SIGTERM); if (ret != 0) { sway_log(L_ERROR, "Unable to terminate status_command [pid: %d]", pid); } else { diff --git a/swaybar/config.c b/swaybar/config.c index 1d802022..8fe552f2 100644 --- a/swaybar/config.c +++ b/swaybar/config.c @@ -48,6 +48,18 @@ struct config *init_config() { /* height */ config->height = 0; +#ifdef ENABLE_TRAY + config->tray_output = NULL; + config->icon_theme = NULL; + config->tray_padding = 2; + /** + * These constants are used by wayland and are defined in + * linux/input-event-codes.h + */ + config->activate_button = 0x110; /* BTN_LEFT */ + config->context_button = 0x111; /* BTN_RIGHT */ +#endif + /* colors */ config->colors.background = 0x000000FF; config->colors.statusline = 0xFFFFFFFF; diff --git a/swaybar/event_loop.c b/swaybar/event_loop.c new file mode 100644 index 00000000..80655d8b --- /dev/null +++ b/swaybar/event_loop.c @@ -0,0 +1,143 @@ +#define _XOPEN_SOURCE 500 +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <strings.h> +#include <poll.h> +#include "swaybar/bar.h" +#include "swaybar/event_loop.h" +#include "list.h" +#include "log.h" + +struct event_item { + void(*cb)(int fd, short mask, void *data); + void *data; +}; + +struct timer_item { + timer_t timer; + void(*cb)(timer_t timer, void *data); + void *data; +}; + +static struct { + // The order of each must be kept consistent + struct { /* pollfd array */ + struct pollfd *items; + int capacity; + int length; + } fds; + list_t *items; /* event_item list */ + + // Timer list + list_t *timers; +} event_loop; + +void add_timer(timer_t timer, + void(*cb)(timer_t timer, void *data), + void *data) { + + struct timer_item *item = malloc(sizeof(struct timer_item)); + item->timer = timer; + item->cb = cb; + item->data = data; + + list_add(event_loop.timers, item); +} + +void add_event(int fd, short mask, + void(*cb)(int fd, short mask, void *data), void *data) { + + struct pollfd pollfd = { + fd, + mask, + 0, + }; + + // Resize + if (event_loop.fds.length == event_loop.fds.capacity) { + event_loop.fds.capacity += 10; + event_loop.fds.items = realloc(event_loop.fds.items, + sizeof(struct pollfd) * event_loop.fds.capacity); + } + + event_loop.fds.items[event_loop.fds.length++] = pollfd; + + struct event_item *item = malloc(sizeof(struct event_item)); + item->cb = cb; + item->data = data; + + list_add(event_loop.items, item); + + return; +} + +bool remove_event(int fd) { + int index = -1; + for (int i = 0; i < event_loop.fds.length; ++i) { + if (event_loop.fds.items[i].fd == fd) { + index = i; + } + } + if (index != -1) { + free(event_loop.items->items[index]); + + --event_loop.fds.length; + memmove(&event_loop.fds.items[index], &event_loop.fds.items[index + 1], + sizeof(struct pollfd) * event_loop.fds.length - index); + + list_del(event_loop.items, index); + return true; + } else { + return false; + } +} + +static int timer_item_timer_cmp(const void *_timer_item, const void *_timer) { + const struct timer_item *timer_item = _timer_item; + const timer_t *timer = _timer; + if (timer_item->timer == timer) { + return 0; + } else { + return -1; + } +} +bool remove_timer(timer_t timer) { + int index = list_seq_find(event_loop.timers, timer_item_timer_cmp, &timer); + if (index != -1) { + list_del(event_loop.timers, index); + return true; + } + return false; +} + +void event_loop_poll() { + poll(event_loop.fds.items, event_loop.fds.length, -1); + + for (int i = 0; i < event_loop.fds.length; ++i) { + struct pollfd pfd = event_loop.fds.items[i]; + struct event_item *item = (struct event_item *)event_loop.items->items[i]; + + if (pfd.revents & pfd.events) { + item->cb(pfd.fd, pfd.revents, item->data); + } + } + + // check timers + // not tested, but seems to work + for (int i = 0; i < event_loop.timers->length; ++i) { + struct timer_item *item = event_loop.timers->items[i]; + int overrun = timer_getoverrun(item->timer); + if (overrun && overrun != -1) { + item->cb(item->timer, item->data); + } + } +} + +void init_event_loop() { + event_loop.fds.length = 0; + event_loop.fds.capacity = 10; + event_loop.fds.items = malloc(event_loop.fds.capacity * sizeof(struct pollfd)); + event_loop.items = create_list(); + event_loop.timers = create_list(); +} diff --git a/swaybar/ipc.c b/swaybar/ipc.c index b08eeea8..93d1219c 100644 --- a/swaybar/ipc.c +++ b/swaybar/ipc.c @@ -19,11 +19,19 @@ void ipc_send_workspace_command(const char *workspace_name) { static void ipc_parse_config(struct config *config, const char *payload) { json_object *bar_config = json_tokener_parse(payload); - json_object *tray_output, *mode, *hidden_bar, *position, *status_command; + json_object *markup, *mode, *hidden_bar, *position, *status_command; json_object *font, *bar_height, *wrap_scroll, *workspace_buttons, *strip_workspace_numbers; json_object *binding_mode_indicator, *verbose, *colors, *sep_symbol, *outputs; - json_object *markup; +#ifdef ENABLE_TRAY + json_object *tray_output, *icon_theme, *tray_padding, *activate_button, *context_button; + json_object *secondary_button; json_object_object_get_ex(bar_config, "tray_output", &tray_output); + json_object_object_get_ex(bar_config, "icon_theme", &icon_theme); + json_object_object_get_ex(bar_config, "tray_padding", &tray_padding); + json_object_object_get_ex(bar_config, "activate_button", &activate_button); + json_object_object_get_ex(bar_config, "context_button", &context_button); + json_object_object_get_ex(bar_config, "secondary_button", &secondary_button); +#endif json_object_object_get_ex(bar_config, "mode", &mode); json_object_object_get_ex(bar_config, "hidden_bar", &hidden_bar); json_object_object_get_ex(bar_config, "position", &position); @@ -83,6 +91,34 @@ static void ipc_parse_config(struct config *config, const char *payload) { config->pango_markup = json_object_get_boolean(markup); } +#ifdef ENABLE_TRAY + if (tray_output) { + free(config->tray_output); + config->tray_output = strdup(json_object_get_string(tray_output)); + } + + if (icon_theme) { + free(config->icon_theme); + config->icon_theme = strdup(json_object_get_string(icon_theme)); + } + + if (tray_padding) { + config->tray_padding = json_object_get_int(tray_padding); + } + + if (activate_button) { + config->activate_button = json_object_get_int(activate_button); + } + + if (context_button) { + config->context_button = json_object_get_int(context_button); + } + + if (secondary_button) { + config->secondary_button = json_object_get_int(secondary_button); + } +#endif + // free previous outputs list int i; for (i = 0; i < config->outputs->length; ++i) { diff --git a/swaybar/render.c b/swaybar/render.c index 2eae997f..6ec47e79 100644 --- a/swaybar/render.c +++ b/swaybar/render.c @@ -8,6 +8,10 @@ #include "swaybar/config.h" #include "swaybar/status_line.h" #include "swaybar/render.h" +#ifdef ENABLE_TRAY +#include "swaybar/tray/tray.h" +#include "swaybar/tray/sni.h" +#endif #include "log.h" @@ -297,6 +301,12 @@ void render(struct output *output, struct config *config, struct status_line *li } cairo_paint(cairo); +#ifdef ENABLE_TRAY + uint32_t tray_width = tray_render(output, config); +#else + const uint32_t tray_width = window->width * window->scale; +#endif + // Command output if (is_focused) { cairo_set_source_u32(cairo, config->colors.focused_statusline); @@ -309,12 +319,11 @@ void render(struct output *output, struct config *config, struct status_line *li if (line->protocol == TEXT) { get_text_size(window->cairo, window->font, &width, &height, window->scale, config->pango_markup, "%s", line->text_line); - cairo_move_to(cairo, (window->width * window->scale) - - margin - width, margin); + cairo_move_to(cairo, tray_width - margin - width, margin); pango_printf(window->cairo, window->font, window->scale, config->pango_markup, "%s", line->text_line); } else if (line->protocol == I3BAR && line->block_line) { - double pos = (window->width * window->scale) - 0.5; + double pos = tray_width - 0.5; bool edge = true; for (i = line->block_line->length - 1; i >= 0; --i) { struct status_block *block = line->block_line->items[i]; diff --git a/swaybar/tray/dbus.c b/swaybar/tray/dbus.c new file mode 100644 index 00000000..333d398e --- /dev/null +++ b/swaybar/tray/dbus.c @@ -0,0 +1,189 @@ +#define _XOPEN_SOURCE 500 +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <stdbool.h> +#include <poll.h> +#include <signal.h> +#include <time.h> +#include <dbus/dbus.h> +#include "swaybar/tray/dbus.h" +#include "swaybar/event_loop.h" +#include "log.h" + +DBusConnection *conn = NULL; + +static void dispatch_watch(int fd, short mask, void *data) { + sway_log(L_DEBUG, "Dispatching watch"); + DBusWatch *watch = data; + + if (!dbus_watch_get_enabled(watch)) { + return; + } + + uint32_t flags = 0; + + if (mask & POLLIN) { + flags |= DBUS_WATCH_READABLE; + } if (mask & POLLOUT) { + flags |= DBUS_WATCH_WRITABLE; + } if (mask & POLLHUP) { + flags |= DBUS_WATCH_HANGUP; + } if (mask & POLLERR) { + flags |= DBUS_WATCH_ERROR; + } + + dbus_watch_handle(watch, flags); +} + +static dbus_bool_t add_watch(DBusWatch *watch, void *_data) { + if (!dbus_watch_get_enabled(watch)) { + // Watch should not be polled + return TRUE; + } + + short mask = 0; + uint32_t flags = dbus_watch_get_flags(watch); + + if (flags & DBUS_WATCH_READABLE) { + mask |= POLLIN; + } if (flags & DBUS_WATCH_WRITABLE) { + mask |= POLLOUT; + } + + int fd = dbus_watch_get_unix_fd(watch); + + sway_log(L_DEBUG, "Adding DBus watch fd: %d", fd); + add_event(fd, mask, dispatch_watch, watch); + + return TRUE; +} + +static void remove_watch(DBusWatch *watch, void *_data) { + int fd = dbus_watch_get_unix_fd(watch); + + remove_event(fd); +} + +static void dispatch_timeout(timer_t timer, void *data) { + sway_log(L_DEBUG, "Dispatching DBus timeout"); + DBusTimeout *timeout = data; + + if (dbus_timeout_get_enabled(timeout)) { + dbus_timeout_handle(timeout); + } +} + +static dbus_bool_t add_timeout(DBusTimeout *timeout, void *_data) { + if (!dbus_timeout_get_enabled(timeout)) { + return TRUE; + } + + timer_t *timer = malloc(sizeof(timer_t)); + if (!timer) { + sway_log(L_ERROR, "Cannot allocate memory"); + return FALSE; + } + struct sigevent ev = { + .sigev_notify = SIGEV_NONE, + }; + + if (timer_create(CLOCK_MONOTONIC, &ev, timer)) { + sway_log(L_ERROR, "Could not create DBus timer"); + return FALSE; + } + + int interval = dbus_timeout_get_interval(timeout); + int interval_sec = interval / 1000; + int interval_msec = (interval_sec * 1000) - interval; + + struct timespec period = { + (time_t) interval_sec, + ((long) interval_msec) * 1000 * 1000, + }; + struct itimerspec time = { + period, + period, + }; + + timer_settime(*timer, 0, &time, NULL); + + dbus_timeout_set_data(timeout, timer, free); + + sway_log(L_DEBUG, "Adding DBus timeout. Interval: %ds %dms", interval_sec, interval_msec); + add_timer(*timer, dispatch_timeout, timeout); + + return TRUE; +} +static void remove_timeout(DBusTimeout *timeout, void *_data) { + timer_t *timer = (timer_t *) dbus_timeout_get_data(timeout); + sway_log(L_DEBUG, "Removing DBus timeout."); + + if (timer) { + remove_timer(*timer); + } +} + +static bool should_dispatch = true; + +static void dispatch_status(DBusConnection *connection, DBusDispatchStatus new_status, + void *_data) { + if (new_status == DBUS_DISPATCH_DATA_REMAINS) { + should_dispatch = true; + } +} + +/* Public functions below */ + +void dispatch_dbus() { + if (!should_dispatch) { + return; + } + + DBusDispatchStatus status; + + do { + status = dbus_connection_dispatch(conn); + } while (status == DBUS_DISPATCH_DATA_REMAINS); + + if (status != DBUS_DISPATCH_COMPLETE) { + sway_log(L_ERROR, "Cannot dispatch dbus events: %d", status); + } + + should_dispatch = false; +} + +int dbus_init() { + DBusError error; + dbus_error_init(&error); + + conn = dbus_bus_get(DBUS_BUS_SESSION, &error); + dbus_connection_set_exit_on_disconnect(conn, FALSE); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "Cannot get bus connection: %s\n", error.message); + conn = NULL; + return -1; + } + + sway_log(L_INFO, "Unique name: %s\n", dbus_bus_get_unique_name(conn)); + + // Will be called if dispatch status changes + dbus_connection_set_dispatch_status_function(conn, dispatch_status, NULL, NULL); + + if (!dbus_connection_set_watch_functions(conn, add_watch, remove_watch, + NULL, NULL, NULL)) { + dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); + sway_log(L_ERROR, "Failed to activate DBUS watch functions"); + return -1; + } + + if (!dbus_connection_set_timeout_functions(conn, add_timeout, remove_timeout, + NULL, NULL, NULL)) { + dbus_connection_set_watch_functions(conn, NULL, NULL, NULL, NULL, NULL); + dbus_connection_set_timeout_functions(conn, NULL, NULL, NULL, NULL, NULL); + sway_log(L_ERROR, "Failed to activate DBUS timeout functions"); + return -1; + } + + return 0; +} diff --git a/swaybar/tray/icon.c b/swaybar/tray/icon.c new file mode 100644 index 00000000..29151a74 --- /dev/null +++ b/swaybar/tray/icon.c @@ -0,0 +1,404 @@ +#define _XOPEN_SOURCE 500 +#define _POSIX_C_SOURCE 200809L +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <dirent.h> +#include <sys/stat.h> +#include <stdbool.h> +#include <stdint.h> +#include <limits.h> +#include "swaybar/tray/icon.h" +#include "swaybar/bar.h" +#include "swaybar/config.h" +#include "stringop.h" +#include "log.h" + +/** + * REVIEW: + * This file repeats lots of "costly" operations that are the same for every + * icon. It's possible to create a dictionary or some other structure to cache + * these, though it may complicate things somewhat. + * + * Also parsing (index.theme) is currently pretty messy, so that could be made + * much better as well. Over all, things work, but are not optimal. + */ + +/* Finds all themes that the given theme inherits */ +static list_t *find_inherits(const char *theme_dir) { + const char inherits[] = "Inherits"; + const char index_name[] = "index.theme"; + list_t *themes = create_list(); + FILE *index = NULL; + char *path = malloc(strlen(theme_dir) + sizeof(index_name)); + if (!path) { + goto fail; + } + if (!themes) { + goto fail; + } + + strcpy(path, theme_dir); + strcat(path, index_name); + + index = fopen(path, "r"); + if (!index) { + goto fail; + } + + char *buf = NULL; + size_t n = 0; + while (!feof(index)) { + getline(&buf, &n, index); + if (n <= sizeof(inherits) + 1) { + continue; + } + if (strncmp(inherits, buf, sizeof(inherits) - 1) == 0) { + char *themestr = buf + sizeof(inherits); + themes = split_string(themestr, ","); + break; + } + } + free(buf); + +fail: + free(path); + if (index) { + fclose(index); + } + return themes; +} + +static bool isdir(const char *path) { + struct stat statbuf; + if (stat(path, &statbuf) != -1) { + if (S_ISDIR(statbuf.st_mode)) { + return true; + } + } + return false; + +} + +/** + * Returns the directory of a given theme if it exists. + * The returned pointer must be freed. + */ +static char *find_theme_dir(const char *theme) { + char *basedir; + char *icon_dir; + + if (!theme) { + return NULL; + } + + if (!(icon_dir = malloc(1024))) { + sway_log(L_ERROR, "Out of memory!"); + goto fail; + } + + if ((basedir = getenv("HOME"))) { + if (snprintf(icon_dir, 1024, "%s/.icons/%s", basedir, theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + // XXX perhaps just goto trying in /usr/share? This + // shouldn't happen anyway, but might with a long global + goto fail; + } + + if (isdir(icon_dir)) { + return icon_dir; + } + } + + if ((basedir = getenv("XDG_DATA_DIRS"))) { + if (snprintf(icon_dir, 1024, "%s/icons/%s", basedir, theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + // ditto + goto fail; + } + + if (isdir(icon_dir)) { + return icon_dir; + } + } + + // Spec says use "/usr/share/pixmaps/", but I see everything in + // "/usr/share/icons/" look it both, I suppose. + if (snprintf(icon_dir, 1024, "/usr/share/pixmaps/%s", theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + goto fail; + } + if (isdir(icon_dir)) { + return icon_dir; + } + + if (snprintf(icon_dir, 1024, "/usr/share/icons/%s", theme) >= 1024) { + sway_log(L_ERROR, "Path too long to render"); + goto fail; + } + if (isdir(icon_dir)) { + return icon_dir; + } + +fail: + free(icon_dir); + sway_log(L_ERROR, "Could not find dir for theme: %s", theme); + return NULL; +} + +/** + * Returns all theme dirs needed to be looked in for an icon. + * Does not check for duplicates + */ +static list_t *find_all_theme_dirs(const char *theme) { + list_t *dirs = create_list(); + if (!dirs) { + return NULL; + } + char *dir = find_theme_dir(theme); + if (dir) { + list_add(dirs, dir); + list_t *inherits = find_inherits(dir); + list_cat(dirs, inherits); + list_free(inherits); + } + dir = find_theme_dir("hicolor"); + if (dir) { + list_add(dirs, dir); + } + + return dirs; +} + +struct subdir { + int size; + char name[]; +}; + +static int subdir_str_cmp(const void *_subdir, const void *_str) { + const struct subdir *subdir = _subdir; + const char *str = _str; + return strcmp(subdir->name, str); +} +/** + * Helper to find_subdirs. Acts similar to `split_string(subdirs, ",")` but + * generates a list of struct subdirs + */ +static list_t *split_subdirs(char *subdir_str) { + list_t *subdir_list = create_list(); + char *copy = strdup(subdir_str); + if (!subdir_list || !copy) { + list_free(subdir_list); + free(copy); + return NULL; + } + + char *token; + token = strtok(copy, ","); + while(token) { + int len = strlen(token) + 1; + struct subdir *subdir = + malloc(sizeof(struct subdir) + sizeof(char [len])); + if (!subdir) { + // Return what we have + return subdir_list; + } + subdir->size = 0; + strcpy(subdir->name, token); + + list_add(subdir_list, subdir); + + token = strtok(NULL, ","); + } + free(copy); + + return subdir_list; +} +/** + * Returns a list of all subdirectories of a theme. + * Take note: the subdir names are all relative to `theme_dir` and must be + * combined with it to form a valid directory. + * + * Each member of the list is of type (struct subdir *) this struct contains + * the name of the subdir, along with size information. These must be freed + * bye the caller. + * + * This currently ignores min and max sizes of icons. + */ +static list_t* find_theme_subdirs(const char *theme_dir) { + const char index_name[] = "/index.theme"; + list_t *dirs = NULL; + char *path = malloc(strlen(theme_dir) + sizeof(index_name)); + FILE *index = NULL; + if (!path) { + sway_log(L_ERROR, "Failed to allocate memory"); + goto fail; + } + + strcpy(path, theme_dir); + strcat(path, index_name); + + index = fopen(path, "r"); + if (!index) { + sway_log(L_ERROR, "Could not open file: %s", path); + goto fail; + } + + char *buf = NULL; + size_t n = 0; + while (!feof(index)) { + const char directories[] = "Directories"; + getline(&buf, &n, index); + if (n <= sizeof(directories) + 1) { + continue; + } + if (strncmp(directories, buf, sizeof(directories) - 1) == 0) { + char *dirstr = buf + sizeof(directories); + dirs = split_subdirs(dirstr); + break; + } + } + // Now, find the size of each dir + struct subdir *current_subdir = NULL; + while (!feof(index)) { + const char size[] = "Size"; + getline(&buf, &n, index); + + if (buf[0] == '[') { + int len = strlen(buf); + if (buf[len-1] == '\n') { + len--; + } + // replace ']' + buf[len-1] = '\0'; + + int index; + if ((index = list_seq_find(dirs, subdir_str_cmp, buf+1)) != -1) { + current_subdir = (dirs->items[index]); + } + } + + if (strncmp(size, buf, sizeof(size) - 1) == 0) { + if (current_subdir) { + current_subdir->size = atoi(buf + sizeof(size)); + } + } + } + free(buf); +fail: + free(path); + if (index) { + fclose(index); + } + return dirs; +} + +/* Returns the file of an icon given its name and size */ +static char *find_icon_file(const char *name, int size) { + int namelen = strlen(name); + list_t *dirs = find_all_theme_dirs(swaybar.config->icon_theme); + if (!dirs) { + return NULL; + } + int min_size_diff = INT_MAX; + char *current_file = NULL; + + for (int i = 0; i < dirs->length; ++i) { + char *dir = dirs->items[i]; + list_t *subdirs = find_theme_subdirs(dir); + + if (!subdirs) { + continue; + } + + for (int i = 0; i < subdirs->length; ++i) { + struct subdir *subdir = subdirs->items[i]; + + // Only use an unsized if we don't already have a + // canidate this should probably change to allow svgs + if (!subdir->size && current_file) { + continue; + } + + int size_diff = abs(size - subdir->size); + + if (size_diff >= min_size_diff) { + continue; + } + + char *path = malloc(strlen(subdir->name) + strlen(dir) + 2); + + strcpy(path, dir); + path[strlen(dir)] = '/'; + strcpy(path + strlen(dir) + 1, subdir->name); + + DIR *icons = opendir(path); + if (!icons) { + free(path); + continue; + } + + struct dirent *direntry; + while ((direntry = readdir(icons)) != NULL) { + int len = strlen(direntry->d_name); + if (len <= namelen + 2) { //must have some ext + continue; + } + if (strncmp(direntry->d_name, name, namelen) == 0) { + char *ext = direntry->d_name + namelen + 1; +#ifdef WITH_GDK_PIXBUF + if (strcmp(ext, "png") == 0 || + strcmp(ext, "xpm") == 0 || + strcmp(ext, "svg") == 0) { +#else + if (strcmp(ext, "png") == 0) { +#endif + free(current_file); + char *icon_path = malloc(strlen(path) + len + 2); + + strcpy(icon_path, path); + icon_path[strlen(path)] = '/'; + strcpy(icon_path + strlen(path) + 1, direntry->d_name); + current_file = icon_path; + min_size_diff = size_diff; + } + } + } + free(path); + closedir(icons); + } + free_flat_list(subdirs); + } + free_flat_list(dirs); + + return current_file; +} + +cairo_surface_t *find_icon(const char *name, int size) { + char *image_path = find_icon_file(name, size); + if (image_path == NULL) { + return NULL; + } + + cairo_surface_t *image = NULL; +#ifdef WITH_GDK_PIXBUF + GError *err = NULL; + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file(image_path, &err); + if (!pixbuf) { + sway_log(L_ERROR, "Failed to load icon image: %s", err->message); + } + image = gdk_cairo_image_surface_create_from_pixbuf(pixbuf); + g_object_unref(pixbuf); +#else + // TODO make svg work? cairo supports it. maybe remove gdk alltogether + image = cairo_image_surface_create_from_png(image_path); +#endif //WITH_GDK_PIXBUF + if (!image) { + sway_log(L_ERROR, "Could not read icon image"); + return NULL; + } + + free(image_path); + return image; +} diff --git a/swaybar/tray/sni.c b/swaybar/tray/sni.c new file mode 100644 index 00000000..0c46d5c0 --- /dev/null +++ b/swaybar/tray/sni.c @@ -0,0 +1,471 @@ +#define _XOPEN_SOURCE 500 +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <stdint.h> +#include <stdbool.h> +#include <dbus/dbus.h> +#include <arpa/inet.h> +#include <netinet/in.h> +#include "swaybar/tray/dbus.h" +#include "swaybar/tray/sni.h" +#include "swaybar/tray/icon.h" +#include "swaybar/bar.h" +#include "client/cairo.h" +#include "log.h" + +// Not sure what this is but cairo needs it. +static const cairo_user_data_key_t cairo_user_data_key; + +struct sni_icon_ref *sni_icon_ref_create(struct StatusNotifierItem *item, + int height) { + struct sni_icon_ref *sni_ref = malloc(sizeof(struct sni_icon_ref)); + if (!sni_ref) { + return NULL; + } + sni_ref->icon = cairo_image_surface_scale(item->image, height, height); + sni_ref->ref = item; + + return sni_ref; +} + +void sni_icon_ref_free(struct sni_icon_ref *sni_ref) { + if (!sni_ref) { + return; + } + cairo_surface_destroy(sni_ref->icon); + free(sni_ref); +} + +/* Gets the pixmap of an icon */ +static void reply_icon(DBusPendingCall *pending, void *_data) { + struct StatusNotifierItem *item = _data; + + DBusMessage *reply = dbus_pending_call_steal_reply(pending); + + if (!reply) { + sway_log(L_ERROR, "Did not get reply"); + goto bail; + } + + int message_type = dbus_message_get_type(reply); + + if (message_type == DBUS_MESSAGE_TYPE_ERROR) { + char *msg; + + dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &msg, + DBUS_TYPE_INVALID); + + sway_log(L_ERROR, "Message is error: %s", msg); + goto bail; + } + + DBusMessageIter iter; + DBusMessageIter variant; /* v[a(iiay)] */ + DBusMessageIter array; /* a(iiay) */ + DBusMessageIter d_struct; /* (iiay) */ + DBusMessageIter icon; /* ay */ + + dbus_message_iter_init(reply, &iter); + + // Each if here checks the types above before recursing + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"v\", is \"%s\"", + dbus_message_iter_get_signature(&iter)); + goto bail; + } + dbus_message_iter_recurse(&iter, &variant); + + if (strcmp("a(iiay)", dbus_message_iter_get_signature(&variant)) != 0) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"a(iiay)\", is \"%s\"", + dbus_message_iter_get_signature(&variant)); + goto bail; + } + + if (dbus_message_iter_get_element_count(&variant) == 0) { + // Can't recurse if there are no items + sway_log(L_INFO, "Item has no icon"); + goto bail; + } + dbus_message_iter_recurse(&variant, &array); + + dbus_message_iter_recurse(&array, &d_struct); + + int width; + dbus_message_iter_get_basic(&d_struct, &width); + dbus_message_iter_next(&d_struct); + + int height; + dbus_message_iter_get_basic(&d_struct, &height); + dbus_message_iter_next(&d_struct); + + int len = dbus_message_iter_get_element_count(&d_struct); + + if (!len) { + sway_log(L_ERROR, "No icon data"); + goto bail; + } + + // Also implies len % 4 == 0, useful below + if (len != width * height * 4) { + sway_log(L_ERROR, "Incorrect array size passed"); + goto bail; + } + + dbus_message_iter_recurse(&d_struct, &icon); + + int stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, width); + // FIXME support a variable stride + // (works on my machine though for all tested widths) + if (!sway_assert(stride == width * 4, "Stride must be equal to byte length")) { + goto bail; + } + + // Data is by reference, no need to free + uint8_t *message_data; + dbus_message_iter_get_fixed_array(&icon, &message_data, &len); + + uint8_t *image_data = malloc(stride * height); + if (!image_data) { + sway_log(L_ERROR, "Could not allocate memory for icon"); + goto bail; + } + + // Transform from network byte order to host byte order + // Assumptions are safe because the equality above + uint32_t *network = (uint32_t *) message_data; + uint32_t *host = (uint32_t *)image_data; + for (int i = 0; i < width * height; ++i) { + host[i] = ntohl(network[i]); + } + + cairo_surface_t *image = cairo_image_surface_create_for_data( + image_data, CAIRO_FORMAT_ARGB32, + width, height, stride); + + if (image) { + if (item->image) { + cairo_surface_destroy(item->image); + } + item->image = image; + // Free the image data on surface destruction + cairo_surface_set_user_data(image, + &cairo_user_data_key, + image_data, + free); + item->dirty = true; + dirty = true; + + dbus_message_unref(reply); + return; + } else { + sway_log(L_ERROR, "Could not create image surface"); + free(image_data); + } + +bail: + if (reply) { + dbus_message_unref(reply); + } + sway_log(L_ERROR, "Could not get icon from item"); + return; +} +static void send_icon_msg(struct StatusNotifierItem *item) { + DBusPendingCall *pending; + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + "org.freedesktop.DBus.Properties", + "Get"); + const char *iface; + if (item->kde_special_snowflake) { + iface = "org.kde.StatusNotifierItem"; + } else { + iface = "org.freedesktop.StatusNotifierItem"; + } + const char *prop = "IconPixmap"; + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); + + bool status = + dbus_connection_send_with_reply(conn, message, &pending, -1); + + dbus_message_unref(message); + + if (!(pending || status)) { + sway_log(L_ERROR, "Could not get item icon"); + return; + } + + dbus_pending_call_set_notify(pending, reply_icon, item, NULL); +} + +/* Get an icon by its name */ +static void reply_icon_name(DBusPendingCall *pending, void *_data) { + struct StatusNotifierItem *item = _data; + + DBusMessage *reply = dbus_pending_call_steal_reply(pending); + + if (!reply) { + sway_log(L_INFO, "Got no icon name reply from item"); + goto bail; + } + + int message_type = dbus_message_get_type(reply); + + if (message_type == DBUS_MESSAGE_TYPE_ERROR) { + char *msg; + + dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &msg, + DBUS_TYPE_INVALID); + + sway_log(L_INFO, "Could not get icon name: %s", msg); + goto bail; + } + + DBusMessageIter iter; /* v[s] */ + DBusMessageIter variant; /* s */ + + dbus_message_iter_init(reply, &iter); + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"v\", is \"%s\"", + dbus_message_iter_get_signature(&iter)); + goto bail; + } + dbus_message_iter_recurse(&iter, &variant); + + + if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_STRING) { + sway_log(L_ERROR, "Relpy type incorrect"); + sway_log(L_ERROR, "Should be \"s\", is \"%s\"", + dbus_message_iter_get_signature(&iter)); + goto bail; + } + + char *icon_name; + dbus_message_iter_get_basic(&variant, &icon_name); + + cairo_surface_t *image = find_icon(icon_name, 256); + + if (image) { + sway_log(L_DEBUG, "Icon for %s found with size %d", icon_name, + cairo_image_surface_get_width(image)); + if (item->image) { + cairo_surface_destroy(item->image); + } + item->image = image; + item->dirty = true; + dirty = true; + + dbus_message_unref(reply); + return; + } + +bail: + if (reply) { + dbus_message_unref(reply); + } + // Now try the pixmap + send_icon_msg(item); + return; +} +static void send_icon_name_msg(struct StatusNotifierItem *item) { + DBusPendingCall *pending; + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + "org.freedesktop.DBus.Properties", + "Get"); + const char *iface; + if (item->kde_special_snowflake) { + iface = "org.kde.StatusNotifierItem"; + } else { + iface = "org.freedesktop.StatusNotifierItem"; + } + const char *prop = "IconName"; + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); + + bool status = + dbus_connection_send_with_reply(conn, message, &pending, -1); + + dbus_message_unref(message); + + if (!(pending || status)) { + sway_log(L_ERROR, "Could not get item icon name"); + return; + } + + dbus_pending_call_set_notify(pending, reply_icon_name, item, NULL); +} + +void get_icon(struct StatusNotifierItem *item) { + send_icon_name_msg(item); +} + +void sni_activate(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { + const char *iface = + (item->kde_special_snowflake ? "org.kde.StatusNotifierItem" + : "org.freedesktop.StatusNotifierItem"); + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + iface, + "Activate"); + + dbus_message_append_args(message, + DBUS_TYPE_INT32, &x, + DBUS_TYPE_INT32, &y, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} + +void sni_context_menu(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { + const char *iface = + (item->kde_special_snowflake ? "org.kde.StatusNotifierItem" + : "org.freedesktop.StatusNotifierItem"); + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + iface, + "ContextMenu"); + + dbus_message_append_args(message, + DBUS_TYPE_INT32, &x, + DBUS_TYPE_INT32, &y, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} +void sni_secondary(struct StatusNotifierItem *item, uint32_t x, uint32_t y) { + const char *iface = + (item->kde_special_snowflake ? "org.kde.StatusNotifierItem" + : "org.freedesktop.StatusNotifierItem"); + DBusMessage *message = dbus_message_new_method_call( + item->name, + "/StatusNotifierItem", + iface, + "SecondaryActivate"); + + dbus_message_append_args(message, + DBUS_TYPE_INT32, &x, + DBUS_TYPE_INT32, &y, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} + +static void get_unique_name(struct StatusNotifierItem *item) { + // I think that we're fine being sync here becaues the message is + // directly to the message bus. Could be async though. + DBusMessage *message = dbus_message_new_method_call( + "org.freedesktop.DBus", + "/org/freedesktop/DBus", + "org.freedesktop.DBus", + "GetNameOwner"); + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &item->name, + DBUS_TYPE_INVALID); + + DBusMessage *reply = dbus_connection_send_with_reply_and_block( + conn, message, -1, NULL); + + dbus_message_unref(message); + + if (!reply) { + sway_log(L_ERROR, "Could not get unique name for item: %s", + item->name); + return; + } + + char *unique_name; + if (!dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &unique_name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error parsing method args"); + } else { + if (item->unique_name) { + free(item->unique_name); + } + item->unique_name = strdup(unique_name); + } + + dbus_message_unref(reply); +} + +struct StatusNotifierItem *sni_create(const char *name) { + struct StatusNotifierItem *item = malloc(sizeof(struct StatusNotifierItem)); + item->name = strdup(name); + item->unique_name = NULL; + item->image = NULL; + item->dirty = false; + + // If it doesn't use this name then assume that it uses the KDE spec + // This is because xembed-sni-proxy uses neither "org.freedesktop" nor + // "org.kde" and just gives us the items "unique name" + // + // We could use this to our advantage and fill out the "unique name" + // field with the given name if it is neither freedesktop or kde, but + // that's makes us rely on KDE hackyness which is bad practice + const char freedesktop_name[] = "org.freedesktop"; + if (strncmp(name, freedesktop_name, sizeof(freedesktop_name) - 1) != 0) { + item->kde_special_snowflake = true; + } else { + item->kde_special_snowflake = false; + } + + get_icon(item); + + get_unique_name(item); + + return item; +} +/* Return 0 if `item` has a name of `str` */ +int sni_str_cmp(const void *_item, const void *_str) { + const struct StatusNotifierItem *item = _item; + const char *str = _str; + + return strcmp(item->name, str); +} +/* Returns 0 if `item` has a unique name of `str` */ +int sni_uniq_cmp(const void *_item, const void *_str) { + const struct StatusNotifierItem *item = _item; + const char *str = _str; + + if (!item->unique_name) { + return false; + } + return strcmp(item->unique_name, str); +} +void sni_free(struct StatusNotifierItem *item) { + if (!item) { + return; + } + free(item->name); + if (item->unique_name) { + free(item->unique_name); + } + if (item->image) { + cairo_surface_destroy(item->image); + } + free(item); +} diff --git a/swaybar/tray/sni_watcher.c b/swaybar/tray/sni_watcher.c new file mode 100644 index 00000000..388e181d --- /dev/null +++ b/swaybar/tray/sni_watcher.c @@ -0,0 +1,487 @@ +#define _XOPEN_SOURCE 500 +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <dbus/dbus.h> +#include "swaybar/tray/dbus.h" +#include "list.h" +#include "log.h" + +static list_t *items = NULL; +static list_t *hosts = NULL; + +/** + * Describes the function of the StatusNotifierWatcher + * See https://freedesktop.org/wiki/Specifications/StatusNotifierItem/StatusNotifierWatcher/ + * + * We also implement KDE's special snowflake protocol, it's like this but with + * all occurrences 'freedesktop' replaced with 'kde'. There is no KDE introspect. + */ +static const char *interface_xml = + "<!DOCTYPE node PUBLIC '-//freedesktop//DTD D-BUS Object Introspection 1.0//EN'" + "'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd'>" + "<node>" + " <interface name='org.freedesktop.DBus.Introspectable'>" + " <method name='Introspect'>" + " <arg name='xml_data' direction='out' type='s'/>" + " </method>" + " </interface>" + " <interface name='org.freedesktop.DBus.Properties'>" + " <method name='Get'>" + " <arg name='interface' direction='in' type='s'/>" + " <arg name='propname' direction='in' type='s'/>" + " <arg name='value' direction='out' type='v'/>" + " </method>" + " <method name='Set'>" + " <arg name='interface' direction='in' type='s'/>" + " <arg name='propname' direction='in' type='s'/>" + " <arg name='value' direction='in' type='v'/>" + " </method>" + " <method name='GetAll'>" + " <arg name='interface' direction='in' type='s'/>" + " <arg name='props' direction='out' type='a{sv}'/>" + " </method>" + " </interface>" + " <interface name='org.freedesktop.StatusNotifierWatcher'>" + " <method name='RegisterStatusNotifierItem'>" + " <arg type='s' name='service' direction='in'/>" + " </method>" + " <method name='RegisterStatusNotifierHost'>" + " <arg type='s' name='service' direction='in'/>" + " </method>" + " <property name='RegisteredStatusNotifierItems' type='as' access='read'/>" + " <property name='IsStatusNotifierHostRegistered' type='b' access='read'/>" + " <property name='ProtocolVersion' type='i' access='read'/>" + " <signal name='StatusNotifierItemRegistered'>" + " <arg type='s' name='service' direction='out'/>" + " </signal>" + " <signal name='StatusNotifierItemUnregistered'>" + " <arg type='s' name='service' direction='out'/>" + " </signal>" + " <signal name='StatusNotifierHostRegistered'>" + " <arg type='' name='service' direction='out'/>" + " </signal>" + " </interface>" + "</node>"; + +static void host_registered_signal(DBusConnection *connection) { + // Send one signal for each protocol + DBusMessage *signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierHostRegistered"); + + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); + + + signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierHostRegistered"); + + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); +} +static void item_registered_signal(DBusConnection *connection, const char *name) { + DBusMessage *signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemRegistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); + + signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierItemRegistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); +} +static void item_unregistered_signal(DBusConnection *connection, const char *name) { + DBusMessage *signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemUnregistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); + + signal = dbus_message_new_signal( + "/StatusNotifierWatcher", + "org.kde.StatusNotifierWatcher", + "StatusNotifierItemUnregistered"); + dbus_message_append_args(signal, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, signal, NULL); + dbus_message_unref(signal); +} + +static void respond_to_introspect(DBusConnection *connection, DBusMessage *request) { + DBusMessage *reply; + + reply = dbus_message_new_method_return(request); + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &interface_xml, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static void register_item(DBusConnection *connection, DBusMessage *message) { + DBusError error; + char *name; + + dbus_error_init(&error); + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error parsing method args: %s\n", error.message); + } + + name = strdup(name); + sway_log(L_INFO, "RegisterStatusNotifierItem called with \"%s\"\n", name); + + // Don't add duplicate or not real item + if (list_seq_find(items, (int (*)(const void *, const void *))strcmp, name) != -1) { + return; + } + if (!dbus_bus_name_has_owner(connection, name, &error)) { + return; + } + + list_add(items, name); + item_registered_signal(connection, name); + + // It's silly, but xembedsniproxy wants a reply for this function + DBusMessage *reply = dbus_message_new_method_return(message); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static void register_host(DBusConnection *connection, DBusMessage *message) { + DBusError error; + char *name; + + dbus_error_init(&error); + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error parsing method args: %s\n", error.message); + } + + sway_log(L_INFO, "RegisterStatusNotifierHost called with \"%s\"\n", name); + + // Don't add duplicate or not real host + if (list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name) != -1) { + return; + } + if (!dbus_bus_name_has_owner(connection, name, &error)) { + return; + } + + list_add(hosts, strdup(name)); + host_registered_signal(connection); +} + +static void get_property(DBusConnection *connection, DBusMessage *message) { + DBusError error; + char *interface; + char *property; + + dbus_error_init(&error); + if (!dbus_message_get_args(message, &error, + DBUS_TYPE_STRING, &interface, + DBUS_TYPE_STRING, &property, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error parsing prop args: %s\n", error.message); + return; + } + + if (strcmp(property, "RegisteredStatusNotifierItems") == 0) { + sway_log(L_INFO, "Replying with items\n"); + DBusMessage *reply; + reply = dbus_message_new_method_return(message); + DBusMessageIter iter; + DBusMessageIter sub; + DBusMessageIter subsub; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, + "as", &sub); + dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, + "s", &subsub); + + for (int i = 0; i < items->length; ++i) { + dbus_message_iter_append_basic(&subsub, + DBUS_TYPE_STRING, &items->items[i]); + } + + dbus_message_iter_close_container(&sub, &subsub); + dbus_message_iter_close_container(&iter, &sub); + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } else if (strcmp(property, "IsStatusNotifierHostRegistered") == 0) { + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter sub; + int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1; + + reply = dbus_message_new_method_return(message); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, + "b", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_BOOLEAN, ®istered); + + dbus_message_iter_close_container(&iter, &sub); + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } else if (strcmp(property, "ProtocolVersion") == 0) { + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter sub; + const int version = 0; + + reply = dbus_message_new_method_return(message); + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_VARIANT, + "i", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_INT32, &version); + + dbus_message_iter_close_container(&iter, &sub); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } +} + +static void set_property(DBusConnection *connection, DBusMessage *message) { + // All properties are read only and we don't allow new properties + return; +} + +static void get_all(DBusConnection *connection, DBusMessage *message) { + DBusMessage *reply; + reply = dbus_message_new_method_return(message); + DBusMessageIter iter; /* a{v} */ + DBusMessageIter arr; + DBusMessageIter dict; + DBusMessageIter sub; + DBusMessageIter subsub; + int registered = (hosts == NULL || hosts->length == 0) ? 0 : 1; + const int version = 0; + const char *prop; + + // Could clean this up with a function for each prop + dbus_message_iter_init_append(reply, &iter); + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + "{sv}", &arr); + + prop = "RegisteredStatusNotifierItems"; + dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, + NULL, &dict); + dbus_message_iter_append_basic(&dict, + DBUS_TYPE_STRING, &prop); + dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, + "as", &sub); + dbus_message_iter_open_container(&sub, DBUS_TYPE_ARRAY, + "s", &subsub); + for (int i = 0; i < items->length; ++i) { + dbus_message_iter_append_basic(&subsub, + DBUS_TYPE_STRING, &items->items[i]); + } + dbus_message_iter_close_container(&sub, &subsub); + dbus_message_iter_close_container(&dict, &sub); + dbus_message_iter_close_container(&arr, &dict); + + prop = "IsStatusNotifierHostRegistered"; + dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, + NULL, &dict); + dbus_message_iter_append_basic(&dict, + DBUS_TYPE_STRING, &prop); + dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, + "b", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_BOOLEAN, ®istered); + dbus_message_iter_close_container(&dict, &sub); + dbus_message_iter_close_container(&arr, &dict); + + prop = "ProtocolVersion"; + dbus_message_iter_open_container(&arr, DBUS_TYPE_DICT_ENTRY, + NULL, &dict); + dbus_message_iter_append_basic(&dict, + DBUS_TYPE_STRING, &prop); + dbus_message_iter_open_container(&dict, DBUS_TYPE_VARIANT, + "i", &sub); + dbus_message_iter_append_basic(&sub, + DBUS_TYPE_INT32, &version); + dbus_message_iter_close_container(&dict, &sub); + dbus_message_iter_close_container(&arr, &dict); + + dbus_message_iter_close_container(&iter, &arr); + + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); +} + +static DBusHandlerResult message_handler(DBusConnection *connection, + DBusMessage *message, void *data) { + const char *interface_name = dbus_message_get_interface(message); + const char *member_name = dbus_message_get_member(message); + + // In order of the xml above + if (strcmp(interface_name, "org.freedesktop.DBus.Introspectable") == 0 && + strcmp(member_name, "Introspect") == 0) { + // We don't have an introspect for KDE + respond_to_introspect(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(interface_name, "org.freedesktop.DBus.Properties") == 0) { + if (strcmp(member_name, "Get") == 0) { + get_property(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(member_name, "Set") == 0) { + set_property(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(member_name, "GetAll") == 0) { + get_all(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } else if (strcmp(interface_name, "org.freedesktop.StatusNotifierWatcher") == 0 || + strcmp(interface_name, "org.kde.StatusNotifierWatcher") == 0) { + if (strcmp(member_name, "RegisterStatusNotifierItem") == 0) { + register_item(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else if (strcmp(member_name, "RegisterStatusNotifierHost") == 0) { + register_host(connection, message); + return DBUS_HANDLER_RESULT_HANDLED; + } else { + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static DBusHandlerResult signal_handler(DBusConnection *connection, + DBusMessage *message, void *_data) { + if (dbus_message_is_signal(message, "org.freedesktop.DBus", "NameOwnerChanged")) { + // Only eat the message if it is name that we are watching + const char *name; + const char *old_owner; + const char *new_owner; + int index; + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_STRING, &old_owner, + DBUS_TYPE_STRING, &new_owner, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error getting LostName args"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + if (strcmp(new_owner, "") != 0) { + // Name is not lost + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + if ((index = list_seq_find(items, (int (*)(const void *, const void *))strcmp, name)) != -1) { + sway_log(L_INFO, "Status Notifier Item lost %s", name); + free(items->items[index]); + list_del(items, index); + item_unregistered_signal(connection, name); + + return DBUS_HANDLER_RESULT_HANDLED; + } + if ((index = list_seq_find(hosts, (int (*)(const void *, const void *))strcmp, name)) != -1) { + sway_log(L_INFO, "Status Notifier Host lost %s", name); + free(hosts->items[index]); + list_del(hosts, index); + + return DBUS_HANDLER_RESULT_HANDLED; + } + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static const DBusObjectPathVTable vtable = { + .message_function = message_handler, + .unregister_function = NULL, +}; + +int init_sni_watcher() { + DBusError error; + dbus_error_init(&error); + if (!conn) { + sway_log(L_ERROR, "Connection is null, cannot initiate StatusNotifierWatcher"); + return -1; + } + + items = create_list(); + hosts = create_list(); + + int status = dbus_bus_request_name(conn, "org.freedesktop.StatusNotifierWatcher", + DBUS_NAME_FLAG_REPLACE_EXISTING, + &error); + if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + sway_log(L_DEBUG, "Got watcher name"); + } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) { + sway_log(L_INFO, "Could not get watcher name, it may start later"); + } + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus err getting watcher name: %s\n", error.message); + return -1; + } + + status = dbus_bus_request_name(conn, "org.kde.StatusNotifierWatcher", + DBUS_NAME_FLAG_REPLACE_EXISTING, + &error); + if (status == DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + sway_log(L_DEBUG, "Got kde watcher name"); + } else if (status == DBUS_REQUEST_NAME_REPLY_IN_QUEUE) { + sway_log(L_INFO, "Could not get kde watcher name, it may start later"); + } + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus err getting kde watcher name: %s\n", error.message); + return -1; + } + + dbus_connection_try_register_object_path(conn, + "/StatusNotifierWatcher", + &vtable, NULL, &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err: %s\n", error.message); + return -1; + } + + dbus_bus_add_match(conn, + "type='signal',\ + sender='org.freedesktop.DBus',\ + interface='org.freedesktop.DBus',\ + member='NameOwnerChanged'", + &error); + + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "DBus error getting match args: %s", error.message); + } + + dbus_connection_add_filter(conn, signal_handler, NULL, NULL); + return 0; +} diff --git a/swaybar/tray/tray.c b/swaybar/tray/tray.c new file mode 100644 index 00000000..00f1a44f --- /dev/null +++ b/swaybar/tray/tray.c @@ -0,0 +1,393 @@ +#define _XOPEN_SOURCE 500 +#include <unistd.h> +#include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <dbus/dbus.h> +#include "swaybar/bar.h" +#include "swaybar/tray/tray.h" +#include "swaybar/tray/dbus.h" +#include "swaybar/tray/sni.h" +#include "swaybar/tray/sni_watcher.h" +#include "swaybar/bar.h" +#include "swaybar/config.h" +#include "list.h" +#include "log.h" + +struct tray *tray; + +static void register_host(char *name) { + DBusMessage *message; + + message = dbus_message_new_method_call( + "org.freedesktop.StatusNotifierWatcher", + "/StatusNotifierWatcher", + "org.freedesktop.StatusNotifierWatcher", + "RegisterStatusNotifierHost"); + if (!message) { + sway_log(L_ERROR, "Cannot allocate dbus method call"); + return; + } + + dbus_message_append_args(message, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID); + + dbus_connection_send(conn, message, NULL); + + dbus_message_unref(message); +} + +static void get_items_reply(DBusPendingCall *pending, void *_data) { + DBusMessage *reply = dbus_pending_call_steal_reply(pending); + + if (!reply) { + sway_log(L_ERROR, "Got no items reply from sni watcher"); + goto bail; + } + + int message_type = dbus_message_get_type(reply); + + if (message_type == DBUS_MESSAGE_TYPE_ERROR) { + char *msg; + + dbus_message_get_args(reply, NULL, + DBUS_TYPE_STRING, &msg, + DBUS_TYPE_INVALID); + + sway_log(L_ERROR, "Message is error: %s", msg); + goto bail; + } + + DBusMessageIter iter; + DBusMessageIter variant; + DBusMessageIter array; + + dbus_message_iter_init(reply, &iter); + if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) { + sway_log(L_ERROR, "Replyed with wrong type, not v(as)"); + goto bail; + } + dbus_message_iter_recurse(&iter, &variant); + if (dbus_message_iter_get_arg_type(&variant) != DBUS_TYPE_ARRAY || + dbus_message_iter_get_element_type(&variant) != DBUS_TYPE_STRING) { + sway_log(L_ERROR, "Replyed with wrong type, not v(as)"); + goto bail; + } + + // Clear list + list_foreach(tray->items, (void (*)(void *))sni_free); + list_free(tray->items); + tray->items = create_list(); + + // O(n) function, could be faster dynamically reading values + int len = dbus_message_iter_get_element_count(&variant); + + dbus_message_iter_recurse(&variant, &array); + for (int i = 0; i < len; i++) { + const char *name; + dbus_message_iter_get_basic(&array, &name); + + struct StatusNotifierItem *item = sni_create(name); + + sway_log(L_DEBUG, "Item registered with host: %s", name); + list_add(tray->items, item); + dirty = true; + } + +bail: + dbus_message_unref(reply); + return; +} +static void get_items() { + DBusPendingCall *pending; + DBusMessage *message = dbus_message_new_method_call( + "org.freedesktop.StatusNotifierWatcher", + "/StatusNotifierWatcher", + "org.freedesktop.DBus.Properties", + "Get"); + + const char *iface = "org.freedesktop.StatusNotifierWatcher"; + const char *prop = "RegisteredStatusNotifierItems"; + dbus_message_append_args(message, + DBUS_TYPE_STRING, &iface, + DBUS_TYPE_STRING, &prop, + DBUS_TYPE_INVALID); + + bool status = + dbus_connection_send_with_reply(conn, message, &pending, -1); + dbus_message_unref(message); + + if (!(pending || status)) { + sway_log(L_ERROR, "Could not get items"); + return; + } + + dbus_pending_call_set_notify(pending, get_items_reply, NULL, NULL); +} + +static DBusHandlerResult signal_handler(DBusConnection *connection, + DBusMessage *message, void *_data) { + if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemRegistered")) { + const char *name; + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error getting StatusNotifierItemRegistered args"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + if (list_seq_find(tray->items, sni_str_cmp, name) == -1) { + struct StatusNotifierItem *item = sni_create(name); + + list_add(tray->items, item); + dirty = true; + } + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierWatcher", + "StatusNotifierItemUnregistered")) { + const char *name; + if (!dbus_message_get_args(message, NULL, + DBUS_TYPE_STRING, &name, + DBUS_TYPE_INVALID)) { + sway_log(L_ERROR, "Error getting StatusNotifierItemUnregistered args"); + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + } + + int index; + if ((index = list_seq_find(tray->items, sni_str_cmp, name)) != -1) { + sni_free(tray->items->items[index]); + list_del(tray->items, index); + dirty = true; + } else { + // If it's not in our list, then our list is incorrect. + // Fetch all items again + sway_log(L_INFO, "Host item list incorrect, refreshing"); + get_items(); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } else if (dbus_message_is_signal(message, "org.freedesktop.StatusNotifierItem", + "NewIcon") || dbus_message_is_signal(message, + "org.kde.StatusNotifierItem", "NewIcon")) { + const char *name; + int index; + struct StatusNotifierItem *item; + + name = dbus_message_get_sender(message); + if ((index = list_seq_find(tray->items, sni_uniq_cmp, name)) != -1) { + item = tray->items->items[index]; + sway_log(L_INFO, "NewIcon signal from item %s", item->name); + get_icon(item); + } + + return DBUS_HANDLER_RESULT_HANDLED; + } + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static int init_host() { + tray = (struct tray *)malloc(sizeof(tray)); + + tray->items = create_list(); + + DBusError error; + dbus_error_init(&error); + char *name = NULL; + if (!conn) { + sway_log(L_ERROR, "Connection is null, cannot init SNI host"); + goto err; + } + name = calloc(sizeof(char), 256); + + if (!name) { + sway_log(L_ERROR, "Cannot allocate name"); + goto err; + } + + pid_t pid = getpid(); + if (snprintf(name, 256, "org.freedesktop.StatusNotifierHost-%d", pid) + >= 256) { + sway_log(L_ERROR, "Cannot get host name because string is too short." + "This should not happen"); + goto err; + } + + // We want to be the sole owner of this name + if (dbus_bus_request_name(conn, name, DBUS_NAME_FLAG_DO_NOT_QUEUE, + &error) != DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER) { + sway_log(L_ERROR, "Cannot get host name and start the tray"); + goto err; + } + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "Dbus err getting host name: %s\n", error.message); + goto err; + } + sway_log(L_DEBUG, "Got host name"); + + register_host(name); + + get_items(); + + // Perhaps use addmatch helper functions like wlc does? + dbus_bus_add_match(conn, + "type='signal',\ + sender='org.freedesktop.StatusNotifierWatcher',\ + member='StatusNotifierItemRegistered'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err: %s", error.message); + goto err; + } + dbus_bus_add_match(conn, + "type='signal',\ + sender='org.freedesktop.StatusNotifierWatcher',\ + member='StatusNotifierItemUnregistered'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err: %s", error.message); + return -1; + } + + // SNI matches + dbus_bus_add_match(conn, + "type='signal',\ + interface='org.freedesktop.StatusNotifierItem',\ + member='NewIcon'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err %s", error.message); + goto err; + } + dbus_bus_add_match(conn, + "type='signal',\ + interface='org.kde.StatusNotifierItem',\ + member='NewIcon'", + &error); + if (dbus_error_is_set(&error)) { + sway_log(L_ERROR, "dbus_err %s", error.message); + goto err; + } + + dbus_connection_add_filter(conn, signal_handler, NULL, NULL); + + free(name); + return 0; + +err: + // TODO better handle errors + free(name); + return -1; +} + +void tray_mouse_event(struct output *output, int x, int y, + uint32_t button, uint32_t state) { + + struct window *window = output->window; + uint32_t tray_padding = swaybar.config->tray_padding; + int tray_width = window->width * window->scale; + + for (int i = 0; i < output->items->length; ++i) { + struct sni_icon_ref *item = + output->items->items[i]; + int icon_width = cairo_image_surface_get_width(item->icon); + + tray_width -= tray_padding; + if (x <= tray_width && x >= tray_width - icon_width) { + if (button == swaybar.config->activate_button) { + sni_activate(item->ref, x, y); + } else if (button == swaybar.config->context_button) { + sni_context_menu(item->ref, x, y); + } else if (button == swaybar.config->secondary_button) { + sni_secondary(item->ref, x, y); + } + break; + } + tray_width -= icon_width; + } +} + +uint32_t tray_render(struct output *output, struct config *config) { + struct window *window = output->window; + cairo_t *cairo = window->cairo; + + // Tray icons + uint32_t tray_padding = config->tray_padding; + uint32_t tray_width = window->width * window->scale; + const int item_size = (window->height * window->scale) - (2 * tray_padding); + + if (item_size < 0) { + // Can't render items if the padding is too large + return tray_width; + } + + if (config->tray_output && strcmp(config->tray_output, output->name) != 0) { + return tray_width; + } + + for (int i = 0; i < tray->items->length; ++i) { + struct StatusNotifierItem *item = + tray->items->items[i]; + if (!item->image) { + continue; + } + + struct sni_icon_ref *render_item = NULL; + int j; + for (j = i; j < output->items->length; ++j) { + struct sni_icon_ref *ref = + output->items->items[j]; + if (ref->ref == item) { + render_item = ref; + break; + } else { + sni_icon_ref_free(ref); + list_del(output->items, j); + } + } + + if (!render_item) { + render_item = sni_icon_ref_create(item, item_size); + list_add(output->items, render_item); + } else if (item->dirty) { + // item needs re-render + sni_icon_ref_free(render_item); + output->items->items[j] = render_item = + sni_icon_ref_create(item, item_size); + } + + tray_width -= tray_padding; + tray_width -= item_size; + + cairo_operator_t op = cairo_get_operator(cairo); + cairo_set_operator(cairo, CAIRO_OPERATOR_OVER); + cairo_set_source_surface(cairo, render_item->icon, tray_width, tray_padding); + cairo_rectangle(cairo, tray_width, tray_padding, item_size, item_size); + cairo_fill(cairo); + cairo_set_operator(cairo, op); + + item->dirty = false; + } + + + if (tray_width != window->width * window->scale) { + tray_width -= tray_padding; + } + + return tray_width; +} + +void init_tray(struct bar *bar) { + if (!bar->config->tray_output || strcmp(bar->config->tray_output, "none") != 0) { + /* Connect to the D-Bus */ + dbus_init(); + + /* Start the SNI watcher */ + init_sni_watcher(); + + /* Start the SNI host */ + init_host(); + } +} diff --git a/swaygrab/json.c b/swaygrab/json.c index 563b8ba3..80dae299 100644 --- a/swaygrab/json.c +++ b/swaygrab/json.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #include <string.h> #include <stdio.h> #include <stdbool.h> diff --git a/swaygrab/main.c b/swaygrab/main.c index f901d318..413de1fa 100644 --- a/swaygrab/main.c +++ b/swaygrab/main.c @@ -1,4 +1,4 @@ -#define _XOPEN_SOURCE 500 +#define _XOPEN_SOURCE 700 #define _POSIX_C_SOURCE 199309L #include <stdio.h> #include <stdbool.h> diff --git a/wayland/cairo.c b/wayland/cairo.c index ba439d9d..193205b1 100644 --- a/wayland/cairo.c +++ b/wayland/cairo.c @@ -8,6 +8,25 @@ void cairo_set_source_u32(cairo_t *cairo, uint32_t color) { (color >> (0*8) & 0xFF) / 255.0); } +cairo_surface_t *cairo_image_surface_scale(cairo_surface_t *image, int width, int height) { + int image_width = cairo_image_surface_get_width(image); + int image_height = cairo_image_surface_get_height(image); + + cairo_surface_t *new = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); + + cairo_t *cairo = cairo_create(new); + + cairo_scale(cairo, (double) width / image_width, (double) height / image_height); + + cairo_set_source_surface(cairo, image, 0, 0); + cairo_paint(cairo); + + cairo_destroy(cairo); + + return new; +} + #ifdef WITH_GDK_PIXBUF #include <gdk-pixbuf/gdk-pixbuf.h> diff --git a/wayland/pango.c b/wayland/pango.c index 702ab15c..f9eec98c 100644 --- a/wayland/pango.c +++ b/wayland/pango.c @@ -5,6 +5,7 @@ #include <string.h> #include <stdio.h> #include <stdbool.h> +#include <stdint.h> #include "log.h" PangoLayout *get_pango_layout(cairo_t *cairo, const char *font, const char *text, |