aboutsummaryrefslogtreecommitdiffstats
path: root/swaynag
diff options
context:
space:
mode:
authorLibravatar Brian Ashworth <bosrsf04@gmail.com>2018-07-27 11:19:42 -0400
committerLibravatar Brian Ashworth <bosrsf04@gmail.com>2018-08-01 22:47:54 -0400
commita4f7bf23b21d0d838a8a19261d5fd69719003a03 (patch)
treee46a226d8c3ded2d93e1933323263b6756199a68 /swaynag
parentSupport a detailed message in swaynagbar (diff)
downloadsway-a4f7bf23b21d0d838a8a19261d5fd69719003a03.tar.gz
sway-a4f7bf23b21d0d838a8a19261d5fd69719003a03.tar.zst
sway-a4f7bf23b21d0d838a8a19261d5fd69719003a03.zip
Address first round review for swaynag
Diffstat (limited to 'swaynag')
-rw-r--r--swaynag/main.c246
-rw-r--r--swaynag/meson.build20
-rw-r--r--swaynag/nagbar.c420
-rw-r--r--swaynag/render.c300
-rw-r--r--swaynag/swaynag.1.scd52
5 files changed, 1038 insertions, 0 deletions
diff --git a/swaynag/main.c b/swaynag/main.c
new file mode 100644
index 00000000..60560c72
--- /dev/null
+++ b/swaynag/main.c
@@ -0,0 +1,246 @@
1#define _XOPEN_SOURCE 500
2#include <getopt.h>
3#include <signal.h>
4#include "log.h"
5#include "list.h"
6#include "readline.h"
7#include "swaynag/nagbar.h"
8#include "wlr-layer-shell-unstable-v1-client-protocol.h"
9
10static struct sway_nagbar nagbar;
11
12void sig_handler(int signal) {
13 nagbar_destroy(&nagbar);
14 exit(EXIT_FAILURE);
15}
16
17void sway_terminate(int code) {
18 nagbar_destroy(&nagbar);
19 exit(code);
20}
21
22static void set_nagbar_colors() {
23 if (nagbar.type == NAGBAR_ERROR) {
24 nagbar.colors.button_background = 0x680A0AFF;
25 nagbar.colors.background = 0x900000FF;
26 nagbar.colors.text = 0xFFFFFFFF;
27 nagbar.colors.border = 0xD92424FF;
28 nagbar.colors.border_bottom = 0x470909FF;
29 } else if (nagbar.type == NAGBAR_WARNING) {
30 nagbar.colors.button_background = 0xFFC100FF;
31 nagbar.colors.background = 0xFFA800FF;
32 nagbar.colors.text = 0x000000FF;
33 nagbar.colors.border = 0xAB7100FF;
34 nagbar.colors.border_bottom = 0xAB7100FF;
35 }
36}
37
38static char *read_from_stdin() {
39 char *buffer = NULL;
40 while (!feof(stdin)) {
41 char *line = read_line(stdin);
42 if (!line) {
43 continue;
44 }
45
46 if (!buffer) {
47 buffer = strdup(line);
48 } else {
49 buffer = realloc(buffer, strlen(buffer) + strlen(line) + 2);
50 strcat(buffer, line);
51 strcat(buffer, "\n");
52 }
53
54 free(line);
55 }
56
57 if (buffer && buffer[strlen(buffer) - 1] == '\n') {
58 buffer[strlen(buffer) - 1] = '\0';
59 }
60
61 return buffer;
62}
63
64int main(int argc, char **argv) {
65 int exit_code = EXIT_SUCCESS;
66 bool debug = false;
67
68 memset(&nagbar, 0, sizeof(nagbar));
69 nagbar.anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
70 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
71 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
72 nagbar.type = NAGBAR_ERROR;
73 set_nagbar_colors();
74 nagbar.font = strdup("pango:monospace 10");
75 nagbar.buttons = create_list();
76
77 struct sway_nagbar_button *button_close =
78 calloc(sizeof(struct sway_nagbar_button), 1);
79 button_close->text = strdup("X");
80 button_close->type = NAGBAR_ACTION_DISMISS;
81 list_add(nagbar.buttons, button_close);
82
83 struct sway_nagbar_button *button_details =
84 calloc(sizeof(struct sway_nagbar_button), 1);
85 button_details->text = strdup("Toggle Details");
86 button_details->type = NAGBAR_ACTION_EXPAND;
87
88 static struct option opts[] = {
89 {"button", required_argument, NULL, 'b'},
90 {"debug", no_argument, NULL, 'd'},
91 {"edge", required_argument, NULL, 'e'},
92 {"font", required_argument, NULL, 'f'},
93 {"help", no_argument, NULL, 'h'},
94 {"detailed-message", no_argument, NULL, 'l'},
95 {"detailed-button", required_argument, NULL, 'L'},
96 {"message", required_argument, NULL, 'm'},
97 {"output", required_argument, NULL, 'o'},
98 {"dismiss-button", required_argument, NULL, 's'},
99 {"type", required_argument, NULL, 't'},
100 {"version", no_argument, NULL, 'v'},
101 {0, 0, 0, 0}
102 };
103
104 const char *usage =
105 "Usage: swaynag [options...]\n"
106 "\n"
107 " -b, --button <text> <action> Create a button with text that "
108 "executes action when pressed. Multiple buttons can be defined.\n"
109 " -d, --debug Enable debugging.\n"
110 " -e, --edge top|bottom Set the edge to use.\n"
111 " -f, --font <font> Set the font to use.\n"
112 " -h, --help Show help message and quit.\n"
113 " -l, --detailed-message Read a detailed message from stdin.\n"
114 " -L, --detailed-button <text> Set the text of the detail button.\n"
115 " -m, --message <msg> Set the message text.\n"
116 " -o, --output <output> Set the output to use.\n"
117 " -s, --dismiss-button <text> Set the dismiss button text.\n"
118 " -t, --type error|warning Set the message type.\n"
119 " -v, --version Show the version number and quit.\n";
120
121 while (1) {
122 int c = getopt_long(argc, argv, "b:de:f:hlL:m:o:s:t:v", opts, NULL);
123 if (c == -1) {
124 break;
125 }
126 switch (c) {
127 case 'b': // Button
128 if (optind >= argc) {
129 fprintf(stderr, "Missing action for button %s\n", optarg);
130 exit_code = EXIT_FAILURE;
131 goto cleanup;
132 }
133 struct sway_nagbar_button *button;
134 button = calloc(sizeof(struct sway_nagbar_button), 1);
135 button->text = strdup(optarg);
136 button->type = NAGBAR_ACTION_COMMAND;
137 button->action = strdup(argv[optind]);
138 optind++;
139 list_add(nagbar.buttons, button);
140 break;
141 case 'd': // Debug
142 debug = true;
143 break;
144 case 'e': // Edge
145 if (strcmp(optarg, "top") == 0) {
146 nagbar.anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP
147 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
148 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
149 } else if (strcmp(optarg, "bottom") == 0) {
150 nagbar.anchors = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM
151 | ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT
152 | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
153 } else {
154 fprintf(stderr, "Invalid edge: %s\n", optarg);
155 exit_code = EXIT_FAILURE;
156 goto cleanup;
157 }
158 break;
159 case 'f': // Font
160 free(nagbar.font);
161 nagbar.font = strdup(optarg);
162 break;
163 case 'l': // Detailed Message
164 free(nagbar.details.message);
165 nagbar.details.message = read_from_stdin();
166 nagbar.details.button_up.text = strdup("▲");
167 nagbar.details.button_down.text = strdup("▼");
168 break;
169 case 'L': // Detailed Button Text
170 free(button_details->text);
171 button_details->text = strdup(optarg);
172 break;
173 case 'm': // Message
174 free(nagbar.message);
175 nagbar.message = strdup(optarg);
176 break;
177 case 'o': // Output
178 free(nagbar.output.name);
179 nagbar.output.name = strdup(optarg);
180 break;
181 case 's': // Dismiss Button Text
182 free(button_close->text);
183 button_close->text = strdup(optarg);
184 break;
185 case 't': // Type
186 if (strcmp(optarg, "error") == 0) {
187 nagbar.type = NAGBAR_ERROR;
188 } else if (strcmp(optarg, "warning") == 0) {
189 nagbar.type = NAGBAR_WARNING;
190 } else {
191 fprintf(stderr, "Type must be either 'error' or 'warning'\n");
192 exit_code = EXIT_FAILURE;
193 goto cleanup;
194 }
195 set_nagbar_colors();
196 break;
197 case 'v': // Version
198 fprintf(stdout, "swaynag version " SWAY_VERSION "\n");
199 exit_code = EXIT_SUCCESS;
200 goto cleanup;
201 default: // Help or unknown flag
202 fprintf(c == 'h' ? stdout : stderr, "%s", usage);
203 exit_code = c == 'h' ? EXIT_SUCCESS : EXIT_FAILURE;
204 goto cleanup;
205 }
206 }
207
208 wlr_log_init(debug ? WLR_DEBUG : WLR_ERROR, NULL);
209
210 if (!nagbar.message) {
211 wlr_log(WLR_ERROR, "No message passed. Please provide --message/-m");
212 exit_code = EXIT_FAILURE;
213 goto cleanup;
214 }
215
216 if (nagbar.details.message) {
217 list_add(nagbar.buttons, button_details);
218 } else {
219 free(button_details->text);
220 free(button_details);
221 }
222
223 wlr_log(WLR_DEBUG, "Output: %s", nagbar.output.name);
224 wlr_log(WLR_DEBUG, "Anchors: %d", nagbar.anchors);
225 wlr_log(WLR_DEBUG, "Type: %d", nagbar.type);
226 wlr_log(WLR_DEBUG, "Message: %s", nagbar.message);
227 wlr_log(WLR_DEBUG, "Font: %s", nagbar.font);
228 wlr_log(WLR_DEBUG, "Buttons");
229 for (int i = 0; i < nagbar.buttons->length; i++) {
230 struct sway_nagbar_button *button = nagbar.buttons->items[i];
231 wlr_log(WLR_DEBUG, "\t[%s] `%s`", button->text, button->action);
232 }
233
234 signal(SIGTERM, sig_handler);
235
236 nagbar_setup(&nagbar);
237 nagbar_run(&nagbar);
238 return exit_code;
239
240cleanup:
241 free(button_details->text);
242 free(button_details);
243 nagbar_destroy(&nagbar);
244 return exit_code;
245}
246
diff --git a/swaynag/meson.build b/swaynag/meson.build
new file mode 100644
index 00000000..6492e4dc
--- /dev/null
+++ b/swaynag/meson.build
@@ -0,0 +1,20 @@
1executable(
2 'swaynag', [
3 'main.c',
4 'nagbar.c',
5 'render.c',
6 ],
7 include_directories: [sway_inc],
8 dependencies: [
9 cairo,
10 client_protos,
11 gdk_pixbuf,
12 pango,
13 pangocairo,
14 wayland_client,
15 wayland_cursor,
16 wlroots,
17 ],
18 link_with: [lib_sway_common, lib_sway_client],
19 install: true
20)
diff --git a/swaynag/nagbar.c b/swaynag/nagbar.c
new file mode 100644
index 00000000..6647e8c2
--- /dev/null
+++ b/swaynag/nagbar.c
@@ -0,0 +1,420 @@
1#define _XOPEN_SOURCE 500
2#include <assert.h>
3#include <sys/stat.h>
4#include <sys/wait.h>
5#include <wayland-client.h>
6#include <wayland-cursor.h>
7#include "log.h"
8#include "list.h"
9#include "swaynag/nagbar.h"
10#include "swaynag/render.h"
11#include "wlr-layer-shell-unstable-v1-client-protocol.h"
12
13static void nop() {
14 // Intentionally left blank
15}
16
17static bool terminal_execute(char *terminal, char *command) {
18 char fname[] = "/tmp/swaynagXXXXXX";
19 FILE *tmp= fdopen(mkstemp(fname), "w");
20 if (!tmp) {
21 wlr_log(WLR_ERROR, "Failed to create temp script");
22 return false;
23 }
24 wlr_log(WLR_DEBUG, "Created temp script: %s", fname);
25 fprintf(tmp, "#!/bin/sh\nrm %s\n%s", fname, command);
26 fclose(tmp);
27 chmod(fname, S_IRUSR | S_IWUSR | S_IXUSR);
28 char cmd[strlen(terminal) + strlen(" -e ") + strlen(fname) + 1];
29 sprintf(cmd, "%s -e %s", terminal, fname);
30 execl("/bin/sh", "/bin/sh", "-c", cmd, NULL);
31 return true;
32}
33
34static void nagbar_button_execute(struct sway_nagbar *nagbar,
35 struct sway_nagbar_button *button) {
36 wlr_log(WLR_DEBUG, "Executing [%s]: %s", button->text, button->action);
37 if (button->type == NAGBAR_ACTION_DISMISS) {
38 nagbar->run_display = false;
39 } else if (button->type == NAGBAR_ACTION_EXPAND) {
40 nagbar->details.visible = !nagbar->details.visible;
41 render_frame(nagbar);
42 } else {
43 if (fork() == 0) {
44 // Child process. Will be used to prevent zombie processes
45 setsid();
46 if (fork() == 0) {
47 // Child of the child. Will be reparented to the init process
48 char *terminal = getenv("TERMINAL");
49 if (terminal && strlen(terminal)) {
50 wlr_log(WLR_DEBUG, "Found $TERMINAL: %s", terminal);
51 if (!terminal_execute(terminal, button->action)) {
52 nagbar_destroy(nagbar);
53 exit(EXIT_FAILURE);
54 }
55 } else {
56 wlr_log(WLR_DEBUG, "$TERMINAL not found. Running directly");
57 execl("/bin/sh", "/bin/sh", "-c", button->action, NULL);
58 }
59 }
60 exit(EXIT_SUCCESS);
61 }
62 }
63 wait(0);
64}
65
66static void layer_surface_configure(void *data,
67 struct zwlr_layer_surface_v1 *surface,
68 uint32_t serial, uint32_t width, uint32_t height) {
69 struct sway_nagbar *nagbar = data;
70 nagbar->width = width;
71 nagbar->height = height;
72 zwlr_layer_surface_v1_ack_configure(surface, serial);
73 render_frame(nagbar);
74}
75
76static void layer_surface_closed(void *data,
77 struct zwlr_layer_surface_v1 *surface) {
78 struct sway_nagbar *nagbar = data;
79 nagbar_destroy(nagbar);
80}
81
82static struct zwlr_layer_surface_v1_listener layer_surface_listener = {
83 .configure = layer_surface_configure,
84 .closed = layer_surface_closed,
85};
86
87static void wl_pointer_enter(void *data, struct wl_pointer *wl_pointer,
88 uint32_t serial, struct wl_surface *surface,
89 wl_fixed_t surface_x, wl_fixed_t surface_y) {
90 struct sway_nagbar *nagbar = data;
91 struct sway_nagbar_pointer *pointer = &nagbar->pointer;
92 wl_surface_set_buffer_scale(pointer->cursor_surface, nagbar->scale);
93 wl_surface_attach(pointer->cursor_surface,
94 wl_cursor_image_get_buffer(pointer->cursor_image), 0, 0);
95 wl_pointer_set_cursor(wl_pointer, serial, pointer->cursor_surface,
96 pointer->cursor_image->hotspot_x / nagbar->scale,
97 pointer->cursor_image->hotspot_y / nagbar->scale);
98 wl_surface_commit(pointer->cursor_surface);
99}
100
101static void wl_pointer_motion(void *data, struct wl_pointer *wl_pointer,
102 uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y) {
103 struct sway_nagbar *nagbar = data;
104 nagbar->pointer.x = wl_fixed_to_int(surface_x);
105 nagbar->pointer.y = wl_fixed_to_int(surface_y);
106}
107
108static void wl_pointer_button(void *data, struct wl_pointer *wl_pointer,
109 uint32_t serial, uint32_t time, uint32_t button, uint32_t state) {
110 struct sway_nagbar *nagbar = data;
111
112 if (state != WL_POINTER_BUTTON_STATE_PRESSED) {
113 return;
114 }
115
116 double x = nagbar->pointer.x * nagbar->scale;
117 double y = nagbar->pointer.y * nagbar->scale;
118 for (int i = 0; i < nagbar->buttons->length; i++) {
119 struct sway_nagbar_button *nagbutton = nagbar->buttons->items[i];
120 if (x >= nagbutton->x
121 && y >= nagbutton->y
122 && x < nagbutton->x + nagbutton->width
123 && y < nagbutton->y + nagbutton->height) {
124 nagbar_button_execute(nagbar, nagbutton);
125 return;
126 }
127 }
128
129 if (nagbar->details.visible &&
130 nagbar->details.total_lines != nagbar->details.visible_lines) {
131 struct sway_nagbar_button button_up = nagbar->details.button_up;
132 if (x >= button_up.x
133 && y >= button_up.y
134 && x < button_up.x + button_up.width
135 && y < button_up.y + button_up.height
136 && nagbar->details.offset > 0) {
137 nagbar->details.offset--;
138 render_frame(nagbar);
139 return;
140 }
141
142 struct sway_nagbar_button button_down = nagbar->details.button_down;
143 int bot = nagbar->details.total_lines - nagbar->details.visible_lines;
144 if (x >= button_down.x
145 && y >= button_down.y
146 && x < button_down.x + button_down.width
147 && y < button_down.y + button_down.height
148 && nagbar->details.offset < bot) {
149 nagbar->details.offset++;
150 render_frame(nagbar);
151 return;
152 }
153 }
154}
155
156static void wl_pointer_axis(void *data, struct wl_pointer *wl_pointer,
157 uint32_t time, uint32_t axis, wl_fixed_t value) {
158 struct sway_nagbar *nagbar = data;
159 if (!nagbar->details.visible
160 || nagbar->pointer.x < nagbar->details.x
161 || nagbar->pointer.y < nagbar->details.y
162 || nagbar->pointer.x >= nagbar->details.x + nagbar->details.width
163 || nagbar->pointer.y >= nagbar->details.y + nagbar->details.height
164 || nagbar->details.total_lines == nagbar->details.visible_lines) {
165 return;
166 }
167
168 int direction = wl_fixed_to_int(value);
169 int bot = nagbar->details.total_lines - nagbar->details.visible_lines;
170 if (direction < 0 && nagbar->details.offset > 0) {
171 nagbar->details.offset--;
172 } else if (direction > 0 && nagbar->details.offset < bot) {
173 nagbar->details.offset++;
174 }
175
176 render_frame(nagbar);
177}
178
179static struct wl_pointer_listener pointer_listener = {
180 .enter = wl_pointer_enter,
181 .leave = nop,
182 .motion = wl_pointer_motion,
183 .button = wl_pointer_button,
184 .axis = wl_pointer_axis,
185 .frame = nop,
186 .axis_source = nop,
187 .axis_stop = nop,
188 .axis_discrete = nop,
189};
190
191static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
192 enum wl_seat_capability caps) {
193 struct sway_nagbar *nagbar = data;
194 if ((caps & WL_SEAT_CAPABILITY_POINTER)) {
195 nagbar->pointer.pointer = wl_seat_get_pointer(wl_seat);
196 wl_pointer_add_listener(nagbar->pointer.pointer, &pointer_listener,
197 nagbar);
198 }
199}
200
201const struct wl_seat_listener seat_listener = {
202 .capabilities = seat_handle_capabilities,
203 .name = nop,
204};
205
206static void output_scale(void *data, struct wl_output *output,
207 int32_t factor) {
208 struct sway_nagbar *nagbar = data;
209 nagbar->scale = factor;
210 render_frame(nagbar);
211}
212
213static struct wl_output_listener output_listener = {
214 .geometry = nop,
215 .mode = nop,
216 .done = nop,
217 .scale = output_scale,
218};
219
220struct output_state {
221 struct wl_output *wl_output;
222 uint32_t wl_name;
223 struct zxdg_output_v1 *xdg_output;
224 struct sway_nagbar *nagbar;
225};
226
227static void xdg_output_handle_name(void *data,
228 struct zxdg_output_v1 *xdg_output, const char *name) {
229 struct output_state *state = data;
230 char *outname = state->nagbar->output.name;
231 wlr_log(WLR_DEBUG, "Checking against output %s for %s", name, outname);
232 if ((!outname && !state->nagbar->output.wl_output)
233 || (name && outname && strcmp(name, outname) == 0)) {
234 wlr_log(WLR_DEBUG, "Using output %s", name);
235 state->nagbar->output.wl_output = state->wl_output;
236 state->nagbar->output.wl_name = state->wl_name;
237 wl_output_add_listener(state->nagbar->output.wl_output,
238 &output_listener, state->nagbar);
239 wl_display_roundtrip(state->nagbar->display);
240 zxdg_output_v1_destroy(state->xdg_output);
241 } else {
242 zxdg_output_v1_destroy(state->xdg_output);
243 wl_output_destroy(state->wl_output);
244 }
245 state->nagbar->querying_outputs--;
246 free(state);
247}
248
249static struct zxdg_output_v1_listener xdg_output_listener = {
250 .logical_position = nop,
251 .logical_size = nop,
252 .done = nop,
253 .name = xdg_output_handle_name,
254 .description = nop,
255};
256
257static void handle_global(void *data, struct wl_registry *registry,
258 uint32_t name, const char *interface, uint32_t version) {
259 struct sway_nagbar *nagbar = data;
260 if (strcmp(interface, wl_compositor_interface.name) == 0) {
261 nagbar->compositor = wl_registry_bind(registry, name,
262 &wl_compositor_interface, 3);
263 } else if (strcmp(interface, wl_seat_interface.name) == 0) {
264 nagbar->seat = wl_registry_bind(registry, name, &wl_seat_interface, 1);
265 wl_seat_add_listener(nagbar->seat, &seat_listener, nagbar);
266 } else if (strcmp(interface, wl_shm_interface.name) == 0) {
267 nagbar->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
268 } else if (strcmp(interface, wl_output_interface.name) == 0) {
269 if (!nagbar->output.wl_output && nagbar->xdg_output_manager) {
270 nagbar->querying_outputs++;
271 struct output_state *state =
272 calloc(1, sizeof(struct output_state));
273 state->nagbar = nagbar;
274 state->wl_output = wl_registry_bind(registry, name,
275 &wl_output_interface, 3);
276 state->wl_name = name;
277 state->xdg_output = zxdg_output_manager_v1_get_xdg_output(
278 nagbar->xdg_output_manager, state->wl_output);
279 zxdg_output_v1_add_listener(state->xdg_output,
280 &xdg_output_listener, state);
281 } else if (!nagbar->output.wl_output && !nagbar->xdg_output_manager) {
282 wlr_log(WLR_ERROR, "Warning: zxdg_output_manager_v1 not supported."
283 " Falling back to first detected output");
284 nagbar->output.wl_output = wl_registry_bind(registry, name,
285 &wl_output_interface, 3);
286 nagbar->output.wl_name = name;
287 wl_output_add_listener(nagbar->output.wl_output,
288 &output_listener, nagbar);
289 }
290 } else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
291 nagbar->layer_shell = wl_registry_bind(
292 registry, name, &zwlr_layer_shell_v1_interface, 1);
293 } else if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0
294 && version >= ZXDG_OUTPUT_V1_NAME_SINCE_VERSION) {
295 nagbar->xdg_output_manager = wl_registry_bind(registry, name,
296 &zxdg_output_manager_v1_interface,
297 ZXDG_OUTPUT_V1_NAME_SINCE_VERSION);
298 }
299}
300
301static void handle_global_remove(void *data, struct wl_registry *registry,
302 uint32_t name) {
303 struct sway_nagbar *nagbar = data;
304 if (nagbar->output.wl_name == name) {
305 nagbar->run_display = false;
306 }
307}
308
309static const struct wl_registry_listener registry_listener = {
310 .global = handle_global,
311 .global_remove = handle_global_remove,
312};
313
314void nagbar_setup(struct sway_nagbar *nagbar) {
315 nagbar->display = wl_display_connect(NULL);
316 assert(nagbar->display);
317
318 nagbar->scale = 1;
319
320 struct wl_registry *registry = wl_display_get_registry(nagbar->display);
321 wl_registry_add_listener(registry, &registry_listener, nagbar);
322 wl_display_roundtrip(nagbar->display);
323 assert(nagbar->compositor && nagbar->layer_shell && nagbar->shm);
324
325 while (nagbar->querying_outputs > 0) {
326 wl_display_roundtrip(nagbar->display);
327 }
328
329 if (!nagbar->output.wl_output) {
330 if (nagbar->output.name) {
331 wlr_log(WLR_ERROR, "Output '%s' not found", nagbar->output.name);
332 } else {
333 wlr_log(WLR_ERROR, "No outputs detected");
334 }
335 nagbar_destroy(nagbar);
336 exit(EXIT_FAILURE);
337 }
338
339 struct sway_nagbar_pointer *pointer = &nagbar->pointer;
340 int scale = nagbar->scale < 1 ? 1 : nagbar->scale;
341 pointer->cursor_theme = wl_cursor_theme_load(
342 NULL, 24 * scale, nagbar->shm);
343 assert(pointer->cursor_theme);
344 struct wl_cursor *cursor =
345 wl_cursor_theme_get_cursor(pointer->cursor_theme, "left_ptr");
346 assert(cursor);
347 pointer->cursor_image = cursor->images[0];
348 pointer->cursor_surface = wl_compositor_create_surface(nagbar->compositor);
349 assert(pointer->cursor_surface);
350
351 nagbar->surface = wl_compositor_create_surface(nagbar->compositor);
352 assert(nagbar->surface);
353 nagbar->layer_surface = zwlr_layer_shell_v1_get_layer_surface(
354 nagbar->layer_shell, nagbar->surface, nagbar->output.wl_output,
355 ZWLR_LAYER_SHELL_V1_LAYER_TOP, "swaynag");
356 assert(nagbar->layer_surface);
357 zwlr_layer_surface_v1_add_listener(nagbar->layer_surface,
358 &layer_surface_listener, nagbar);
359 zwlr_layer_surface_v1_set_anchor(nagbar->layer_surface, nagbar->anchors);
360
361 wl_registry_destroy(registry);
362}
363
364void nagbar_run(struct sway_nagbar *nagbar) {
365 nagbar->run_display = true;
366 render_frame(nagbar);
367 while (nagbar->run_display && wl_display_dispatch(nagbar->display) != -1) {
368 // This is intentionally left blank
369 }
370}
371
372void nagbar_destroy(struct sway_nagbar *nagbar) {
373 nagbar->run_display = false;
374
375 free(nagbar->message);
376 free(nagbar->font);
377 while (nagbar->buttons->length) {
378 struct sway_nagbar_button *button = nagbar->buttons->items[0];
379 list_del(nagbar->buttons, 0);
380 free(button->text);
381 free(button->action);
382 free(button);
383 }
384 list_free(nagbar->buttons);
385 free(nagbar->details.message);
386 free(nagbar->details.button_up.text);
387 free(nagbar->details.button_down.text);
388
389 if (nagbar->layer_surface) {
390 zwlr_layer_surface_v1_destroy(nagbar->layer_surface);
391 }
392
393 if (nagbar->surface) {
394 wl_surface_destroy(nagbar->surface);
395 }
396
397 if (nagbar->output.wl_output) {
398 wl_output_destroy(nagbar->output.wl_output);
399 }
400
401 if (&nagbar->buffers[0]) {
402 destroy_buffer(&nagbar->buffers[0]);
403 }
404
405 if (&nagbar->buffers[1]) {
406 destroy_buffer(&nagbar->buffers[1]);
407 }
408
409 if (nagbar->compositor) {
410 wl_compositor_destroy(nagbar->compositor);
411 }
412
413 if (nagbar->shm) {
414 wl_shm_destroy(nagbar->shm);
415 }
416
417 if (nagbar->display) {
418 wl_display_disconnect(nagbar->display);
419 }
420}
diff --git a/swaynag/render.c b/swaynag/render.c
new file mode 100644
index 00000000..150ae3f2
--- /dev/null
+++ b/swaynag/render.c
@@ -0,0 +1,300 @@
1#include <stdint.h>
2#include "cairo.h"
3#include "log.h"
4#include "pango.h"
5#include "pool-buffer.h"
6#include "swaynag/nagbar.h"
7#include "wlr-layer-shell-unstable-v1-client-protocol.h"
8
9static uint32_t render_message(cairo_t *cairo, struct sway_nagbar *nagbar) {
10 uint32_t height = nagbar->height * nagbar->scale;
11 height -= NAGBAR_BAR_BORDER_THICKNESS * nagbar->scale;
12
13 int text_width, text_height;
14 get_text_size(cairo, nagbar->font, &text_width, &text_height,
15 nagbar->scale, true, "%s", nagbar->message);
16
17 int padding = NAGBAR_MESSAGE_PADDING * nagbar->scale;
18
19 uint32_t ideal_height = text_height + padding * 2;
20 uint32_t ideal_surface_height = ideal_height / nagbar->scale;
21 if (nagbar->height < ideal_surface_height) {
22 return ideal_surface_height;
23 }
24
25 cairo_set_source_u32(cairo, nagbar->colors.text);
26 cairo_move_to(cairo, padding, (int)(ideal_height - text_height) / 2);
27 pango_printf(cairo, nagbar->font, nagbar->scale, true, "%s",
28 nagbar->message);
29
30 return ideal_height;
31}
32
33static void render_details_scroll_button(cairo_t *cairo,
34 struct sway_nagbar *nagbar, struct sway_nagbar_button *button) {
35 int text_width, text_height;
36 get_text_size(cairo, nagbar->font, &text_width, &text_height,
37 nagbar->scale, true, "%s", button->text);
38
39 int border = NAGBAR_BUTTON_BORDER_THICKNESS * nagbar->scale;
40 int padding = NAGBAR_BUTTON_PADDING * nagbar->scale;
41
42 cairo_set_source_u32(cairo, nagbar->colors.border);
43 cairo_rectangle(cairo, button->x, button->y,
44 button->width, button->height);
45 cairo_fill(cairo);
46
47 cairo_set_source_u32(cairo, nagbar->colors.button_background);
48 cairo_rectangle(cairo, button->x + border, button->y + border,
49 button->width - (border * 2), button->height - (border * 2));
50 cairo_fill(cairo);
51
52 cairo_set_source_u32(cairo, nagbar->colors.text);
53 cairo_move_to(cairo, button->x + border + padding,
54 button->y + border + (button->height - text_height) / 2);
55 pango_printf(cairo, nagbar->font, nagbar->scale, true, "%s", button->text);
56}
57
58static int get_detailed_scroll_button_width(cairo_t *cairo,
59 struct sway_nagbar *nagbar) {
60 int up_width, down_width, temp_height;
61 get_text_size(cairo, nagbar->font, &up_width, &temp_height,
62 nagbar->scale, true, "%s", nagbar->details.button_up.text);
63 get_text_size(cairo, nagbar->font, &down_width, &temp_height,
64 nagbar->scale, true, "%s", nagbar->details.button_down.text);
65
66 int text_width = up_width > down_width ? up_width : down_width;
67 int border = NAGBAR_BUTTON_BORDER_THICKNESS * nagbar->scale;
68 int padding = NAGBAR_BUTTON_PADDING * nagbar->scale;
69
70 return text_width + border * 2 + padding * 2;
71}
72
73static uint32_t render_detailed(cairo_t *cairo, struct sway_nagbar *nagbar,
74 uint32_t y) {
75 uint32_t width = nagbar->width * nagbar->scale;
76 uint32_t height = nagbar->height * nagbar->scale;
77 height -= NAGBAR_BAR_BORDER_THICKNESS * nagbar->scale;
78
79 int border = NAGBAR_DETAILS_BORDER_THICKNESS * nagbar->scale;
80 int padding = NAGBAR_MESSAGE_PADDING * nagbar->scale;
81 int decor = padding + border;
82
83 nagbar->details.x = decor;
84 nagbar->details.y = y + decor;
85 nagbar->details.width = width - decor * 2;
86
87 PangoLayout *layout = get_pango_layout(cairo, nagbar->font,
88 nagbar->details.message, nagbar->scale, true);
89 pango_layout_set_width(layout,
90 (nagbar->details.width - padding * 2) * PANGO_SCALE);
91 pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
92 pango_layout_set_single_paragraph_mode(layout, false);
93 pango_cairo_update_layout(cairo, layout);
94 nagbar->details.total_lines = pango_layout_get_line_count(layout);
95
96 PangoLayoutLine *line;
97 line = pango_layout_get_line_readonly(layout, nagbar->details.offset);
98 gint offset = line->start_index;
99 const char *text = pango_layout_get_text(layout);
100 pango_layout_set_text(layout, text + offset, strlen(text) - offset);
101
102 int text_width, text_height;
103 pango_cairo_update_layout(cairo, layout);
104 pango_layout_get_pixel_size(layout, &text_width, &text_height);
105
106 bool show_buttons = nagbar->details.offset > 0;
107 int button_width = get_detailed_scroll_button_width(cairo, nagbar);
108 if (show_buttons) {
109 nagbar->details.width -= button_width;
110 pango_layout_set_width(layout,
111 (nagbar->details.width - padding * 2) * PANGO_SCALE);
112 }
113
114 uint32_t ideal_height;
115 do {
116 ideal_height = nagbar->details.y + text_height + decor + padding * 2;
117 if (ideal_height > NAGBAR_MAX_HEIGHT) {
118 ideal_height = NAGBAR_MAX_HEIGHT;
119
120 if (!show_buttons) {
121 show_buttons = true;
122 nagbar->details.width -= button_width;
123 pango_layout_set_width(layout,
124 (nagbar->details.width - padding * 2) * PANGO_SCALE);
125 }
126 }
127
128 nagbar->details.height = ideal_height - nagbar->details.y - decor;
129 pango_layout_set_height(layout,
130 (nagbar->details.height - padding * 2) * PANGO_SCALE);
131 pango_layout_set_ellipsize(layout, PANGO_ELLIPSIZE_END);
132 pango_cairo_update_layout(cairo, layout);
133 pango_layout_get_pixel_size(layout, &text_width, &text_height);
134 } while (text_height != (nagbar->details.height - padding * 2));
135
136 nagbar->details.visible_lines = pango_layout_get_line_count(layout);
137
138 if (show_buttons) {
139 nagbar->details.button_up.x =
140 nagbar->details.x + nagbar->details.width;
141 nagbar->details.button_up.y = nagbar->details.y;
142 nagbar->details.button_up.width = button_width;
143 nagbar->details.button_up.height = nagbar->details.height / 2;
144 render_details_scroll_button(cairo, nagbar,
145 &nagbar->details.button_up);
146
147 nagbar->details.button_down.x =
148 nagbar->details.x + nagbar->details.width;
149 nagbar->details.button_down.y =
150 nagbar->details.button_up.y + nagbar->details.button_up.height;
151 nagbar->details.button_down.width = button_width;
152 nagbar->details.button_down.height = nagbar->details.height / 2;
153 render_details_scroll_button(cairo, nagbar,
154 &nagbar->details.button_down);
155 }
156
157 cairo_set_source_u32(cairo, nagbar->colors.border);
158 cairo_rectangle(cairo, nagbar->details.x, nagbar->details.y,
159 nagbar->details.width, nagbar->details.height);
160 cairo_fill(cairo);
161
162 cairo_move_to(cairo, nagbar->details.x + padding,
163 nagbar->details.y + padding);
164 cairo_set_source_u32(cairo, nagbar->colors.text);
165 pango_cairo_show_layout(cairo, layout);
166 g_object_unref(layout);
167
168 return ideal_height;
169}
170
171static uint32_t render_button(cairo_t *cairo, struct sway_nagbar *nagbar,
172 int button_index, int *x) {
173 uint32_t height = nagbar->height * nagbar->scale;
174 height -= NAGBAR_BAR_BORDER_THICKNESS * nagbar->scale;
175 struct sway_nagbar_button *button = nagbar->buttons->items[button_index];
176
177 int text_width, text_height;
178 get_text_size(cairo, nagbar->font, &text_width, &text_height,
179 nagbar->scale, true, "%s", button->text);
180
181 int border = NAGBAR_BUTTON_BORDER_THICKNESS * nagbar->scale;
182 int padding = NAGBAR_BUTTON_PADDING * nagbar->scale;
183
184 uint32_t ideal_height = text_height + padding * 2 + border * 2;
185 uint32_t ideal_surface_height = ideal_height / nagbar->scale;
186 if (nagbar->height < ideal_surface_height) {
187 return ideal_surface_height;
188 }
189
190 button->x = *x - border - text_width - padding * 2;
191 button->y = (int)(ideal_height - text_height) / 2 - padding;
192 button->width = text_width + padding * 2;
193 button->height = text_height + padding * 2;
194
195 cairo_set_source_u32(cairo, nagbar->colors.border);
196 cairo_rectangle(cairo, button->x - border, button->y - border,
197 button->width + border * 2, button->height + border * 2);
198 cairo_fill(cairo);
199
200 cairo_set_source_u32(cairo, nagbar->colors.button_background);
201 cairo_rectangle(cairo, button->x, button->y,
202 button->width, button->height);
203 cairo_fill(cairo);
204
205 cairo_set_source_u32(cairo, nagbar->colors.text);
206 cairo_move_to(cairo, button->x + padding, button->y + padding);
207 pango_printf(cairo, nagbar->font, nagbar->scale, true, "%s", button->text);
208
209 *x = button->x - border;
210
211 return ideal_height;
212}
213
214static uint32_t render_to_cairo(cairo_t *cairo, struct sway_nagbar *nagbar) {
215 uint32_t max_height = 0;
216
217 cairo_set_operator(cairo, CAIRO_OPERATOR_SOURCE);
218 cairo_set_source_u32(cairo, nagbar->colors.background);
219 cairo_paint(cairo);
220
221 uint32_t h = render_message(cairo, nagbar);
222 max_height = h > max_height ? h : max_height;
223
224 int x = (nagbar->width - NAGBAR_BUTTON_MARGIN_RIGHT) * nagbar->scale;
225 for (int i = 0; i < nagbar->buttons->length; i++) {
226 h = render_button(cairo, nagbar, i, &x);
227 max_height = h > max_height ? h : max_height;
228 x -= NAGBAR_BUTTON_GAP * nagbar->scale;
229 if (i == 0) {
230 x -= NAGBAR_BUTTON_GAP_CLOSE * nagbar->scale;
231 }
232 }
233
234 if (nagbar->details.visible) {
235 h = render_detailed(cairo, nagbar, max_height);
236 max_height = h > max_height ? h : max_height;
237 }
238
239 int border = NAGBAR_BAR_BORDER_THICKNESS * nagbar->scale;
240 if (max_height > nagbar->height) {
241 max_height += border;
242 }
243 cairo_set_source_u32(cairo, nagbar->colors.border_bottom);
244 cairo_rectangle(cairo, 0, nagbar->height * nagbar->scale - border,
245 nagbar->width * nagbar->scale, border);
246 cairo_fill(cairo);
247
248 return max_height;
249}
250
251void render_frame(struct sway_nagbar *nagbar) {
252 if (!nagbar->run_display) {
253 return;
254 }
255
256 cairo_surface_t *recorder = cairo_recording_surface_create(
257 CAIRO_CONTENT_COLOR_ALPHA, NULL);
258 cairo_t *cairo = cairo_create(recorder);
259 cairo_save(cairo);
260 cairo_set_operator(cairo, CAIRO_OPERATOR_CLEAR);
261 cairo_paint(cairo);
262 cairo_restore(cairo);
263 uint32_t height = render_to_cairo(cairo, nagbar);
264 if (height != nagbar->height) {
265 zwlr_layer_surface_v1_set_size(nagbar->layer_surface, 0, height);
266 zwlr_layer_surface_v1_set_exclusive_zone(nagbar->layer_surface,
267 height);
268 wl_surface_commit(nagbar->surface);
269 wl_display_roundtrip(nagbar->display);
270 } else {
271 nagbar->current_buffer = get_next_buffer(nagbar->shm,
272 nagbar->buffers,
273 nagbar->width * nagbar->scale,
274 nagbar->height * nagbar->scale);
275 if (!nagbar->current_buffer) {
276 wlr_log(WLR_DEBUG, "Failed to get buffer. Skipping frame.");
277 goto cleanup;
278 }
279
280 cairo_t *shm = nagbar->current_buffer->cairo;
281 cairo_save(shm);
282 cairo_set_operator(shm, CAIRO_OPERATOR_CLEAR);
283 cairo_paint(shm);
284 cairo_restore(shm);
285 cairo_set_source_surface(shm, recorder, 0.0, 0.0);
286 cairo_paint(shm);
287
288 wl_surface_set_buffer_scale(nagbar->surface, nagbar->scale);
289 wl_surface_attach(nagbar->surface,
290 nagbar->current_buffer->buffer, 0, 0);
291 wl_surface_damage(nagbar->surface, 0, 0,
292 nagbar->width, nagbar->height);
293 wl_surface_commit(nagbar->surface);
294 wl_display_roundtrip(nagbar->display);
295 }
296
297cleanup:
298 cairo_surface_destroy(recorder);
299 cairo_destroy(cairo);
300}
diff --git a/swaynag/swaynag.1.scd b/swaynag/swaynag.1.scd
new file mode 100644
index 00000000..879aaf2e
--- /dev/null
+++ b/swaynag/swaynag.1.scd
@@ -0,0 +1,52 @@
1swaynagbar(1)
2
3# NAME
4
5swaynag - Show a warning or error message with buttons
6
7# SYNOPSIS
8
9_swaynag_ [options...]
10
11# OPTIONS
12*-b, --button* <text> <action>
13 Create a button with the text _text_ that executes _action_ when pressed.
14 Multiple buttons can be defined by providing the flag multiple times.
15
16*-d, --debug*
17 Enable debugging.
18
19*-e, --edge top|bottom*
20 Set the edge to use.
21
22*-f, --font <font>*
23 Set the font to use.
24
25*-h, --help*
26 Show help message and quit.
27
28*-l, --detailed-message*
29 Read a detailed message from stdin. A button to toggle details will be
30 added. Details are shown in a scrollable multi-line text area.
31
32*-L, --detailed-button <text>*
33 Set the text for the button that toggles details. This has no effect if
34 there is not a detailed message. The default is _Toggle Details_.
35
36*-m, --message <msg>*
37 Set the message text.
38
39*-o, --output <output>*
40 Set the output to use. This should be the name of a _xdg\_output_. If
41 _xdg\_output\_manager_ is not supported, then the first detected output
42 will be used
43
44*-s, --dismiss-button <text>*
45 Sets the text for the dismiss nagbar button. The default is _X_.
46
47*-t, --type error|warning*
48 Set the message type.
49
50*-v, --version
51 Show the version number and quit.
52