Add labnag

Based on swaynag (https://github.com/swaywm/sway/tree/master/swaynag)

Copied at commit:
03483ff370

Contains the following modifiations:

- Some functional changes including:
  - Disable exclusive-zone by default (Written-by: @Consolatis) and add
    command line option -x|--exclusive-zone
  - Add close timeout (Written-by: @Consolatis) and -t|--timeout option
  - Use index of button (from right-to-left) for exit code
  - Disable reading from config file and remove associated --type option
- Refactoring including:
  - Use wlr_log() instead of the log.{c,h} functions
  - Use wl_list instead of sway's list.c implementation
  - In the pango wrapper functions, use glib's g_strdup_vprintf() rather
    than the original stringop.c functions
- Align with labwc coding style to pass checkpatch.pl
- Re-licenced from MIT to GPL-2.0, and add Copyright notices for original
  authors

v2

- Remove option -s|--dismiss-button and the default "X" button. To get
  such a button, "-Z X :"
- Remove options -b and -z because there is no requirement to run
  in a terminal.
- Remove *-no-terminal from options --button and --button-dismiss because
  commands are now always run directly without a terminal.

v3

- Allow -B/-Z options without action-argument
- Invert button order of -B/-Z so that `labnag -m foo -Z x -Z y -Z z`
  results in three buttons with "x" furthest to the left, and "z" on the
  right (rather than the other way around).
- Use signalfd() to prevent race conditions on SIGTERM

v4

- Limit number of stdin lines to 200 to avoid hogging CPU

Co-Authored-by: tokyo4j
This commit is contained in:
Johan Malm 2025-04-28 20:38:19 +01:00 committed by Johan Malm
parent 6fd14987dd
commit c63d35c942
8 changed files with 1986 additions and 1 deletions

1652
clients/labnag.c Normal file

File diff suppressed because it is too large Load diff

47
clients/meson.build Normal file
View file

@ -0,0 +1,47 @@
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
nag_sources = files(
'labnag.c',
'pool-buffer.c',
)
wl_protocol_dir = wayland_protos.get_variable('pkgdatadir')
protocols = [
wl_protocol_dir / 'stable/tablet/tablet-v2.xml',
wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml',
wl_protocol_dir / 'staging/cursor-shape/cursor-shape-v1.xml',
'../protocols/wlr-layer-shell-unstable-v1.xml',
]
foreach xml : protocols
nag_sources += custom_target(
xml.underscorify() + '_c',
input: xml,
output: '@BASENAME@-protocol.c',
command: [wayland_scanner, 'private-code', '@INPUT@', '@OUTPUT@'],
)
nag_sources += custom_target(
xml.underscorify() + '_client_h',
input: xml,
output: '@BASENAME@-client-protocol.h',
command: [wayland_scanner, 'client-header', '@INPUT@', '@OUTPUT@'],
)
endforeach
executable(
'labnag',
nag_sources,
dependencies: [
cairo,
pangocairo,
glib,
wayland_client,
wayland_cursor,
wlroots,
server_protos,
],
install: true
)

145
clients/pool-buffer.c Normal file
View file

@ -0,0 +1,145 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copied from https://github.com/swaywm/sway
*
* Copyright (C) 2016-2017 Drew DeVault
*/
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <cairo.h>
#include <errno.h>
#include <fcntl.h>
#include <pango/pangocairo.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client.h>
#include "pool-buffer.h"
static int anonymous_shm_open(void)
{
int retries = 100;
do {
// try a probably-unique name
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
pid_t pid = getpid();
char name[50];
snprintf(name, sizeof(name), "/labnag-%x-%x",
(unsigned int)pid, (unsigned int)ts.tv_nsec);
// shm_open guarantees that O_CLOEXEC is set
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd >= 0) {
shm_unlink(name);
return fd;
}
--retries;
} while (retries > 0 && errno == EEXIST);
return -1;
}
static void buffer_release(void *data, struct wl_buffer *wl_buffer)
{
struct pool_buffer *buffer = data;
buffer->busy = false;
}
static const struct wl_buffer_listener buffer_listener = {
.release = buffer_release
};
static struct pool_buffer *create_buffer(struct wl_shm *shm,
struct pool_buffer *buf, int32_t width, int32_t height,
uint32_t format)
{
uint32_t stride = width * 4;
size_t size = stride * height;
int fd = anonymous_shm_open();
if (fd == -1) {
return NULL;
}
if (ftruncate(fd, size) < 0) {
close(fd);
return NULL;
}
void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
buf->buffer = wl_shm_pool_create_buffer(pool, 0,
width, height, stride, format);
wl_shm_pool_destroy(pool);
close(fd);
buf->size = size;
buf->width = width;
buf->height = height;
buf->data = data;
buf->surface = cairo_image_surface_create_for_data(data,
CAIRO_FORMAT_ARGB32, width, height, stride);
buf->cairo = cairo_create(buf->surface);
buf->pango = pango_cairo_create_context(buf->cairo);
wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
return buf;
}
void destroy_buffer(struct pool_buffer *buffer)
{
if (buffer->buffer) {
wl_buffer_destroy(buffer->buffer);
buffer->buffer = NULL;
}
if (buffer->cairo) {
cairo_destroy(buffer->cairo);
buffer->cairo = NULL;
}
if (buffer->surface) {
cairo_surface_destroy(buffer->surface);
buffer->surface = NULL;
}
if (buffer->pango) {
g_object_unref(buffer->pango);
buffer->pango = NULL;
}
if (buffer->data) {
munmap(buffer->data, buffer->size);
buffer->data = NULL;
}
}
struct pool_buffer *get_next_buffer(struct wl_shm *shm,
struct pool_buffer pool[static 2], uint32_t width, uint32_t height)
{
struct pool_buffer *buffer = NULL;
for (size_t i = 0; i < 2; ++i) {
if (pool[i].busy) {
continue;
}
buffer = &pool[i];
}
if (!buffer) {
return NULL;
}
if (buffer->width != width || buffer->height != height) {
destroy_buffer(buffer);
}
if (!buffer->buffer) {
if (!create_buffer(shm, buffer, width, height,
WL_SHM_FORMAT_ARGB8888)) {
return NULL;
}
}
buffer->busy = true;
return buffer;
}

