mirror of
https://github.com/jtheoof/swappy.git
synced 2024-08-17 02:30:47 +03:00
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:
parent
ceb907a5dc
commit
204a93eb0f
24
README.md
24
README.md
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1,6 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "swappy.h"
|
||||
|
||||
bool wayland_init(struct swappy_state *state);
|
||||
void wayland_finish(struct swappy_state *state);
|
13
meson.build
13
meson.build
@ -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],
|
||||
|
@ -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,
|
||||
)
|
@ -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>
|
@ -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',
|
||||
|
268
src/buffer.c
268
src/buffer.c
@ -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);
|
||||
|
@ -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:
|
||||
|
292
src/wayland.c
292
src/wayland.c
@ -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, ®istry_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;
|
||||
}
|
13
swappy.1.scd
13
swappy.1.scd
@ -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.
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user