wob-0.12/0000755000175100001710000000000014105527345011570 5ustar runnerdockerwob-0.12/README.md0000644000175100001710000000755514105527344013062 0ustar runnerdocker# wob — Wayland Overlay Bar [![Build Status](https://github.com/francma/wob/workflows/test/badge.svg)](https://github.com/francma/wob/actions) ![preview](https://martinfranc.eu/wob-preview.svg) A lightweight overlay volume/backlight/progress/anything bar for wlroots based Wayland compositors (requrires support for `wlr_layer_shell_unstable_v1`). This project is inspired by [xob - X Overlay Bar](https://github.com/florentc/xob). ## Release signatures Releases are signed with [5C6DA024DDE27178073EA103F4B432D5D67990E3](https://keys.openpgp.org/vks/v1/by-fingerprint/5C6DA024DDE27178073EA103F4B432D5D67990E3) and published on [GitHub](https://github.com/francma/wob/releases). ## Installation ### Compiling from source Install dependencies: - wayland - wayland-protocols \* - meson \* - [scdoc](https://git.sr.ht/~sircmpwn/scdoc) (optional: man page) \* - [libseccomp](https://github.com/seccomp/libseccomp) (optional: Linux kernel syscall filtering) \* \* _compile-time dependecy_ Run these commands: ``` git clone git@github.com:francma/wob.git cd wob meson build ninja -C build sudo ninja -C build install ``` ### From packages [![Packaging status](https://repology.org/badge/tiny-repos/wob.svg)](https://repology.org/project/wob/versions) ## Usage Launch wob in a terminal, enter a value (positive integer), press return. ``` wob ``` ### General case You may manage a bar for audio volume, backlight intensity, or whatever, using a named pipe. Create a named pipe, e.g. /tmp/wobpipe, on your filesystem using. ``` mkfifo /tmp/wobpipe ``` Connect the named pipe to the standard input of an wob instance. ``` tail -f /tmp/wobpipe | wob ``` Set up your environment so that after updating audio volume, backlight intensity, or whatever, to a new value like 43, it writes that value into the pipe: ``` echo 43 > /tmp/wobpipe ``` Adapt this use-case to your workflow (scripts, callbacks, or keybindings handled by the window manager). See [man page](https://github.com/francma/wob/blob/master/wob.1.scd) for styling and positioning options. ### Sway WM example Add these lines to your Sway config file: ``` set $WOBSOCK $XDG_RUNTIME_DIR/wob.sock exec mkfifo $WOBSOCK && tail -f $WOBSOCK | wob ``` Volume using alsa: ``` bindsym XF86AudioRaiseVolume exec amixer sset Master 5%+ | sed -En 's/.*\[([0-9]+)%\].*/\1/p' | head -1 > $WOBSOCK bindsym XF86AudioLowerVolume exec amixer sset Master 5%- | sed -En 's/.*\[([0-9]+)%\].*/\1/p' | head -1 > $WOBSOCK bindsym XF86AudioMute exec amixer sset Master toggle | sed -En '/\[on\]/ s/.*\[([0-9]+)%\].*/\1/ p; /\[off\]/ s/.*/0/p' | head -1 > $WOBSOCK ``` Volume using pulse audio: ``` bindsym XF86AudioRaiseVolume exec pamixer -ui 2 && pamixer --get-volume > $WOBSOCK bindsym XF86AudioLowerVolume exec pamixer -ud 2 && pamixer --get-volume > $WOBSOCK bindsym XF86AudioMute exec pamixer --toggle-mute && ( pamixer --get-mute && echo 0 > $WOBSOCK ) || pamixer --get-volume > $WOBSOCK ``` Brightness using [haikarainen/light](https://github.com/haikarainen/light): ``` bindsym XF86MonBrightnessUp exec light -A 5 && light -G | cut -d'.' -f1 > $WOBSOCK bindsym XF86MonBrightnessDown exec light -U 5 && light -G | cut -d'.' -f1 > $WOBSOCK ``` Brightness using [brightnessctl](https://github.com/Hummer12007/brightnessctl): ``` bindsym XF86MonBrightnessDown exec brightnessctl set 5%- | sed -En 's/.*\(([0-9]+)%\).*/\1/p' > $WOBSOCK bindsym XF86MonBrightnessUp exec brightnessctl set +5% | sed -En 's/.*\(([0-9]+)%\).*/\1/p' > $WOBSOCK ``` #### Systemd Add this line to your config file: ``` exec systemctl --user import-environment DISPLAY WAYLAND_DISPLAY SWAYSOCK ``` Copy systemd unit files (if not provided by your distribution package): ``` cp contrib/systemd/wob.{service,socket} ~/.local/share/systemd/user/ systemctl daemon-reload --user ``` Enable systemd wob socket: ``` systemctl enable --now --user wob.socket ``` ## License ISC, see [LICENSE](/LICENSE). wob-0.12/wob.1.scd0000644000175100001710000000432114105527344013210 0ustar runnerdockerwob(1) # NAME wob - Wayland Overlay Bar # DESCRIPTION wob is a lightweight overlay volume/backlight/progress/anything bar for Wayland. # SYNOPSIS *wob* [options...] # OPTIONS *-h, --help* Show help message and quit. *--version* Show the version number and quit. *-v* Increase verbosity of messages, defaults to errors and warnings only. *-t --timeout* Hide wob after milliseconds, defaults to 1000. *-m --max* <%> Define the maximum percentage, defaults to 100. *-W --width* Define bar width in pixels, defaults to 400. *-H --height* Define bar height in pixels, defaults to 50. *-o --offset* Define border offset in pixels, defaults to 4. *-b --border* Define border size in pixels, defaults to 4. *-p --padding* Define bar padding in pixels, defaults to 4. *-a --anchor* Define anchor point, one of 'top', 'left', 'right', 'bottom', 'center' (default). May be specified multiple times. *-M --margin* Define anchor margin in pixels, defaults to 0. *-O --output* Define output to show bar on or '\*' for all. If ommited, focused output is chosen. May be specified multiple times. *--border-color* <#RRGGBBAA> Define border color, defaults to #FFFFFFFF. *--background-color* <#RRGGBBAA> Define background color, defaults to #000000FF. *--bar-color* <#RRGGBBAA> Define bar color, defaults to #FFFFFFFF. *--overflow-mode * Change the overflow mode. Valid options are `none`, `wrap`, and `nowrap`. *--overflow-bar-color* <#AARRGGBB> Define overflow bar color, defaults to #FFFF0000 *--overflow-background-color* <#AARRGGBB> Define overflow background color, defaults to #FF000000 *--overflow-border-color* <#AARRGGBB> Define overflow border color, defaults to #FFFFFFFF # USAGE Wob reads values to display from standart input in the following formats: or <#background_color> <#border_color> <#bar_color> Where is number in interval from 0 to *--max* and <#\*color> is color in #RRGGBBAA format. # ENVIRONMENT The following environment variables have an effect on wob: *WOB_DISABLE_PLEDGE* Disable seccomp syscall filtering on Linux. Set this if you are having trouble running wob with tools like valgrind or strace. wob-0.12/LICENSE0000644000175100001710000000135014105527344012573 0ustar runnerdockerISC License Copyright (c) 2019, Martin Franc Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. wob-0.12/protocols/0000755000175100001710000000000014105527344013613 5ustar runnerdockerwob-0.12/protocols/wlr-layer-shell-unstable-v1.xml0000644000175100001710000003325114105527344021523 0ustar runnerdocker Copyright © 2017 Drew DeVault Permission to use, copy, modify, distribute, and sell this software and its documentation for any purpose is hereby granted without fee, provided that the above copyright notice appear in all copies and that both that copyright notice and this permission notice appear in supporting documentation, and that the name of the copyright holders not be used in advertising or publicity pertaining to distribution of the software without specific, written prior permission. The copyright holders make no representations about the suitability of this software for any purpose. It is provided "as is" without express or implied warranty. THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. Clients can use this interface to assign the surface_layer role to wl_surfaces. Such surfaces are assigned to a "layer" of the output and rendered with a defined z-depth respective to each other. They may also be anchored to the edges and corners of a screen and specify input handling semantics. This interface should be suitable for the implementation of many desktop shell components, and a broad number of other applications that interact with the desktop. Create a layer surface for an existing surface. This assigns the role of layer_surface, or raises a protocol error if another role is already assigned. Creating a layer surface from a wl_surface which has a buffer attached or committed is a client error, and any attempts by a client to attach or manipulate a buffer prior to the first layer_surface.configure call must also be treated as errors. You may pass NULL for output to allow the compositor to decide which output to use. Generally this will be the one that the user most recently interacted with. Clients can specify a namespace that defines the purpose of the layer surface. These values indicate which layers a surface can be rendered in. They are ordered by z depth, bottom-most first. Traditional shell surfaces will typically be rendered between the bottom and top layers. Fullscreen shell surfaces are typically rendered at the top layer. Multiple surfaces can share a single layer, and ordering within a single layer is undefined. An interface that may be implemented by a wl_surface, for surfaces that are designed to be rendered as a layer of a stacked desktop-like environment. Layer surface state (layer, size, anchor, exclusive zone, margin, interactivity) is double-buffered, and will be applied at the time wl_surface.commit of the corresponding wl_surface is called. Sets the size of the surface in surface-local coordinates. The compositor will display the surface centered with respect to its anchors. If you pass 0 for either value, the compositor will assign it and inform you of the assignment in the configure event. You must set your anchor to opposite edges in the dimensions you omit; not doing so is a protocol error. Both values are 0 by default. Size is double-buffered, see wl_surface.commit. Requests that the compositor anchor the surface to the specified edges and corners. If two orthogonal edges are specified (e.g. 'top' and 'left'), then the anchor point will be the intersection of the edges (e.g. the top left corner of the output); otherwise the anchor point will be centered on that edge, or in the center if none is specified. Anchor is double-buffered, see wl_surface.commit. Requests that the compositor avoids occluding an area with other surfaces. The compositor's use of this information is implementation-dependent - do not assume that this region will not actually be occluded. A positive value is only meaningful if the surface is anchored to one edge or an edge and both perpendicular edges. If the surface is not anchored, anchored to only two perpendicular edges (a corner), anchored to only two parallel edges or anchored to all edges, a positive value will be treated the same as zero. A positive zone is the distance from the edge in surface-local coordinates to consider exclusive. Surfaces that do not wish to have an exclusive zone may instead specify how they should interact with surfaces that do. If set to zero, the surface indicates that it would like to be moved to avoid occluding surfaces with a positive exclusive zone. If set to -1, the surface indicates that it would not like to be moved to accommodate for other surfaces, and the compositor should extend it all the way to the edges it is anchored to. For example, a panel might set its exclusive zone to 10, so that maximized shell surfaces are not shown on top of it. A notification might set its exclusive zone to 0, so that it is moved to avoid occluding the panel, but shell surfaces are shown underneath it. A wallpaper or lock screen might set their exclusive zone to -1, so that they stretch below or over the panel. The default value is 0. Exclusive zone is double-buffered, see wl_surface.commit. Requests that the surface be placed some distance away from the anchor point on the output, in surface-local coordinates. Setting this value for edges you are not anchored to has no effect. The exclusive zone includes the margin. Margin is double-buffered, see wl_surface.commit. Set to 1 to request that the seat send keyboard events to this layer surface. For layers below the shell surface layer, the seat will use normal focus semantics. For layers above the shell surface layers, the seat will always give exclusive keyboard focus to the top-most layer which has keyboard interactivity set to true. Layer surfaces receive pointer, touch, and tablet events normally. If you do not want to receive them, set the input region on your surface to an empty region. Events is double-buffered, see wl_surface.commit. This assigns an xdg_popup's parent to this layer_surface. This popup should have been created via xdg_surface::get_popup with the parent set to NULL, and this request must be invoked before committing the popup's initial state. See the documentation of xdg_popup for more details about what an xdg_popup is and how it is used. When a configure event is received, if a client commits the surface in response to the configure event, then the client must make an ack_configure request sometime before the commit request, passing along the serial of the configure event. If the client receives multiple configure events before it can respond to one, it only has to ack the last configure event. A client is not required to commit immediately after sending an ack_configure request - it may even ack_configure several times before its next surface commit. A client may send multiple ack_configure requests before committing, but only the last request sent before a commit indicates which configure event the client really is responding to. This request destroys the layer surface. The configure event asks the client to resize its surface. Clients should arrange their surface for the new states, and then send an ack_configure request with the serial sent in this configure event at some point before committing the new surface. The client is free to dismiss all but the last configure event it received. The width and height arguments specify the size of the window in surface-local coordinates. The size is a hint, in the sense that the client is free to ignore it if it doesn't resize, pick a smaller size (to satisfy aspect ratio or resize in steps of NxM pixels). If the client picks a smaller size and is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the surface will be centered on this axis. If the width or height arguments are zero, it means the client should decide its own window dimension. The closed event is sent by the compositor when the surface will no longer be shown. The output may have been destroyed or the user may have asked for it to be removed. Further changes to the surface will be ignored. The client should destroy the resource after receiving this event, and create a new surface if they so choose. Change the layer that the surface is rendered on. Layer is double-buffered, see wl_surface.commit. wob-0.12/parse.c0000644000175100001710000000325614105527344013053 0ustar runnerdocker#define WOB_FILE "parse.c" #include #include #include #include "parse.h" bool wob_parse_color(const char *restrict str, char **restrict str_end, struct wob_color *color) { if (str[0] != '#') { return false; } str += 1; uint8_t parts[4]; for (size_t i = 0; i < (sizeof(parts) / sizeof(uint8_t)); ++i) { char *strtoul_end; char buffer[3] = {0}; strncpy(buffer, &str[i * 2], 2); parts[i] = strtoul(buffer, &strtoul_end, 16); if (strtoul_end != buffer + 2) { return false; } } *color = (struct wob_color){ .r = (float) parts[0] / UINT8_MAX, .g = (float) parts[1] / UINT8_MAX, .b = (float) parts[2] / UINT8_MAX, .a = (float) parts[3] / UINT8_MAX, }; if (str_end) { *str_end = ((char *) str) + sizeof("FFFFFFFF") - 1; } return true; } bool wob_parse_input(const char *input_buffer, unsigned long *percentage, struct wob_color *background_color, struct wob_color *border_color, struct wob_color *bar_color) { char *input_ptr, *newline_position, *str_end; newline_position = strchr(input_buffer, '\n'); if (newline_position == NULL) { return false; } if (newline_position == input_buffer) { return false; } *percentage = strtoul(input_buffer, &input_ptr, 10); if (input_ptr == newline_position) { return true; } struct wob_color *colors_to_parse[3] = { background_color, border_color, bar_color, }; for (size_t i = 0; i < sizeof(colors_to_parse) / sizeof(struct wob_color *); ++i) { if (input_ptr[0] != ' ') { return false; } input_ptr += 1; if (!wob_parse_color(input_ptr, &str_end, colors_to_parse[i])) { return false; } input_ptr = str_end; } return input_ptr == newline_position; } wob-0.12/meson.build0000644000175100001710000000517114105527344013735 0ustar runnerdockerproject( 'wob', 'c', version: '0.12', license: 'ISC', default_options: ['c_std=c99'] ) cc = meson.get_compiler('c') wayland_protos = dependency('wayland-protocols', version: '>=1.13') wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir') wayland_scanner = find_program('wayland-scanner') wayland_client = dependency('wayland-client') rt = cc.find_library('rt') seccomp = dependency('libseccomp', required: get_option('seccomp')) wob_version = '"@0@"'.format(meson.project_version()) add_project_arguments('-DWOB_VERSION=@0@'.format(wob_version), language: 'c') wayland_scanner_code = generator( wayland_scanner, output: '@BASENAME@-protocol.c', arguments: ['private-code', '@INPUT@', '@OUTPUT@'], ) wayland_scanner_client = generator( wayland_scanner, output: '@BASENAME@-client-protocol.h', arguments: ['client-header', '@INPUT@', '@OUTPUT@'], ) client_protocols = [ [wl_protocol_dir + '/stable/xdg-shell', 'xdg-shell.xml'], [wl_protocol_dir + '/unstable/xdg-output', 'xdg-output-unstable-v1.xml'], [meson.source_root() + '/protocols', 'wlr-layer-shell-unstable-v1.xml'], ] foreach p : client_protocols xml = join_paths(p) src = wayland_scanner_code.process(xml) header = wayland_scanner_client.process(xml) name = p[1].split('.')[0].underscorify() lib = static_library( name, [src, header], dependencies: [wayland_client], ) dep = declare_dependency( link_with: lib, sources: header, ) set_variable(name, dep) endforeach wob_inc = include_directories('include') wob_sources = ['main.c', 'parse.c', 'buffer.c', 'log.c', 'color.c'] wob_dependencies = [xdg_output_unstable_v1, wayland_client, wlr_layer_shell_unstable_v1, xdg_shell, rt] if seccomp.found() wob_dependencies += seccomp wob_sources += 'pledge_seccomp.c' else wob_sources += 'pledge.c' endif executable( 'wob', wob_sources, include_directories: [wob_inc], dependencies: wob_dependencies, install: true ) test('parse-input', executable( 'test-parse-input', ['tests/wob_parse_input.c', 'parse.c', 'color.c'], include_directories: [wob_inc] )) scdoc = dependency('scdoc', version: '>=1.9.2', native: true, required: get_option('man-pages')) if scdoc.found() scdoc = find_program(scdoc.get_pkgconfig_variable('scdoc'), native: true) sh = find_program('sh', native: true) mandir = get_option('mandir') scdfile = 'wob.1.scd' manfile = scdfile.split('.scd')[0] custom_target( manfile, input: scdfile, output: manfile, command: [ sh, '-c', '@0@ < @INPUT@ > @1@'.format(scdoc.path(), manfile) ], install: true, install_dir: join_paths(mandir, 'man1') ) endif wob-0.12/pledge.c0000644000175100001710000000013214105527344013167 0ustar runnerdocker#define WOB_FILE "pledge.c" #include "pledge.h" bool wob_pledge(void) { return true; } wob-0.12/pledge_seccomp.c0000644000175100001710000000266214105527344014712 0ustar runnerdocker#define WOB_FILE "pledge_seccomp.c" #include #include #include #include #include #include #include #include "log.h" #include "pledge.h" bool wob_pledge(void) { const int scmp_sc[] = { SCMP_SYS(clock_gettime), SCMP_SYS(close), SCMP_SYS(exit), SCMP_SYS(exit_group), SCMP_SYS(fcntl), SCMP_SYS(gettimeofday), SCMP_SYS(munmap), SCMP_SYS(poll), SCMP_SYS(ppoll), SCMP_SYS(read), SCMP_SYS(readv), SCMP_SYS(recvmsg), SCMP_SYS(restart_syscall), SCMP_SYS(sendmsg), SCMP_SYS(write), SCMP_SYS(writev), }; int ret; scmp_filter_ctx scmp_ctx = seccomp_init(SCMP_ACT_KILL); if (scmp_ctx == NULL) { wob_log_error("seccomp_init(SCMP_ACT_KILL) failed"); return false; } for (size_t i = 0; i < sizeof(scmp_sc) / sizeof(int); ++i) { wob_log_debug("Adding syscall %d to whitelist", scmp_sc[i]); if ((ret = seccomp_rule_add(scmp_ctx, SCMP_ACT_ALLOW, scmp_sc[i], 0)) < 0) { wob_log_error("seccomp_rule_add(scmp_ctxm, SCMP_ACT_ALLOW, %d) failed with return value %d", scmp_sc[i], ret); seccomp_release(scmp_ctx); return false; } } if ((ret = seccomp_load(scmp_ctx)) < 0) { wob_log_error("seccomp_load(scmp_ctx) failed with return value %d", ret); seccomp_release(scmp_ctx); return false; } wob_log_debug("Seccomp syscall whitelist successfully installed"); seccomp_release(scmp_ctx); return true; } wob-0.12/meson_options.txt0000644000175100001710000000027214105527344015225 0ustar runnerdockeroption('man-pages', type: 'feature', value: 'auto', description: 'Generate and install man pages') option('seccomp', type: 'feature', value: 'auto', description: 'Use seccomp on Linux') wob-0.12/main.c0000644000175100001710000007062414105527344012670 0ustar runnerdocker#define WOB_FILE "main.c" #define WOB_DEFAULT_WIDTH 400 #define WOB_DEFAULT_HEIGHT 50 #define WOB_DEFAULT_BORDER_OFFSET 4 #define WOB_DEFAULT_BORDER_SIZE 4 #define WOB_DEFAULT_BAR_PADDING 4 #define WOB_DEFAULT_ANCHOR 0 #define WOB_DEFAULT_MARGIN 0 #define WOB_DEFAULT_MAXIMUM 100 #define WOB_DEFAULT_TIMEOUT 1000 #define MIN_PERCENTAGE_BAR_WIDTH 1 #define MIN_PERCENTAGE_BAR_HEIGHT 1 #define STR(x) #x // sizeof already includes NULL byte #define INPUT_BUFFER_LENGTH (3 * sizeof(unsigned long) + sizeof(" #000000FF #FFFFFFFF #FFFFFFFF\n")) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define STDIN_BUFFER_LENGTH INPUT_BUFFER_LENGTH #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include "buffer.h" #include "color.h" #include "log.h" #include "parse.h" #include "pledge.h" #include "wlr-layer-shell-unstable-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" enum wob_overflow_mode { OVERFLOW_MODE_NONE, OVERFLOW_MODE_WRAP, OVERFLOW_MODE_NOWRAP, }; struct wob_geom { unsigned long width; unsigned long height; unsigned long border_offset; unsigned long border_size; unsigned long bar_padding; unsigned long stride; unsigned long size; unsigned long anchor; unsigned long margin; }; struct wob_colors { struct wob_color bar; struct wob_color background; struct wob_color border; }; struct wob_output_config { char *name; struct wl_list link; }; struct wob_surface { struct zwlr_layer_surface_v1 *wlr_layer_surface; struct wl_surface *wl_surface; }; struct wob_output { char *name; struct wl_list link; struct wl_output *wl_output; struct wob *app; struct wob_surface *wob_surface; struct zxdg_output_v1 *xdg_output; uint32_t wl_name; }; struct wob { int shmid; struct wl_buffer *wl_buffer; struct wl_compositor *wl_compositor; struct wl_display *wl_display; struct wl_list wob_outputs; struct wl_list output_configs; struct wl_registry *wl_registry; struct wl_shm *wl_shm; struct wob_geom *wob_geom; struct zwlr_layer_shell_v1 *wlr_layer_shell; struct zxdg_output_manager_v1 *xdg_output_manager; struct wob_surface *fallback_wob_surface; }; void noop() { /* intentionally left blank */ } void layer_surface_configure(void *data, struct zwlr_layer_surface_v1 *surface, uint32_t serial, uint32_t w, uint32_t h) { zwlr_layer_surface_v1_ack_configure(surface, serial); } void xdg_output_handle_name(void *data, struct zxdg_output_v1 *xdg_output, const char *name) { wob_log_info("Detected output %s", name); struct wob_output *output = (struct wob_output *) data; output->name = strdup(name); if (output->name == NULL) { wob_log_error("strdup failed\n"); exit(EXIT_FAILURE); } } struct wob_surface * wob_surface_create(struct wob *app, struct wl_output *wl_output) { const static struct zwlr_layer_surface_v1_listener zwlr_layer_surface_listener = { .configure = layer_surface_configure, .closed = noop, }; struct wob_surface *wob_surface = calloc(1, sizeof(struct wob_surface)); if (wob_surface == NULL) { wob_log_error("calloc failed"); exit(EXIT_FAILURE); } wob_surface->wl_surface = wl_compositor_create_surface(app->wl_compositor); if (wob_surface->wl_surface == NULL) { wob_log_error("wl_compositor_create_surface failed"); exit(EXIT_FAILURE); } wob_surface->wlr_layer_surface = zwlr_layer_shell_v1_get_layer_surface(app->wlr_layer_shell, wob_surface->wl_surface, wl_output, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "wob"); if (wob_surface->wlr_layer_surface == NULL) { wob_log_error("wlr_layer_shell_v1_get_layer_surface failed"); exit(EXIT_FAILURE); } zwlr_layer_surface_v1_set_size(wob_surface->wlr_layer_surface, app->wob_geom->width, app->wob_geom->height); zwlr_layer_surface_v1_set_anchor(wob_surface->wlr_layer_surface, app->wob_geom->anchor); zwlr_layer_surface_v1_set_margin(wob_surface->wlr_layer_surface, app->wob_geom->margin, app->wob_geom->margin, app->wob_geom->margin, app->wob_geom->margin); zwlr_layer_surface_v1_add_listener(wob_surface->wlr_layer_surface, &zwlr_layer_surface_listener, app); wl_surface_commit(wob_surface->wl_surface); return wob_surface; } void wob_surface_destroy(struct wob_surface *wob_surface) { if (wob_surface == NULL) { return; } zwlr_layer_surface_v1_destroy(wob_surface->wlr_layer_surface); wl_surface_destroy(wob_surface->wl_surface); wob_surface->wl_surface = NULL; wob_surface->wlr_layer_surface = NULL; } void wob_output_destroy(struct wob_output *output) { wob_surface_destroy(output->wob_surface); zxdg_output_v1_destroy(output->xdg_output); wl_output_destroy(output->wl_output); free(output->name); free(output->wob_surface); output->wob_surface = NULL; output->wl_output = NULL; output->xdg_output = NULL; output->name = NULL; } void xdg_output_handle_done(void *data, struct zxdg_output_v1 *xdg_output) { struct wob_output *output = (struct wob_output *) data; struct wob *app = output->app; struct wob_output_config *output_config, *tmp; wl_list_for_each_safe (output_config, tmp, &app->output_configs, link) { if (strcmp(output->name, output_config->name) == 0 || strcmp("*", output_config->name) == 0) { wl_list_insert(&output->app->wob_outputs, &output->link); wob_log_info("Bar will be displayed on output %s", output->name); return; } } wob_log_info("Bar will NOT be displayed on output %s", output->name); wob_output_destroy(output); free(output); } void handle_global(void *data, struct wl_registry *registry, uint32_t name, const char *interface, uint32_t version) { const static struct zxdg_output_v1_listener xdg_output_listener = { .logical_position = noop, .logical_size = noop, .name = xdg_output_handle_name, .description = noop, .done = xdg_output_handle_done, }; struct wob *app = (struct wob *) data; if (strcmp(interface, wl_shm_interface.name) == 0) { app->wl_shm = wl_registry_bind(registry, name, &wl_shm_interface, 1); } else if (strcmp(interface, wl_compositor_interface.name) == 0) { app->wl_compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1); } else if (strcmp(interface, "wl_output") == 0) { if (!wl_list_empty(&(app->output_configs))) { struct wob_output *output = calloc(1, sizeof(struct wob_output)); output->wl_output = wl_registry_bind(registry, name, &wl_output_interface, 1); output->app = app; output->wl_name = name; output->xdg_output = zxdg_output_manager_v1_get_xdg_output(app->xdg_output_manager, output->wl_output); zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener, output); if (wl_display_roundtrip(app->wl_display) == -1) { wob_log_error("wl_display_roundtrip failed"); exit(EXIT_FAILURE); } } } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) { app->wlr_layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1); } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) { app->xdg_output_manager = wl_registry_bind(registry, name, &zxdg_output_manager_v1_interface, 2); } } void handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) { struct wob *app = (struct wob *) data; struct wob_output *output, *tmp; wl_list_for_each_safe (output, tmp, &(app->wob_outputs), link) { if (output->wl_name == name) { wob_output_destroy(output); break; } } } void wob_flush(struct wob *app) { if (wl_list_empty(&(app->wob_outputs))) { wl_surface_attach(app->fallback_wob_surface->wl_surface, app->wl_buffer, 0, 0); wl_surface_damage(app->fallback_wob_surface->wl_surface, 0, 0, app->wob_geom->width, app->wob_geom->height); wl_surface_commit(app->fallback_wob_surface->wl_surface); } else { struct wob_output *output, *tmp; wl_list_for_each_safe (output, tmp, &(app->wob_outputs), link) { wl_surface_attach(output->wob_surface->wl_surface, app->wl_buffer, 0, 0); wl_surface_damage(output->wob_surface->wl_surface, 0, 0, app->wob_geom->width, app->wob_geom->height); wl_surface_commit(output->wob_surface->wl_surface); } } if (wl_display_dispatch(app->wl_display) == -1) { wob_log_error("wl_display_dispatch failed"); exit(EXIT_FAILURE); } } void wob_hide(struct wob *app) { if (wl_list_empty(&(app->wob_outputs))) { wob_log_info("Hiding bar on focused output"); wob_surface_destroy(app->fallback_wob_surface); free(app->fallback_wob_surface); app->fallback_wob_surface = NULL; } else { struct wob_output *output, *tmp; wl_list_for_each_safe (output, tmp, &app->wob_outputs, link) { wob_log_info("Hiding bar on output %s", output->name); wob_surface_destroy(output->wob_surface); free(output->wob_surface); output->wob_surface = NULL; } } if (wl_display_roundtrip(app->wl_display) == -1) { wob_log_error("wl_display_roundtrip failed"); exit(EXIT_FAILURE); } } void wob_show(struct wob *app) { if (wl_list_empty(&(app->wob_outputs))) { wob_log_info("No output matching configuration found, fallbacking to focused output"); app->fallback_wob_surface = wob_surface_create(app, NULL); } else { struct wob_output *output, *tmp; wl_list_for_each_safe (output, tmp, &app->wob_outputs, link) { wob_log_info("Showing bar on output %s", output->name); output->wob_surface = wob_surface_create(app, output->wl_output); } } if (wl_display_roundtrip(app->wl_display) == -1) { wob_log_error("wl_display_roundtrip failed"); exit(EXIT_FAILURE); } } void wob_destroy(struct wob *app) { struct wob_output *output, *output_tmp; wl_list_for_each_safe (output, output_tmp, &app->wob_outputs, link) { wob_output_destroy(output); free(output); } struct wob_output_config *config, *config_tmp; wl_list_for_each_safe (config, config_tmp, &app->output_configs, link) { free(config->name); free(config); } zwlr_layer_shell_v1_destroy(app->wlr_layer_shell); wl_registry_destroy(app->wl_registry); wl_buffer_destroy(app->wl_buffer); wl_compositor_destroy(app->wl_compositor); wl_shm_destroy(app->wl_shm); zxdg_output_manager_v1_destroy(app->xdg_output_manager); wl_display_disconnect(app->wl_display); } void wob_connect(struct wob *app) { const static struct wl_registry_listener wl_registry_listener = { .global = handle_global, .global_remove = noop, }; app->wl_display = wl_display_connect(NULL); if (app->wl_display == NULL) { wob_log_error("wl_display_connect failed"); exit(EXIT_FAILURE); } app->wl_registry = wl_display_get_registry(app->wl_display); if (app->wl_registry == NULL) { wob_log_error("wl_display_get_registry failed"); exit(EXIT_FAILURE); } wl_registry_add_listener(app->wl_registry, &wl_registry_listener, app); wl_list_init(&app->wob_outputs); if (wl_display_roundtrip(app->wl_display) == -1) { wob_log_error("wl_display_roundtrip failed"); exit(EXIT_FAILURE); } struct wl_shm_pool *pool = wl_shm_create_pool(app->wl_shm, app->shmid, app->wob_geom->size); if (pool == NULL) { wob_log_error("wl_shm_create_pool failed"); exit(EXIT_FAILURE); } app->wl_buffer = wl_shm_pool_create_buffer(pool, 0, app->wob_geom->width, app->wob_geom->height, app->wob_geom->stride, WL_SHM_FORMAT_ARGB8888); wl_shm_pool_destroy(pool); if (app->wl_buffer == NULL) { wob_log_error("wl_shm_pool_create_buffer failed"); exit(EXIT_FAILURE); } } void wob_draw_background(const struct wob_geom *geom, uint32_t *argb, struct wob_color color) { uint32_t argb_color = wob_color_to_argb(wob_color_premultiply_alpha(color)); for (size_t i = 0; i < geom->width * geom->height; ++i) { argb[i] = argb_color; } } void wob_draw_border(const struct wob_geom *geom, uint32_t *argb, struct wob_color color) { uint32_t argb_color = wob_color_to_argb(wob_color_premultiply_alpha(color)); // create top and bottom line size_t i = geom->width * geom->border_offset; size_t k = geom->width * (geom->height - geom->border_offset - geom->border_size); for (size_t line = 0; line < geom->border_size; ++line) { i += geom->border_offset; k += geom->border_offset; for (size_t pixel = 0; pixel < geom->width - 2 * geom->border_offset; ++pixel) { argb[i++] = argb_color; argb[k++] = argb_color; } i += geom->border_offset; k += geom->border_offset; } // create left and right horizontal line i = geom->width * (geom->border_offset + geom->border_size); k = geom->width * (geom->border_offset + geom->border_size); for (size_t line = 0; line < geom->height - 2 * (geom->border_size + geom->border_offset); ++line) { i += geom->border_offset; k += geom->width - geom->border_offset - geom->border_size; for (size_t pixel = 0; pixel < geom->border_size; ++pixel) { argb[i++] = argb_color; argb[k++] = argb_color; } i += geom->width - geom->border_offset - geom->border_size; k += geom->border_offset; } } void wob_draw_percentage(const struct wob_geom *geom, uint32_t *argb, struct wob_color bar_color, struct wob_color background_color, unsigned long percentage, unsigned long maximum) { uint32_t argb_bar_color = wob_color_to_argb(wob_color_premultiply_alpha(bar_color)); uint32_t argb_background_color = wob_color_to_argb(wob_color_premultiply_alpha(background_color)); size_t offset_border_padding = geom->border_offset + geom->border_size + geom->bar_padding; size_t bar_width = geom->width - 2 * offset_border_padding; size_t bar_height = geom->height - 2 * offset_border_padding; size_t bar_colored_width = (bar_width * percentage) / maximum; // draw 1px horizontal line uint32_t *start, *end, *pixel; start = &argb[offset_border_padding * (geom->width + 1)]; end = start + bar_colored_width; for (pixel = start; pixel < end; ++pixel) { *pixel = argb_bar_color; } for (end = start + bar_width; pixel < end; ++pixel) { *pixel = argb_background_color; } // copy it to make full percentage bar uint32_t *source = &argb[offset_border_padding * geom->width]; uint32_t *destination = source + geom->width; end = &argb[geom->width * (bar_height + offset_border_padding)]; while (destination != end) { memcpy(destination, source, MIN(destination - source, end - destination) * sizeof(uint32_t)); destination += MIN(destination - source, end - destination); } } static char stdin_buffer[STDIN_BUFFER_LENGTH]; int main(int argc, char **argv) { wob_log_use_colors(isatty(STDERR_FILENO)); wob_log_level_warn(); // libc is doing fstat syscall to determine the optimal buffer size and that can be problematic to wob_pledge() // to solve this problem we can just pass the optimal buffer ourselves if (setvbuf(stdin, stdin_buffer, _IOFBF, sizeof(stdin_buffer)) != 0) { wob_log_error("Failed to set stdin buffer size to %zu", sizeof(stdin_buffer)); return EXIT_FAILURE; } const char *usage = "Usage: wob [options]\n" "\n" " -h, --help Show help message and quit.\n" " --version Show the version number and quit.\n" " -v Increase verbosity of messages, defaults to errors and warnings only\n" " -t, --timeout Hide wob after milliseconds, defaults to " STR(WOB_DEFAULT_TIMEOUT) ".\n" " -m, --max <%> Define the maximum percentage, defaults to " STR(WOB_DEFAULT_MAXIMUM) ". \n" " -W, --width Define bar width in pixels, defaults to " STR(WOB_DEFAULT_WIDTH) ". \n" " -H, --height Define bar height in pixels, defaults to " STR(WOB_DEFAULT_HEIGHT) ". \n" " -o, --offset Define border offset in pixels, defaults to " STR(WOB_DEFAULT_BORDER_OFFSET) ". \n" " -b, --border Define border size in pixels, defaults to " STR(WOB_DEFAULT_BORDER_SIZE) ". \n" " -p, --padding Define bar padding in pixels, defaults to " STR(WOB_DEFAULT_BAR_PADDING) ". \n" " -a, --anchor Define anchor point; one of 'top', 'left', 'right', 'bottom', 'center' (default). \n" " May be specified multiple times. \n" " -M, --margin Define anchor margin in pixels, defaults to " STR(WOB_DEFAULT_MARGIN) ". \n" " -O, --output Define output to show bar on or '*' for all. If ommited, focused output is chosen.\n" " May be specified multiple times.\n" " --border-color <#rgba> Define border color\n" " --background-color <#rgba> Define background color\n" " --bar-color <#rgba> Define bar color\n" " --overflow-mode Change the overflow behavior. Valid options are `none`, `wrap` (default), and `nowrap`.\n" " --overflow-bar-color <#rgba> Define bar color when overflowed\n" " --overflow-border-color <#rgba> Define the border color when overflowed\n" " --overflow-background-color <#rgba> Define the background color when overflowed\n" "\n"; struct wob app = {0}; wl_list_init(&(app.output_configs)); unsigned long maximum = WOB_DEFAULT_MAXIMUM; unsigned long timeout_msec = WOB_DEFAULT_TIMEOUT; enum wob_overflow_mode overflow_mode = OVERFLOW_MODE_WRAP; struct wob_geom geom = { .width = WOB_DEFAULT_WIDTH, .height = WOB_DEFAULT_HEIGHT, .border_offset = WOB_DEFAULT_BORDER_OFFSET, .border_size = WOB_DEFAULT_BORDER_SIZE, .bar_padding = WOB_DEFAULT_BAR_PADDING, .anchor = WOB_DEFAULT_ANCHOR, .margin = WOB_DEFAULT_MARGIN, }; struct wob_colors colors = { .background = (struct wob_color){.a = 1.0f, .r = 0.0f, .g = 0.0f, .b = 0.0f}, .bar = (struct wob_color){.a = 1.0f, .r = 1.0f, .g = 1.0f, .b = 1.0f}, .border = (struct wob_color){.a = 1.0f, .r = 1.0f, .g = 1.0f, .b = 1.0f}}; struct wob_colors overflow_colors = { .background = (struct wob_color){.a = 1.0f, .r = 0.0f, .g = 0.0f, .b = 0.0f}, .bar = (struct wob_color){.a = 1.0f, .r = 1.0f, .g = 0.0f, .b = 0.0f}, .border = (struct wob_color){.a = 1.0f, .r = 1.0f, .g = 1.0f, .b = 1.0f}}; bool pledge = true; char *disable_pledge_env = getenv("WOB_DISABLE_PLEDGE"); if (disable_pledge_env != NULL && strcmp(disable_pledge_env, "0") != 0) { pledge = false; } struct wob_output_config *output_config; int option_index = 0; int c; char *strtoul_end; static struct option long_options[] = { {"help", no_argument, NULL, 'h'}, {"version", no_argument, NULL, 4}, {"timeout", required_argument, NULL, 't'}, {"max", required_argument, NULL, 'm'}, {"width", required_argument, NULL, 'W'}, {"height", required_argument, NULL, 'H'}, {"offset", required_argument, NULL, 'o'}, {"border", required_argument, NULL, 'b'}, {"padding", required_argument, NULL, 'p'}, {"anchor", required_argument, NULL, 'a'}, {"margin", required_argument, NULL, 'M'}, {"output", required_argument, NULL, 'O'}, {"border-color", required_argument, NULL, 1}, {"background-color", required_argument, NULL, 2}, {"bar-color", required_argument, NULL, 3}, {"verbose", no_argument, NULL, 'v'}, {"overflow-mode", required_argument, NULL, 6}, {"overflow-bar-color", required_argument, NULL, 5}, {"overflow-background-color", required_argument, NULL, 7}, {"overflow-border-color", required_argument, NULL, 8}}; while ((c = getopt_long(argc, argv, "t:m:W:H:o:b:p:a:M:O:vh:f", long_options, &option_index)) != -1) { switch (c) { case 1: if (!wob_parse_color(optarg, &strtoul_end, &(colors.border))) { wob_log_error("Border color must be a value between #00000000 and #FFFFFFFF."); return EXIT_FAILURE; } break; case 2: if (!wob_parse_color(optarg, &strtoul_end, &(colors.background))) { wob_log_error("Background color must be a value between #00000000 and #FFFFFFFF."); return EXIT_FAILURE; } break; case 3: if (!wob_parse_color(optarg, &strtoul_end, &(colors.bar))) { wob_log_error("Bar color must be a value between #00000000 and #FFFFFFFF."); return EXIT_FAILURE; } break; case 't': timeout_msec = strtoul(optarg, &strtoul_end, 10); if (*strtoul_end != '\0' || errno == ERANGE || timeout_msec == 0) { wob_log_error("Timeout must be a value between 1 and %lu.", ULONG_MAX); return EXIT_FAILURE; } break; case 'm': maximum = strtoul(optarg, &strtoul_end, 10); if (*strtoul_end != '\0' || errno == ERANGE || maximum == 0) { wob_log_error("Maximum must be a value between 1 and %lu.", ULONG_MAX); return EXIT_FAILURE; } break; case 'W': geom.width = strtoul(optarg, &strtoul_end, 10); if (*strtoul_end != '\0' || errno == ERANGE) { wob_log_error("Width must be a positive value."); return EXIT_FAILURE; } break; case 'H': geom.height = strtoul(optarg, &strtoul_end, 10); if (*strtoul_end != '\0' || errno == ERANGE) { wob_log_error("Height must be a positive value."); return EXIT_FAILURE; } break; case 'o': geom.border_offset = strtoul(optarg, &strtoul_end, 10); if (*strtoul_end != '\0' || errno == ERANGE) { wob_log_error("Border offset must be a positive value."); return EXIT_FAILURE; } break; case 'b': geom.border_size = strtoul(optarg, &strtoul_end, 10); if (*strtoul_end != '\0' || errno == ERANGE) { wob_log_error("Border size must be a positive value."); return EXIT_FAILURE; } break; case 'p': geom.bar_padding = strtoul(optarg, &strtoul_end, 10); if (*strtoul_end != '\0' || errno == ERANGE) { wob_log_error("Bar padding must be a positive value."); return EXIT_FAILURE; } break; case 'a': if (strcmp(optarg, "left") == 0) { geom.anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT; } else if (strcmp(optarg, "right") == 0) { geom.anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT; } else if (strcmp(optarg, "top") == 0) { geom.anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP; } else if (strcmp(optarg, "bottom") == 0) { geom.anchor |= ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM; } else if (strcmp(optarg, "center") != 0) { wob_log_error("Anchor must be one of 'top', 'bottom', 'left', 'right', 'center'."); return EXIT_FAILURE; } break; case 'M': geom.margin = strtoul(optarg, &strtoul_end, 10); if (*strtoul_end != '\0' || errno == ERANGE) { wob_log_error("Anchor margin must be a positive value."); return EXIT_FAILURE; } break; case 'O': output_config = calloc(1, sizeof(struct wob_output_config)); if (output_config == NULL) { wob_log_error("calloc failed"); return EXIT_FAILURE; } output_config->name = strdup(optarg); if (output_config->name == NULL) { free(output_config); wob_log_error("strdup failed"); return EXIT_FAILURE; } wl_list_insert(&(app.output_configs), &(output_config->link)); break; case 4: printf("wob version: " WOB_VERSION "\n"); return EXIT_SUCCESS; case 'h': printf("%s", usage); return EXIT_SUCCESS; case 'v': wob_log_inc_verbosity(); break; case 5: if (!wob_parse_color(optarg, &strtoul_end, &(overflow_colors.bar))) { wob_log_error("Overflow bar color must be a value between #00000000 and #FFFFFFFF."); return EXIT_FAILURE; } break; case 6: if (strcmp(optarg, "none") == 0) { overflow_mode = OVERFLOW_MODE_NONE; } else if (strcmp(optarg, "wrap") == 0) { overflow_mode = OVERFLOW_MODE_WRAP; // this is the default } else if (strcmp(optarg, "nowrap") == 0) { overflow_mode = OVERFLOW_MODE_NOWRAP; } else { wob_log_error("Invalid argument for overflow-mode. Valid options are none, wrap, and nowrap."); return EXIT_FAILURE; } break; case 7: if (!wob_parse_color(optarg, &strtoul_end, &(overflow_colors.background))) { wob_log_error("Overflow background color must be a value between #00000000 and #FFFFFFFF."); return EXIT_FAILURE; } break; case 8: if (!wob_parse_color(optarg, &strtoul_end, &(overflow_colors.border))) { wob_log_error("Overflow border color must be a value between #00000000 and #FFFFFFFF."); return EXIT_FAILURE; } break; default: fprintf(stderr, "%s", usage); return EXIT_FAILURE; } } if (geom.width < MIN_PERCENTAGE_BAR_WIDTH + 2 * (geom.border_offset + geom.border_size + geom.bar_padding)) { wob_log_error("Invalid geometry: width is too small for given parameters"); return EXIT_FAILURE; } if (geom.height < MIN_PERCENTAGE_BAR_HEIGHT + 2 * (geom.border_offset + geom.border_size + geom.bar_padding)) { wob_log_error("Invalid geometry: height is too small for given parameters"); return EXIT_FAILURE; } geom.stride = geom.width * 4; geom.size = geom.stride * geom.height; app.wob_geom = &geom; int shmid = wob_shm_create(); if (shmid < 0) { return EXIT_FAILURE; } app.shmid = shmid; uint32_t *argb = wob_shm_alloc(shmid, app.wob_geom->size); if (argb == NULL) { return EXIT_FAILURE; } wob_connect(&app); if (app.wl_shm == NULL || app.wl_compositor == NULL || app.wlr_layer_shell == NULL) { wob_log_error("Wayland compositor doesn't support all required protocols"); return EXIT_FAILURE; } if (pledge) { if (!wob_pledge()) { return EXIT_FAILURE; } } struct wob_colors old_colors; struct wob_colors effective_colors = colors; // Draw these at least once wob_draw_background(app.wob_geom, argb, colors.background); wob_draw_border(app.wob_geom, argb, colors.border); struct pollfd fds[2] = { { .fd = wl_display_get_fd(app.wl_display), .events = POLLIN, }, { .fd = STDIN_FILENO, .events = POLLIN, }, }; bool hidden = true; for (;;) { unsigned long percentage = 0; char input_buffer[INPUT_BUFFER_LENGTH] = {0}; char *fgets_rv; switch (poll(fds, 2, hidden ? -1 : timeout_msec)) { case -1: wob_log_error("poll() failed: %s", strerror(errno)); return EXIT_FAILURE; case 0: if (!hidden) wob_hide(&app); hidden = true; break; default: if (fds[0].revents) { if (!(fds[0].revents & POLLIN)) { wob_log_error("WL_DISPLAY_FD unexpectedly closed, revents = %hd", fds[0].revents); return EXIT_FAILURE; } if (wl_display_dispatch(app.wl_display) == -1) { return EXIT_FAILURE; } } if (fds[1].revents) { if (!(fds[1].revents & POLLIN)) { wob_log_error("STDIN unexpectedly closed, revents = %hd", fds[1].revents); if (!hidden) wob_hide(&app); wob_destroy(&app); return EXIT_FAILURE; } fgets_rv = fgets(input_buffer, INPUT_BUFFER_LENGTH, stdin); if (feof(stdin)) { wob_log_info("Received EOF"); if (!hidden) wob_hide(&app); wob_destroy(&app); return EXIT_SUCCESS; } if (fgets_rv == NULL) { wob_log_error("fgets() failed: %s", strerror(errno)); if (!hidden) wob_hide(&app); wob_destroy(&app); return EXIT_FAILURE; } if (!wob_parse_input(input_buffer, &percentage, &colors.background, &colors.border, &colors.bar)) { wob_log_error("Received invalid input"); if (!hidden) wob_hide(&app); wob_destroy(&app); return EXIT_FAILURE; } old_colors = effective_colors; if (percentage > maximum) { switch (overflow_mode) { case OVERFLOW_MODE_NONE: wob_log_error("Received value %ld is above defined maximum %ld", percentage, maximum); if (!hidden) wob_hide(&app); wob_destroy(&app); return EXIT_FAILURE; case OVERFLOW_MODE_WRAP: effective_colors = overflow_colors; percentage %= maximum; break; case OVERFLOW_MODE_NOWRAP: effective_colors = overflow_colors; percentage = maximum; break; } } else { effective_colors = colors; } wob_log_info( "Received input { value = %ld, bg = %#x, border = %#x, bar = %#x, overflow = %s }", percentage, effective_colors.background, effective_colors.border, effective_colors.bar, overflow_mode == OVERFLOW_MODE_NONE ? "false" : "true"); // how should this be handled w/ the overflow colors? if (hidden) { wob_show(&app); } bool redraw_background_and_border = false; if (wob_color_to_argb(old_colors.background) != wob_color_to_argb(effective_colors.background)) { redraw_background_and_border = true; } else if (wob_color_to_argb(old_colors.border) != wob_color_to_argb(effective_colors.border)) { redraw_background_and_border = true; } if (redraw_background_and_border) { wob_draw_background(app.wob_geom, argb, effective_colors.background); wob_draw_border(app.wob_geom, argb, effective_colors.border); } wob_draw_percentage(app.wob_geom, argb, effective_colors.bar, effective_colors.background, percentage, maximum); wob_flush(&app); hidden = false; } } } } wob-0.12/log.c0000644000175100001710000000470014105527344012515 0ustar runnerdocker#define WOB_FILE "log.c" #define _POSIX_C_SOURCE 199506L #define COLOR_RESET "\x1B[0m" #define COLOR_WHITE "\x1B[1;37m" #define COLOR_BLACK "\x1B[0;30m" #define COLOR_BLUE "\x1B[0;34m" #define COLOR_LIGHT_BLUE "\x1B[1;34m" #define COLOR_GREEN "\x1B[0;32m" #define COLOR_LIGHT_GREEN "\x1B[1;32m" #define COLOR_CYAN "\x1B[0;36m" #define COLOR_LIGHT_CYAN "\x1B[1;36m" #define COLOR_RED "\x1B[0;31m" #define COLOR_LIGHT_RED "\x1B[1;31m" #define COLOR_PURPLE "\x1B[0;35m" #define COLOR_LIGHT_PURPLE "\x1B[1;35m" #define COLOR_BROWN "\x1B[0;33m" #define COLOR_YELLOW "\x1B[1;33m" #define COLOR_GRAY "\x1B[0;30m" #define COLOR_LIGHT_GRAY "\x1B[0;37m" #include #include #include #include #include #include #include #include #include #include "log.h" static wob_log_importance min_importance_to_log = WOB_LOG_WARN; static bool use_colors = false; static const char *verbosity_names[] = { "DEBUG", "INFO", "WARN", "ERROR", }; static const char *verbosity_colors[] = { COLOR_LIGHT_CYAN, COLOR_GREEN, COLOR_YELLOW, COLOR_LIGHT_RED, }; void wob_log(const wob_log_importance importance, const char *file, const int line, const char *fmt, ...) { if (importance < min_importance_to_log) { return; } struct timespec ts; if (clock_gettime(CLOCK_REALTIME, &ts) != 0) { fprintf(stderr, "clock_gettime() failed: %s\n", strerror(errno)); ts.tv_sec = 0; ts.tv_nsec = 0; } // formatting time via localtime() requires open syscall (to read /etc/localtime) // and that is problematic with seccomp rules in place if (use_colors) { fprintf( stderr, "%jd.%06ld %s%-5s%s %s%s:%d:%s ", (intmax_t) ts.tv_sec, ts.tv_nsec / 1000, verbosity_colors[importance], verbosity_names[importance], COLOR_RESET, COLOR_LIGHT_GRAY, file, line, COLOR_RESET); } else { fprintf(stderr, "%jd.%06ld %s %s:%d: ", (intmax_t) ts.tv_sec, ts.tv_nsec / 1000, verbosity_names[importance], file, line); } va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); } void wob_log_set_level(const wob_log_importance importance) { min_importance_to_log = importance; } void wob_log_use_colors(const bool colors) { use_colors = colors; } void wob_log_inc_verbosity(void) { if (min_importance_to_log != WOB_LOG_DEBUG) { min_importance_to_log -= 1; wob_log_debug("Set log level to %s", verbosity_names[min_importance_to_log]); } } wob-0.12/tests/0000755000175100001710000000000014105527344012731 5ustar runnerdockerwob-0.12/tests/wob_parse_input.c0000644000175100001710000000274014105527344016300 0ustar runnerdocker#include #include #include #include "parse.h" int main(int argc, char **argv) { unsigned long percentage; struct wob_color background = {0}; struct wob_color border = {0}; struct wob_color bar = {0}; char *input; bool result; printf("running 1\n"); input = "25 #000000FF #FFFFFFFF #FFFFFFFF\n"; result = wob_parse_input(input, &percentage, &background, &border, &bar); if (!result || percentage != 25 || wob_color_to_argb(background) != 0xFF000000 || wob_color_to_argb(border) != 0xFFFFFFFF || wob_color_to_argb(bar) != 0xFFFFFFFF) { return EXIT_FAILURE; } printf("running 2\n"); input = "25 #000000FF\n"; result = wob_parse_input(input, &percentage, &background, &border, &bar); if (result) { return EXIT_FAILURE; } printf("running 3\n"); input = "25\n"; result = wob_parse_input(input, &percentage, &background, &border, &bar); if (!result || percentage != 25) { return EXIT_FAILURE; } printf("running 4\n"); input = "25 #000000FF #FFFFFFFF #FFFFFFFF \n"; result = wob_parse_input(input, &percentage, &background, &border, &bar); if (result) { return EXIT_FAILURE; } printf("running 5\n"); input = "25 #000000FF #16a085FF #FF0000FF\n"; result = wob_parse_input(input, &percentage, &background, &border, &bar); if (!result || percentage != 25 || wob_color_to_argb(background) != 0xFF000000 || wob_color_to_argb(border) != 0xFF16a085 || wob_color_to_argb(bar) != 0xFFFF0000) { return EXIT_FAILURE; } return EXIT_SUCCESS; } wob-0.12/color.c0000644000175100001710000000117514105527344013055 0ustar runnerdocker#define WOB_FILE "color.c" #include #include "color.h" uint32_t wob_color_to_argb(const struct wob_color color) { uint8_t alpha = (uint8_t) (color.a * UINT8_MAX); uint8_t red = (uint8_t) (color.r * UINT8_MAX); uint8_t green = (uint8_t) (color.g * UINT8_MAX); uint8_t blue = (uint8_t) (color.b * UINT8_MAX); return (alpha << 24) + (red << 16) + (green << 8) + blue; } struct wob_color wob_color_premultiply_alpha(const struct wob_color color) { struct wob_color premultiplied_color = { .a = color.a, .r = color.r * color.a, .g = color.g * color.a, .b = color.b * color.a, }; return premultiplied_color; } wob-0.12/buffer.c0000644000175100001710000000216514105527344013210 0ustar runnerdocker#define WOB_FILE "buffer.c" #define _POSIX_C_SOURCE 200112L #include #include #include #include #include #include #include #include "buffer.h" #include "log.h" int wob_shm_create() { int shmid = -1; char shm_name[NAME_MAX]; for (int i = 0; i < UCHAR_MAX; ++i) { if (snprintf(shm_name, NAME_MAX, "/wob-%d", i) >= NAME_MAX) { break; } shmid = shm_open(shm_name, O_RDWR | O_CREAT | O_EXCL, 0600); if (shmid > 0 || errno != EEXIST) { break; } } if (shmid < 0) { wob_log_error("shm_open() failed: %s", strerror(errno)); return -1; } if (shm_unlink(shm_name) != 0) { wob_log_error("shm_unlink() failed: %s", strerror(errno)); return -1; } return shmid; } void * wob_shm_alloc(const int shmid, const size_t size) { if (ftruncate(shmid, size) != 0) { wob_log_error("ftruncate() failed: %s", strerror(errno)); return NULL; } void *buffer = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, shmid, 0); if (buffer == MAP_FAILED) { wob_log_error("mmap() failed: %s", strerror(errno)); return NULL; } return buffer; } wob-0.12/include/0000755000175100001710000000000014105527344013212 5ustar runnerdockerwob-0.12/include/pledge.h0000644000175100001710000000014214105527344014620 0ustar runnerdocker#ifndef _WOB_PLEDGE_H #define _WOB_PLEDGE_H #include bool wob_pledge(void); #endif wob-0.12/include/color.h0000644000175100001710000000040214105527344014475 0ustar runnerdocker#ifndef _WOB_COLOR_H #define _WOB_COLOR_H #include struct wob_color { float a; float r; float g; float b; }; uint32_t wob_color_to_argb(struct wob_color color); struct wob_color wob_color_premultiply_alpha(struct wob_color color); #endif wob-0.12/include/parse.h0000644000175100001710000000053214105527344014475 0ustar runnerdocker#ifndef _WOB_PARSE_H #define _WOB_PARSE_H #include #include "color.h" bool wob_parse_color(const char *restrict str, char **restrict str_end, struct wob_color *color); bool wob_parse_input(const char *input_buffer, unsigned long *percentage, struct wob_color *background, struct wob_color *border, struct wob_color *bar); #endif wob-0.12/include/buffer.h0000644000175100001710000000021614105527344014633 0ustar runnerdocker#ifndef _WOB_BUFFER_H #define _WOB_BUFFER_H #include int wob_shm_create(); void *wob_shm_alloc(int shmid, size_t size); #endif wob-0.12/include/log.h0000644000175100001710000000174414105527344014152 0ustar runnerdocker#ifndef _WOB_LOG_H #define _WOB_LOG_H #include typedef enum { WOB_LOG_DEBUG = 0, WOB_LOG_INFO = 1, WOB_LOG_WARN = 2, WOB_LOG_ERROR = 3, } wob_log_importance; void wob_log(wob_log_importance importance, const char *file, int line, const char *fmt, ...); void wob_log_set_level(wob_log_importance importance); void wob_log_inc_verbosity(void); void wob_log_use_colors(bool use_colors); #define wob_log_debug(...) wob_log(WOB_LOG_DEBUG, WOB_FILE, __LINE__, __VA_ARGS__) #define wob_log_info(...) wob_log(WOB_LOG_INFO, WOB_FILE, __LINE__, __VA_ARGS__) #define wob_log_warn(...) wob_log(WOB_LOG_WARN, WOB_FILE, __LINE__, __VA_ARGS__) #define wob_log_error(...) wob_log(WOB_LOG_ERROR, WOB_FILE, __LINE__, __VA_ARGS__) #define wob_log_level_debug() wob_log_set_level(WOB_LOG_DEBUG); #define wob_log_level_info() wob_log_set_level(WOB_LOG_INFO); #define wob_log_level_warn() wob_log_set_level(WOB_LOG_WARN); #define wob_log_level_error() wob_log_set_level(WOB_LOG_ERROR); #endif