30
clients/pool-buffer.h Normal file
View file

@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copied from https://github.com/swaywm/sway
*
* Copyright (C) 2016-2017 Drew DeVault
*/
#ifndef LAB_POOL_BUFFER_H
#define LAB_POOL_BUFFER_H
#include <cairo.h>
#include <pango/pangocairo.h>
#include <stdbool.h>
#include <stdint.h>
#include <wayland-client.h>
struct pool_buffer {
struct wl_buffer *buffer;
cairo_surface_t *surface;
cairo_t *cairo;
PangoContext *pango;
uint32_t width, height;
void *data;
size_t size;
bool busy;
};
struct pool_buffer *get_next_buffer(struct wl_shm *shm,
struct pool_buffer pool[static 2], uint32_t width, uint32_t height);
void destroy_buffer(struct pool_buffer *buffer);
#endif /* LAB_POOL_BUFFER_H */

109
docs/labnag.1.scd Normal file
View file

@ -0,0 +1,109 @@
labnag(1)
# NAME
labnag - Show dialog with message and buttons
# SYNOPSIS
_labnag_ [options...]
# OPTIONS
*-B, --button* <text> [<action>]
Create a button with the text _text_ that optionally executes _action_
when pressed. Multiple buttons can be defined by providing the flag
multiple times. Buttons will appear in the order they are provided from
lef to right.
*-Z, --button-dismiss* <text> [<action>]
Create a button with the text _text_ that optionally executes _action_
when pressed, and dismisses labnag. Multiple buttons can be defined by
providing the flag multiple times. Buttons will appear in the order
they are provided from lef to right.
*-d, --debug*
Enable debugging.
*-e, --edge* top|bottom
Set the edge to use.
*-y, --layer* overlay|top|bottom|background
Set the layer to use.
*-f, --font* <font>
Set the font to use.
*-h, --help*
Show help message and quit.
*-l, --detailed-message*
Read a detailed message from stdin. A button to toggle details will be
added. Details are shown in a scrollable multi-line text area.
*-L, --detailed-button* <text>
Set the text for the button that toggles details. This has no effect if
there is not a detailed message. The default is _Toggle details_.
*-m, --message* <msg>
Set the message text.
*-o, --output* <output>
Set the output to use. This should be the name of a _xdg\_output_.
*-t, --timeout*
Set duration to close dialog. Default is 5 seconds.
*-x, --exclusive-zone*
Use exclusive zone. Default is false.
*-v, --version*
Show the version number and quit.
# APPEARANCE OPTIONS
*--background* <RRGGBB[AA]>
Set the color of the background.
*--border* <RRGGBB[AA]>
Set the color of the border.
*--border-bottom* <RRGGBB[AA]>
Set the color of the bottom border.
*--button-background* <RRGGBB[AA]>
Set the color for the background for buttons.
*--text* <RRGGBB[AA]>
Set the text color.
*--button-text* <RRGGBB[AA]>
Set the button text color.
*--border-bottom-size* <size>
Set the thickness of the bottom border.
*--message-padding* <padding>
Set the padding for the message.
*--details-background* <RRGGBB[AA]>
Set the color for the background for details.
*--details-border-size* <size>
Set the thickness for the details border.
*--button-border-size* <size>
Set the thickness for the button border.
*--button-gap* <gap>
Set the size of the gap between buttons.
*--button-dismiss-gap* <gap>
Set the size of the gap between the dismiss button and another button.
*--button-margin-right* <margin>
Set the margin from the right of the dismiss button to edge.
*--button-padding* <padding>
Set the padding for the button text.

View file

@ -7,6 +7,7 @@ if scdoc.found()
'labwc-config.5',
'labwc-menu.5',
'labwc-theme.5',
'labnag.1',
]
foreach manpage : manpages
markdown = manpage + '.scd'

View file

@ -180,6 +180,7 @@ endif
subdir('include')
subdir('src')
subdir('docs')
subdir('clients')
dep_cmocka = dependency('cmocka', required: get_option('test'))
if dep_cmocka.found()

View file

@ -19,7 +19,7 @@ run_checks () {
return $?
fi
find src/ include/ \( -name "*.c" -o -name "*.h" \) -type f -print0 |
find src/ include/ clients/ \( -name "*.c" -o -name "*.h" \) -type f -print0 |
nice xargs -0 --max-args 1 --max-procs $(nproc) \
scripts/checkpatch.pl --terse --no-tree --strict --file
return $?