refactor!(wayland): remove wayland code

BREAKING CHANGE: We do no support the `-g` option anymore.

This tool simply makes more sense as the output of `grim` rather than
trying to be `grim`.

RIP my ugly wayland code, long live maintainable code.

Next stop, rust?
This commit is contained in:
Jeremy Attali 2020-06-16 23:02:58 -04:00
parent ceb907a5dc
commit 204a93eb0f
12 changed files with 12 additions and 963 deletions

View File

@ -1,10 +1,6 @@
# swappy
A Wayland native snapshot and editor tool, inspired by [Snappy] on macOS. Works great with [grim], [slurp] and [sway]. Also works with other screenshot tools if you use the `-f` option. See [below](#example-usage).
Wayland code was largely taken from [grim] and requires a compositor that implements the [wlr-screencopy-unstable-v1] protocol.
You can use this tool in two ways, either use it as the output of `grim` (**recommended**) or grab the geometry yourself (`wayland` code is still WIP).
A Wayland native snapshot and editor tool, inspired by [Snappy] on macOS. Works great with [grim], [slurp] and [sway]. But can easily work with other screen copy tools that can output a final PNG image to `stdout`. See [below](#example-usage).
## Screenshot
@ -18,24 +14,12 @@ Output of `grim` (or any tool outputing a PNG file):
grim -g "$(slurp)" - | swappy -f -
```
Swappshot a PNG file (good for compositors not supporting screencopy protocol):
Swappshot a PNG file:
```sh
swappy -f "~/Desktop/my-gnome-saved-file.png"
```
Swappshot a region:
```sh
swappy -g "100,100 200x200"
```
Select a region and swappshot it:
```sh
swappy -g "$(slurp)"
```
Print final surface to stdout (useful to pipe with other tools):
```sh
@ -118,7 +102,6 @@ Install dependencies (on Arch, name can vary for other distros):
- meson
- ninja
- wayland
- cairo
- pango
- gtk
@ -127,7 +110,7 @@ Install dependencies (on Arch, name can vary for other distros):
Optional dependencies:
- wayland-protocols (for the `-g` option to work with [wlr-screencopy-unstable-v1] protocol)
- `wl-clipboard` (to make sure the copy is saved if you close swappy)
- wl-clipboard (to make sure the copy is saved if you close swappy)
- libnotify (not get notified when swappshot is copied or saved)
@ -151,4 +134,3 @@ MIT
[grim]: https://github.com/emersion/grim
[sway]: https://github.com/swaywm/sway
[wl-clipboard]: https://github.com/bugaevc/wl-clipboard
[wlr-screencopy-unstable-v1]: https://github.com/swaywm/wlr-protocols/blob/master/unstable/wlr-screencopy-unstable-v1.xml

View File

@ -1,17 +1,7 @@
#pragma once
#include <stdio.h>
#include <wayland-client.h>
#include "swappy.h"
void buffer_wayland_destroy(struct swappy_buffer *buffer);
bool buffer_init_from_screencopy(struct swappy_state *state);
bool buffer_init_from_file(struct swappy_state *state);
bool buffer_parse_geometry(struct swappy_state *state);
void buffer_resize_patterns(struct swappy_state *state);
void buffer_free_all(struct swappy_state *state);

View File

@ -5,17 +5,9 @@
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <wayland-client.h>
#ifdef HAVE_WAYLAND_PROTOCOLS
#include <xdg-output-unstable-v1-client-protocol.h>
#endif
#include "wlr-screencopy-unstable-v1-client-protocol.h"
#define MAX_PATH 4096
#define GEOMETRY_PATTERN "xx,yy wwxhh"
#define SWAPPY_LINE_SIZE_MIN 1
#define SWAPPY_LINE_SIZE_MAX 50
@ -140,47 +132,6 @@ struct swappy_state_ui {
GtkButton *text_size;
};
struct swappy_buffer {
struct wl_buffer *wl_buffer;
void *data;
int32_t width, height, stride;
size_t size;
enum wl_shm_format format;
};
struct swappy_output {
struct swappy_state *state;
struct swappy_box geometry;
struct swappy_box logical_geometry;
struct wl_output *wl_output;
struct wl_list link;
int32_t scale;
struct swappy_buffer *buffer;
char *name;
enum wl_output_transform transform;
struct zwlr_screencopy_frame_v1 *screencopy_frame;
uint32_t screencopy_frame_flags; // enum zwlr_screencopy_frame_v1_flags
#ifdef HAVE_WAYLAND_PROTOCOLS
struct zxdg_output_v1 *xdg_output;
#endif
};
struct swappy_wayland {
struct wl_display *display;
struct wl_registry *registry;
struct wl_compositor *compositor;
struct wl_shm *shm;
struct wl_list outputs;
struct zwlr_screencopy_manager_v1 *zwlr_screencopy_manager;
size_t n_done;
#ifdef HAVE_WAYLAND_PROTOCOLS
struct zxdg_output_manager_v1 *xdg_output_manager;
#endif
};
struct swappy_config {
char *config_file;
char *save_dir;
@ -195,7 +146,6 @@ struct swappy_state {
struct swappy_state_ui *ui;
struct swappy_config *config;
struct swappy_wayland *wl;
cairo_surface_t *original_image_surface;
cairo_surface_t *scaled_image_surface;
@ -206,7 +156,6 @@ struct swappy_state {
enum swappy_paint_type mode;
/* Options */
char *geometry_str;
char *file_str;
char *output_file;

View File

@ -1,6 +0,0 @@
#pragma once
#include "swappy.h"
bool wayland_init(struct swappy_state *state);
void wayland_finish(struct swappy_state *state);

View File

@ -28,9 +28,6 @@ math = cc.find_library('m')
realtime = cc.find_library('rt')
gtk = dependency('gtk+-3.0', version: '>=3.20.0')
gio = cc.find_library('gio-2.0')
wayland_client = dependency('wayland-client')
wayland_cursor = dependency('wayland-cursor')
wayland_protos = dependency('wayland-protocols', version: '>=1.14', required: false)
libnotify = dependency('libnotify', required: false)
@ -38,13 +35,7 @@ if libnotify.found()
add_project_arguments('-DHAVE_LIBNOTIFY', language: 'c')
endif
if wayland_protos.found()
add_project_arguments('-DHAVE_WAYLAND_PROTOCOLS', language: 'c')
endif
subdir('res')
subdir('protocol')
executable(
'swappy',
@ -63,19 +54,15 @@ executable(
'src/render.c',
'src/notification.c',
'src/util.c',
'src/wayland.c',
]),
dependencies: [
cairo,
pango,
client_protos,
gio,
gtk,
libnotify,
math,
realtime,
wayland_client,
wayland_cursor,
],
link_args: '-rdynamic',
include_directories: [swappy_inc],

View File

@ -1,54 +0,0 @@
wayland_scanner = find_program('wayland-scanner')
# should check wayland_scanner's version, but it is hard to get
if wayland_client.version().version_compare('>=1.14.91')
code_type = 'private-code'
else
code_type = 'code'
endif
wayland_scanner_code = generator(
wayland_scanner,
output: '@BASENAME@-protocol.c',
arguments: [code_type, '@INPUT@', '@OUTPUT@'],
)
wayland_scanner_client = generator(
wayland_scanner,
output: '@BASENAME@-client-protocol.h',
arguments: ['client-header', '@INPUT@', '@OUTPUT@'],
)
if wayland_protos.found()
wl_protocol_dir = wayland_protos.get_pkgconfig_variable('pkgdatadir')
client_protocols = [
[wl_protocol_dir, 'stable/xdg-shell/xdg-shell.xml'],
[wl_protocol_dir, 'unstable/xdg-output/xdg-output-unstable-v1.xml'],
['wlr-screencopy-unstable-v1.xml'],
]
else
client_protocols = [
['wlr-screencopy-unstable-v1.xml'],
]
endif
client_protos_src = []
client_protos_headers = []
foreach p : client_protocols
xml = join_paths(p)
client_protos_src += wayland_scanner_code.process(xml)
client_protos_headers += wayland_scanner_client.process(xml)
endforeach
lib_client_protos = static_library(
'client_protos',
client_protos_src + client_protos_headers,
dependencies: [wayland_client]
) # for the include directory
client_protos = declare_dependency(
link_with: lib_client_protos,
sources: client_protos_headers,
)

View File

@ -1,207 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<protocol name="wlr_screencopy_unstable_v1">
<copyright>
Copyright © 2018 Simon Ser
Copyright © 2019 Andri Yngvason
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
</copyright>
<description summary="screen content capturing on client buffers">
This protocol allows clients to ask the compositor to copy part of the
screen content to a client buffer.
Warning! The protocol described in this file is experimental and
backward incompatible changes may be made. Backward compatible changes
may be added together with the corresponding interface version bump.
Backward incompatible changes are done by bumping the version number in
the protocol and interface names and resetting the interface version.
Once the protocol is to be declared stable, the 'z' prefix and the
version number in the protocol and interface names are removed and the
interface version number is reset.
</description>
<interface name="zwlr_screencopy_manager_v1" version="2">
<description summary="manager to inform clients and begin capturing">
This object is a manager which offers requests to start capturing from a
source.
</description>
<request name="capture_output">
<description summary="capture an output">
Capture the next frame of an entire output.
</description>
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
<arg name="overlay_cursor" type="int"
summary="composite cursor onto the frame"/>
<arg name="output" type="object" interface="wl_output"/>
</request>
<request name="capture_output_region">
<description summary="capture an output's region">
Capture the next frame of an output's region.
The region is given in output logical coordinates, see
xdg_output.logical_size. The region will be clipped to the output's
extents.
</description>
<arg name="frame" type="new_id" interface="zwlr_screencopy_frame_v1"/>
<arg name="overlay_cursor" type="int"
summary="composite cursor onto the frame"/>
<arg name="output" type="object" interface="wl_output"/>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
<arg name="width" type="int"/>
<arg name="height" type="int"/>
</request>
<request name="destroy" type="destructor">
<description summary="destroy the manager">
All objects created by the manager will still remain valid, until their
appropriate destroy request has been called.
</description>
</request>
</interface>
<interface name="zwlr_screencopy_frame_v1" version="2">
<description summary="a frame ready for copy">
This object represents a single frame.
When created, a "buffer" event will be sent. The client will then be able
to send a "copy" request. If the capture is successful, the compositor
will send a "flags" followed by a "ready" event.
If the capture failed, the "failed" event is sent. This can happen anytime
before the "ready" event.
Once either a "ready" or a "failed" event is received, the client should
destroy the frame.
</description>
<event name="buffer">
<description summary="buffer information">
Provides information about the frame's buffer. This event is sent once
as soon as the frame is created.
The client should then create a buffer with the provided attributes, and
send a "copy" request.
</description>
<arg name="format" type="uint" summary="buffer format"/>
<arg name="width" type="uint" summary="buffer width"/>
<arg name="height" type="uint" summary="buffer height"/>
<arg name="stride" type="uint" summary="buffer stride"/>
</event>
<request name="copy">
<description summary="copy the frame">
Copy the frame to the supplied buffer. The buffer must have a the
correct size, see zwlr_screencopy_frame_v1.buffer. The buffer needs to
have a supported format.
If the frame is successfully copied, a "flags" and a "ready" events are
sent. Otherwise, a "failed" event is sent.
</description>
<arg name="buffer" type="object" interface="wl_buffer"/>
</request>
<enum name="error">
<entry name="already_used" value="0"
summary="the object has already been used to copy a wl_buffer"/>
<entry name="invalid_buffer" value="1"
summary="buffer attributes are invalid"/>
</enum>
<enum name="flags" bitfield="true">
<entry name="y_invert" value="1" summary="contents are y-inverted"/>
</enum>
<event name="flags">
<description summary="frame flags">
Provides flags about the frame. This event is sent once before the
"ready" event.
</description>
<arg name="flags" type="uint" enum="flags" summary="frame flags"/>
</event>
<event name="ready">
<description summary="indicates frame is available for reading">
Called as soon as the frame is copied, indicating it is available
for reading. This event includes the time at which presentation happened
at.
The timestamp is expressed as tv_sec_hi, tv_sec_lo, tv_nsec triples,
each component being an unsigned 32-bit value. Whole seconds are in
tv_sec which is a 64-bit value combined from tv_sec_hi and tv_sec_lo,
and the additional fractional part in tv_nsec as nanoseconds. Hence,
for valid timestamps tv_nsec must be in [0, 999999999]. The seconds part
may have an arbitrary offset at start.
After receiving this event, the client should destroy the object.
</description>
<arg name="tv_sec_hi" type="uint"
summary="high 32 bits of the seconds part of the timestamp"/>
<arg name="tv_sec_lo" type="uint"
summary="low 32 bits of the seconds part of the timestamp"/>
<arg name="tv_nsec" type="uint"
summary="nanoseconds part of the timestamp"/>
</event>
<event name="failed">
<description summary="frame copy failed">
This event indicates that the attempted frame copy has failed.
After receiving this event, the client should destroy the object.
</description>
</event>
<request name="destroy" type="destructor">
<description summary="delete this object, used or not">
Destroys the frame. This request can be sent at any time by the client.
</description>
</request>
<!-- Version 2 additions -->
<request name="copy_with_damage" since="2">
<description summary="copy the frame when it's damaged">
Same as copy, except it waits until there is damage to copy.
</description>
<arg name="buffer" type="object" interface="wl_buffer"/>
</request>
<event name="damage" since="2">
<description summary="carries the coordinates of the damaged region">
This event is sent right before the ready event when copy_with_damage is
requested. It may be generated multiple times for each copy_with_damage
request.
The arguments describe a box around an area that has changed since the
last copy request that was derived from the current screencopy manager
instance.
The union of all regions received between the call to copy_with_damage
and a ready event is the total damage since the prior ready event.
</description>
<arg name="x" type="uint" summary="damaged x coordinates"/>
<arg name="y" type="uint" summary="damaged y coordinates"/>
<arg name="width" type="uint" summary="current width"/>
<arg name="height" type="uint" summary="current height"/>
</event>
</interface>
</protocol>

View File

@ -11,7 +11,6 @@
#include "pixbuf.h"
#include "render.h"
#include "swappy.h"
#include "wayland.h"
static void update_ui_undo_redo(struct swappy_state *state) {
GtkWidget *undo = GTK_WIDGET(state->ui->undo);
@ -235,13 +234,11 @@ void application_finish(struct swappy_state *state) {
cairo_surface_destroy(state->original_image_surface);
cairo_surface_destroy(state->scaled_image_surface);
g_free(state->file_str);
g_free(state->geometry_str);
g_free(state->geometry);
g_free(state->window);
g_free(state->ui);
g_object_unref(state->app);
wayland_finish(state);
config_free(state);
}
@ -689,10 +686,6 @@ static bool init_gtk_window(struct swappy_state *state) {
return true;
}
static gboolean has_option_geometry(struct swappy_state *state) {
return (state->geometry_str != NULL);
}
static gboolean has_option_file(struct swappy_state *state) {
return (state->file_str != NULL);
}
@ -716,22 +709,6 @@ static gint command_line_handler(GtkApplication *app,
config_load(state);
init_settings(state);
if (!wayland_init(state)) {
g_warning(
"error while initializing wayland objects, can only be used in file "
"mode");
}
if (has_option_geometry(state)) {
if (!buffer_parse_geometry(state)) {
return EXIT_FAILURE;
}
if (!buffer_init_from_screencopy(state)) {
return EXIT_FAILURE;
}
}
if (has_option_file(state)) {
if (is_file_from_stdin(state->file_str)) {
char *new_file_str = file_dump_stdin_into_a_temp_file();
@ -753,14 +730,6 @@ static gint command_line_handler(GtkApplication *app,
bool application_init(struct swappy_state *state) {
const GOptionEntry cli_options[] = {
{
.long_name = "geometry",
.short_name = 'g',
.arg = G_OPTION_ARG_STRING,
.arg_data = &state->geometry_str,
.description =
"Set the region to capture. (Can be an output of slurp)",
},
{
.long_name = "file",
.short_name = 'f',

View File

@ -1,251 +1,9 @@
#include "buffer.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <cairo.h>
#include "box.h"
static void randname(char *buf) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
long r = ts.tv_nsec;
for (int i = 0; i < 6; ++i) {
buf[i] = 'A' + (r & 15) + (r & 16) * 2;
r >>= 5;
}
}
static int anonymous_shm_open(void) {
char name[] = "/swappy-XXXXXX";
int retries = 100;
do {
randname(name + strlen(name) - 6);
--retries;
// 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;
}
} while (retries > 0 && errno == EEXIST);
return -1;
}
static int create_shm_file(off_t size) {
int fd = anonymous_shm_open();
if (fd < 0) {
return fd;
}
if (ftruncate(fd, size) < 0) {
close(fd);
return -1;
}
return fd;
}
static cairo_format_t get_cairo_format(enum wl_shm_format wl_fmt) {
switch (wl_fmt) {
case WL_SHM_FORMAT_ARGB8888:
return CAIRO_FORMAT_ARGB32;
case WL_SHM_FORMAT_XRGB8888:
return CAIRO_FORMAT_RGB24;
default:
return CAIRO_FORMAT_INVALID;
}
}
static int get_output_flipped(enum wl_output_transform transform) {
return transform & WL_OUTPUT_TRANSFORM_FLIPPED ? -1 : 1;
}
static void apply_output_transform(enum wl_output_transform transform,
int32_t *width, int32_t *height) {
if (transform & WL_OUTPUT_TRANSFORM_90) {
int32_t tmp = *width;
*width = *height;
*height = tmp;
}
}
static struct swappy_buffer *create_buffer(struct wl_shm *shm,
enum wl_shm_format format,
int32_t width, int32_t height,
int32_t stride) {
size_t size = stride * height;
int fd = create_shm_file(size);
if (fd == -1) {
return NULL;
}
void *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
close(fd);
return NULL;
}
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
struct wl_buffer *wl_buffer =
wl_shm_pool_create_buffer(pool, 0, width, height, stride, format);
wl_shm_pool_destroy(pool);
close(fd);
struct swappy_buffer *buffer = calloc(1, sizeof(struct swappy_buffer));
buffer->wl_buffer = wl_buffer;
buffer->data = data;
buffer->width = width;
buffer->height = height;
buffer->stride = stride;
buffer->size = size;
buffer->format = format;
return buffer;
}
static void screencopy_frame_handle_buffer(
void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format,
uint32_t width, uint32_t height, uint32_t stride) {
struct swappy_output *output = data;
output->buffer =
create_buffer(output->state->wl->shm, format, width, height, stride);
if (output->buffer == NULL) {
g_warning("failed to create buffer");
exit(EXIT_FAILURE);
}
zwlr_screencopy_frame_v1_copy(frame, output->buffer->wl_buffer);
}
static void screencopy_frame_handle_flags(
void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) {
struct swappy_output *output = data;
output->screencopy_frame_flags = flags;
}
static void screencopy_frame_handle_ready(
void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t tv_sec_hi,
uint32_t tv_sec_lo, uint32_t tv_nsec) {
struct swappy_output *output = data;
++output->state->wl->n_done;
}
static void screencopy_frame_handle_failed(
void *data, struct zwlr_screencopy_frame_v1 *frame) {
struct swappy_output *output = data;
g_warning("screencopy: failed to copy output %s", output->name);
exit(EXIT_FAILURE);
}
bool buffer_init_from_screencopy(struct swappy_state *state) {
struct swappy_box *geometry = state->geometry;
int32_t with_cursor = 0;
size_t n_pending = 0;
struct swappy_output *output;
g_assert(geometry != NULL);
const struct zwlr_screencopy_frame_v1_listener screencopy_frame_listener = {
.buffer = screencopy_frame_handle_buffer,
.flags = screencopy_frame_handle_flags,
.ready = screencopy_frame_handle_ready,
.failed = screencopy_frame_handle_failed,
};
wl_list_for_each(output, &state->wl->outputs, link) {
if (state->geometry != NULL &&
!intersect_box(state->geometry, &output->logical_geometry)) {
continue;
}
output->screencopy_frame = zwlr_screencopy_manager_v1_capture_output(
state->wl->zwlr_screencopy_manager, with_cursor, output->wl_output);
zwlr_screencopy_frame_v1_add_listener(output->screencopy_frame,
&screencopy_frame_listener, output);
++n_pending;
}
if (n_pending == 0) {
g_warning("screencopy: region is empty");
return EXIT_FAILURE;
}
bool done = false;
while (!done && wl_display_dispatch(state->wl->display) != -1) {
done = (state->wl->n_done == n_pending);
}
if (!done) {
g_warning("failed to screenshot all outputs");
return EXIT_FAILURE;
}
wl_list_for_each(output, &state->wl->outputs, link) {
struct swappy_buffer *buffer = output->buffer;
if (output->buffer == NULL) {
// screencopy buffer is empty, cannot draw it onto the paint area"
continue;
}
cairo_format_t format = get_cairo_format(buffer->format);
g_assert(format != CAIRO_FORMAT_INVALID);
int32_t output_x = output->logical_geometry.x - geometry->x;
int32_t output_y = output->logical_geometry.y - geometry->y;
int32_t output_width = output->logical_geometry.width;
int32_t output_height = output->logical_geometry.height;
int32_t scale = output->scale;
int32_t raw_output_width = output->geometry.width;
int32_t raw_output_height = output->geometry.height;
apply_output_transform(output->transform, &raw_output_width,
&raw_output_height);
int output_flipped_x = get_output_flipped(output->transform);
int output_flipped_y =
output->screencopy_frame_flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT
? -1
: 1;
cairo_surface_t *output_surface = cairo_image_surface_create_for_data(
buffer->data, format, buffer->width, buffer->height, buffer->stride);
cairo_pattern_t *output_pattern =
cairo_pattern_create_for_surface(output_surface);
// All transformations are in pattern-local coordinates
cairo_matrix_t matrix;
cairo_matrix_init_identity(&matrix);
cairo_matrix_translate(&matrix, (double)output->geometry.width / 2,
(double)output->geometry.height / 2);
// cairo_matrix_rotate(&matrix, -get_output_rotation(output->transform));
cairo_matrix_scale(
&matrix, (double)raw_output_width / output_width * output_flipped_x,
(double)raw_output_height / output_height * output_flipped_y);
cairo_matrix_translate(&matrix, -(double)output_width / 2,
-(double)output_height / 2);
cairo_matrix_translate(&matrix, -output_x, -output_y);
cairo_matrix_scale(&matrix, 1 / scale, 1 / scale);
cairo_pattern_set_matrix(output_pattern, &matrix);
cairo_pattern_set_filter(output_pattern, CAIRO_FILTER_BEST);
state->patterns = g_list_append(state->patterns, output_pattern);
cairo_surface_destroy(output_surface);
}
return true;
}
#include "swappy.h"
bool buffer_init_from_file(struct swappy_state *state) {
char *file = state->file_str;
@ -280,19 +38,6 @@ bool buffer_init_from_file(struct swappy_state *state) {
return true;
}
bool buffer_parse_geometry(struct swappy_state *state) {
struct swappy_box *geometry = g_new(struct swappy_box, 1);
char *geometry_str = state->geometry_str;
state->geometry = geometry;
if (!box_parse(geometry, geometry_str)) {
g_critical("%s is not a valid geometry, must follow the pattern \"%s",
geometry_str, GEOMETRY_PATTERN);
return false;
}
return true;
}
static void scale_pattern(gpointer data, gpointer user_data) {
struct swappy_state *state = (struct swappy_state *)user_data;
cairo_pattern_t *pattern = (cairo_pattern_t *)data;
@ -333,15 +78,6 @@ void buffer_resize_patterns(struct swappy_state *state) {
g_list_foreach(state->patterns, scale_pattern, state);
}
void buffer_wayland_destroy(struct swappy_buffer *buffer) {
if (buffer == NULL) {
return;
}
munmap(buffer->data, buffer->size);
wl_buffer_destroy(buffer->wl_buffer);
free(buffer);
}
static void free_pattern(gpointer data) {
cairo_pattern_t *pattern = data;
cairo_pattern_destroy(pattern);

View File

@ -1,5 +1,7 @@
#include "paint.h"
#include <glib.h>
#include "util.h"
static void cursor_move_backward(struct swappy_paint_text *text) {
@ -157,8 +159,8 @@ void paint_update_temporary_shape(struct swappy_state *state, double x,
// Bounding x and y to the window dimensions to avoid side effects in
// rendering.
x = fmin(fmax(x, 0), width);
y = fmin(fmax(y, 0), height);
x = MIN(MAX(x, 0), width);
y = MIN(MAX(y, 0), height);
switch (paint->type) {
case SWAPPY_PAINT_MODE_BLUR:

View File

@ -1,292 +0,0 @@
#define _POSIX_C_SOURCE 2000809L
#include <stdio.h>
#include <string.h>
#include <wayland-client.h>
#include "buffer.h"
#include "swappy.h"
#include "wlr-screencopy-unstable-v1-client-protocol.h"
#ifdef HAVE_WAYLAND_PROTOCOLS
#include "xdg-output-unstable-v1-client-protocol.h"
#endif
static bool guess_output_logical_geometry(struct swappy_output *output) {
// TODO Implement
g_warning("guessing output is not yet implemented");
return false;
}
void apply_output_transform(enum wl_output_transform transform, int32_t *width,
int32_t *height) {
if (transform & WL_OUTPUT_TRANSFORM_90) {
int32_t tmp = *width;
*width = *height;
*height = tmp;
}
}
#ifdef HAVE_WAYLAND_PROTOCOLS
static void xdg_output_handle_logical_position(
void *data, struct zxdg_output_v1 *xdg_output, int32_t x, int32_t y) {
struct swappy_output *output = data;
output->logical_geometry.x = x;
output->logical_geometry.y = y;
}
static void xdg_output_handle_logical_size(void *data,
struct zxdg_output_v1 *xdg_output,
int32_t width, int32_t height) {
struct swappy_output *output = data;
output->logical_geometry.width = width;
output->logical_geometry.height = height;
}
static void xdg_output_handle_done(void *data,
struct zxdg_output_v1 *xdg_output) {
struct swappy_output *output = data;
// Guess the output scale from the logical size
int32_t width = output->geometry.width;
int32_t height = output->geometry.height;
apply_output_transform(output->transform, &width, &height);
}
static void xdg_output_handle_name(void *data,
struct zxdg_output_v1 *xdg_output,
const char *name) {
struct swappy_output *output = data;
output->name = strdup(name);
}
static void xdg_output_handle_description(void *data,
struct zxdg_output_v1 *xdg_output,
const char *name) {}
static const struct zxdg_output_v1_listener xdg_output_listener = {
.logical_position = xdg_output_handle_logical_position,
.logical_size = xdg_output_handle_logical_size,
.done = xdg_output_handle_done,
.name = xdg_output_handle_name,
.description = xdg_output_handle_description,
};
#endif
static void output_handle_geometry(void *data, struct wl_output *wl_output,
int32_t x, int32_t y, int32_t physical_width,
int32_t physical_height, int32_t subpixel,
const char *make, const char *model,
int32_t transform) {
struct swappy_output *output = data;
output->geometry.x = x;
output->geometry.y = y;
output->transform = transform;
}
static void output_handle_mode(void *data, struct wl_output *wl_output,
uint32_t flags, int32_t width, int32_t height,
int32_t refresh) {
struct swappy_output *output = data;
if ((flags & WL_OUTPUT_MODE_CURRENT) != 0) {
output->geometry.width = width;
output->geometry.height = height;
}
}
static void output_handle_done(void *data, struct wl_output *wl_output) {
// No-op
}
static void output_handle_scale(void *data, struct wl_output *wl_output,
int32_t factor) {
struct swappy_output *output = data;
output->scale = factor;
}
static const struct wl_output_listener output_listener = {
.geometry = output_handle_geometry,
.mode = output_handle_mode,
.done = output_handle_done,
.scale = output_handle_scale,
};
static void global_registry_handler(void *data, struct wl_registry *registry,
uint32_t name, const char *interface,
uint32_t version) {
g_debug("got a registry event for interface: %s, name: %d", interface, name);
struct swappy_state *state = data;
bool bound = false;
if (strcmp(interface, wl_compositor_interface.name) == 0) {
state->wl->compositor =
wl_registry_bind(registry, name, &wl_compositor_interface, version);
bound = true;
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
state->wl->shm =
wl_registry_bind(registry, name, &wl_shm_interface, version);
bound = true;
} else if (strcmp(interface, wl_output_interface.name) == 0) {
struct swappy_output *output = calloc(1, sizeof(struct swappy_output));
output->state = state;
output->scale = 1;
output->wl_output =
wl_registry_bind(registry, name, &wl_output_interface, 3);
wl_output_add_listener(output->wl_output, &output_listener, output);
wl_list_insert(&state->wl->outputs, &output->link);
bound = true;
} else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) ==
0) {
state->wl->zwlr_screencopy_manager = wl_registry_bind(
registry, name, &zwlr_screencopy_manager_v1_interface, version);
bound = true;
} else {
#ifdef HAVE_WAYLAND_PROTOCOLS
if (strcmp(interface, zxdg_output_manager_v1_interface.name) == 0) {
state->wl->xdg_output_manager = wl_registry_bind(
registry, name, &zxdg_output_manager_v1_interface, version);
bound = true;
}
#endif
}
if (bound) {
g_debug("bound registry: %s", interface);
}
}
static void global_registry_remove_handler(void *data,
struct wl_registry *wl_registry,
uint32_t name) {}
static struct wl_registry_listener registry_listener = {
.global = global_registry_handler,
.global_remove = global_registry_remove_handler,
};
bool wayland_init(struct swappy_state *state) {
state->wl = g_new(struct swappy_wayland, 1);
state->wl->display = wl_display_connect(NULL);
state->wl->n_done = 0;
if (state->wl->display == NULL) {
g_warning("cannot connect to wayland display");
return false;
}
wl_list_init(&state->wl->outputs);
state->wl->registry = wl_display_get_registry(state->wl->display);
wl_registry_add_listener(state->wl->registry, &registry_listener, state);
wl_display_roundtrip(state->wl->display);
if (state->wl->compositor == NULL) {
g_warning("compositor doesn't support wl_compositor");
return false;
}
if (state->wl->shm == NULL) {
g_warning("compositor doesn't support wl_shm");
return false;
}
if (wl_list_empty(&state->wl->outputs)) {
g_warning("no wl_output found");
return false;
}
bool found_output_layout = false;
#ifdef HAVE_WAYLAND_PROTOCOLS
if (state->wl->xdg_output_manager != NULL) {
struct swappy_output *output;
wl_list_for_each(output, &state->wl->outputs, link) {
output->xdg_output = zxdg_output_manager_v1_get_xdg_output(
state->wl->xdg_output_manager, output->wl_output);
zxdg_output_v1_add_listener(output->xdg_output, &xdg_output_listener,
output);
}
wl_display_dispatch(state->wl->display);
wl_display_roundtrip(state->wl->display);
found_output_layout = true;
}
#endif
if (!found_output_layout) {
g_warning(
"zxdg_output_manager_v1 isn't available, guessing the output layout");
struct swappy_output *output;
bool output_guessed = false;
wl_list_for_each(output, &state->wl->outputs, link) {
output_guessed = guess_output_logical_geometry(output);
}
if (!output_guessed) {
g_warning("could not guess output logical geometry");
return false;
}
}
if (state->wl->zwlr_screencopy_manager == NULL) {
g_warning("compositor does not support zwlr_screencopy_v1");
return false;
}
return true;
}
void wayland_finish(struct swappy_state *state) {
struct swappy_output *output;
struct swappy_output *output_tmp;
if (!state->wl) {
return;
}
wl_list_for_each_safe(output, output_tmp, &state->wl->outputs, link) {
wl_list_remove(&output->link);
free(output->name);
if (output->screencopy_frame != NULL) {
zwlr_screencopy_frame_v1_destroy(output->screencopy_frame);
}
#ifdef HAVE_WAYLAND_PROTOCOLS
if (output->xdg_output != NULL) {
zxdg_output_v1_destroy(output->xdg_output);
}
#endif
wl_output_release(output->wl_output);
buffer_wayland_destroy(output->buffer);
free(output);
}
if (state->wl->compositor != NULL) {
wl_compositor_destroy(state->wl->compositor);
}
if (state->wl->zwlr_screencopy_manager != NULL) {
zwlr_screencopy_manager_v1_destroy(state->wl->zwlr_screencopy_manager);
}
#ifdef HAVE_WAYLAND_PROTOCOLS
if (state->wl->xdg_output_manager != NULL) {
zxdg_output_manager_v1_destroy(state->wl->xdg_output_manager);
}
#endif
if (state->wl->shm != NULL) {
wl_shm_destroy(state->wl->shm);
}
if (state->wl->registry != NULL) {
wl_registry_destroy(state->wl->registry);
}
if (state->wl->display) {
wl_display_disconnect(state->wl->display);
}
g_free(state->wl);
state->wl = NULL;
}

View File

@ -11,12 +11,10 @@ swappy - grab and edit on the fly snapshots of a Wayland compositor
# SYNOPSIS
swappy is a command-line utility to take and edit screenshots of Wayland
desktops. It can also work on regular X11 desktops if using the *-f* option.
desktops. Works great with grim, slurp and sway. But can easily work with
other screen copy tools that can output a final PNG image to *stdout*.
Can be used in two ways, either as the output of grim (recommended) or by
grabbing the geometry (if the compositor supports the screencopy protocol).
swappy will save the swappshot images to the config *save_dir*, see below.
swappy will save the annotated images to the config *save_dir*, see below.
If absent, then if it will try to default to a *Desktop* folder following this
pattern: *$XDG\_DESKTOP\_DIR*. If this variable is not set, it will revert to:
@ -28,11 +26,6 @@ to: *$HOME/Desktop*.
*-h*
Show help message and quit.
*-g* "<x>,<y> <width>x<height>"
Set the region to capture, in layout coordinates. This is slurp friendly.
Requires a Wayland compositor that supports screencopy protocol.
*-f* <file>
A PNG file to load for editing.