1
0
mirror of https://codeberg.org/vyivel/dulcepan/ synced 2025-03-12 18:59:15 +02:00

Make it good™

This commit is contained in:
Kirill Primak 2024-06-19 18:40:22 +03:00
parent 8609265acf
commit 1436162663
16 changed files with 1576 additions and 862 deletions

20
.editorconfig Normal file
View File

@ -0,0 +1,20 @@
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
max_line_length = 100
[*.[ch]]
indent_style = tab
indent_size = 4
[*.xml]
indent_style = space
indent_size = 2
[meson.build]
indent_style = space
indent_size = 2

View File

@ -1,6 +1,7 @@
# dulcepan # dulcepan
A (WIP) screenshot tool. A screenshot tool for Wayland compositors. Requires wlr-screencopy-unstable-v1
support.
Discuss in [#eclairs on Libera.Chat]. Discuss in [#eclairs on Libera.Chat].
@ -13,6 +14,16 @@ meson setup build/
ninja -C build/ ninja -C build/
``` ```
## Configuration
dulcepan will try to load a configuration file from
`$XDG_CONFIG_DIRS/dulcepan.cfg`, which is a set of `key=value` pairs. See the
example configuration file `dulcepan.cfg` for more information.
## Usage
See `dulcepan -h`.
## License ## License
GPL-3.0-only GPL-3.0-only

13
dulcepan.cfg Normal file
View File

@ -0,0 +1,13 @@
# An example configuration.
# RRGGBB or RRGGBBAA
unselected-color = ffffff40
selected-color = 00000000
border-color = ffffff
# 0 to disable borders
border-size = 2
# If true, dulcepan will save immediately when interactive selection is stopped
# or when a whole output is selected with a mouse button.
quick-select = false

855
main.c
View File

@ -1,855 +0,0 @@
#include <assert.h>
#include <fcntl.h>
#include <linux/input-event-codes.h>
#include <pixman.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client-core.h>
#include <wayland-client-protocol.h>
#include <xkbcommon/xkbcommon.h>
#include "viewporter-protocol.h"
#include "wlr-layer-shell-unstable-v1-protocol.h"
#include "wlr-screencopy-unstable-v1-protocol.h"
#define SWAPCHAIN_LEN 4
struct dp_swapchain_buffer {
struct wl_buffer *buffer;
void *data;
pixman_image_t *image;
bool used;
};
struct dp_output {
uint32_t name;
struct wl_output *global;
struct wl_surface *surface;
struct zwlr_layer_surface_v1 *layer_surface;
struct wp_viewport *viewport;
struct wl_surface *ui_surface;
struct wl_subsurface *ui_subsurface;
struct wp_viewport *ui_viewport;
int width, height;
int px_width, px_height;
int frame_width, frame_height;
int32_t transform;
struct zwlr_screencopy_frame_v1 *frame;
bool frame_ready;
struct wl_buffer *frame_buffer;
int32_t frame_stride;
uint32_t frame_format;
void *frame_data;
bool configured;
struct dp_swapchain_buffer swapchain[SWAPCHAIN_LEN];
struct wl_list link;
};
enum dp_status {
DP_RUNNING,
DP_SAVE,
DP_QUIT,
};
static enum dp_status status = DP_RUNNING;
static struct wl_compositor *compositor = NULL;
static struct wl_subcompositor *subcompositor = NULL;
static struct wp_viewporter *viewporter = NULL;
static struct wl_shm *shm = NULL;
static struct zwlr_layer_shell_v1 *layer_shell = NULL;
static struct zwlr_screencopy_manager_v1 *screencopy_manager = NULL;
static struct wl_seat *seat = NULL;
static struct wl_keyboard *keyboard = NULL;
static struct wl_pointer *pointer = NULL;
static struct wl_list outputs;
static uint32_t seat_caps = false;
static struct xkb_context *xkb_context = NULL;
static struct xkb_keymap *xkb_keymap = NULL;
static struct xkb_state *xkb_state = NULL;
static struct dp_output *selected_output = NULL;
static int selected_x = 0;
static int selected_y = 0;
static int selected_width = 0;
static int selected_height = 0;
static bool selecting_rect = false;
static int select_start_x = 0;
static int select_start_y = 0;
static struct dp_output *pointer_output = NULL;
static int pointer_x = 0;
static int pointer_y = 0;
static bool quick = false;
static pixman_image_t *unselected_fill_image = NULL;
static pixman_image_t *selected_fill_image = NULL;
static pixman_image_t *border_fill_image = NULL;
static void die(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
fprintf(stderr, "Fatal: ");
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
va_end(args);
exit(1);
}
static void *xzalloc(size_t size) {
void *ptr = calloc(1, size);
if (ptr == NULL) {
die("allocation error");
}
return ptr;
}
static void sc_buffer_handle_release(void *data, struct wl_buffer *wl_buffer) {
struct dp_swapchain_buffer *sc_buffer = data;
sc_buffer->used = false;
}
static const struct wl_buffer_listener sc_buffer_listener = {
.release = sc_buffer_handle_release,
};
static void fnv_1a_cont(uint64_t *h, const void *data, size_t len) {
const uint8_t *bytes = data;
for (size_t i = 0; i < len; i++) {
*h = (*h * 0x00000100000001B3) ^ bytes[i];
}
}
static void generate_name(char buffer[static 16]) {
uint64_t h = 0xcbf29ce484222325;
int pid = getpid();
fnv_1a_cont(&h, &pid, sizeof(pid));
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
fnv_1a_cont(&h, &ts.tv_sec, sizeof(&ts.tv_sec));
fnv_1a_cont(&h, &ts.tv_nsec, sizeof(&ts.tv_nsec));
for (size_t i = 16; i-- > 0;) {
buffer[i] = "0123456789abcdef"[h & 0xF];
h >>= 4;
}
}
static struct wl_buffer *create_buffer(
int32_t width, int32_t height, int32_t stride, uint32_t format, void **data) {
int32_t size = stride * height;
static const char template[] = "randfall-";
char name[sizeof(template) + 16] = {0};
memcpy(name, template, sizeof(template) - 1);
generate_name(&name[sizeof(template) - 1]);
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd < 0) {
die("shm_open() failed");
}
shm_unlink(name);
if (ftruncate(fd, (off_t)size) < 0) {
die("ftruncate() failed");
}
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, (int32_t)size);
*data = mmap(NULL, (size_t)size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
die("mmap() failed");
}
struct wl_buffer *buffer =
wl_shm_pool_create_buffer(pool, 0, width, height, (int32_t)stride, format);
wl_shm_pool_destroy(pool);
close(fd);
return buffer;
}
static void layer_surface_handle_configure(void *data, struct zwlr_layer_surface_v1 *layer_surface,
uint32_t serial, uint32_t width, uint32_t height) {
struct dp_output *output = data;
if (output->configured && (output->width != (int)width || output->height != (int)height)) {
die("got unexpected layer surface configure size");
}
zwlr_layer_surface_v1_ack_configure(layer_surface, serial);
if (output->configured) {
wl_surface_commit(output->surface);
return;
}
output->width = (int)width;
output->height = (int)height;
output->configured = true;
wp_viewport_set_destination(output->viewport, output->width, output->height);
wp_viewport_set_destination(output->ui_viewport, output->width, output->height);
wl_surface_attach(output->surface, output->frame_buffer, 0, 0);
wl_surface_commit(output->surface);
}
static void redraw(struct dp_output *output) {
struct dp_swapchain_buffer *sc_buffer = NULL;
for (size_t i = 0; i < SWAPCHAIN_LEN; i++) {
struct dp_swapchain_buffer *iter = &output->swapchain[i];
if (!iter->used) {
sc_buffer = iter;
break;
}
}
if (sc_buffer == NULL) {
fprintf(stderr, "no free buffers in a swapchain\n");
return;
}
sc_buffer->used = true;
pixman_image_composite32(PIXMAN_OP_SRC, unselected_fill_image, NULL, sc_buffer->image, 0, 0, 0,
0, 0, 0, output->px_width, output->px_height);
if (output == selected_output) {
int border_size = 2;
pixman_image_composite32(PIXMAN_OP_SRC, border_fill_image, NULL, sc_buffer->image, 0, 0, 0,
0, selected_x - border_size, selected_y - border_size,
selected_width + border_size * 2, border_size);
pixman_image_composite32(PIXMAN_OP_SRC, border_fill_image, NULL, sc_buffer->image, 0, 0, 0,
0, selected_x - border_size, selected_y + selected_height,
selected_width + border_size * 2, border_size);
pixman_image_composite32(PIXMAN_OP_SRC, border_fill_image, NULL, sc_buffer->image, 0, 0, 0,
0, selected_x - border_size, selected_y, border_size, selected_height);
pixman_image_composite32(PIXMAN_OP_SRC, border_fill_image, NULL, sc_buffer->image, 0, 0, 0,
0, selected_x + selected_width, selected_y, border_size, selected_height);
pixman_image_composite32(PIXMAN_OP_SRC, selected_fill_image, NULL, sc_buffer->image, 0, 0,
0, 0, selected_x, selected_y, selected_width, selected_height);
}
wl_surface_attach(output->ui_surface, sc_buffer->buffer, 0, 0);
wl_surface_damage(output->ui_surface, 0, 0, output->px_width, output->px_height);
wl_surface_commit(output->ui_surface);
}
static void select_whole(void) {
selected_x = 0;
selected_y = 0;
selected_width = selected_output->px_width;
selected_height = selected_output->px_height;
}
static void set_selected_output(struct dp_output *output) {
if (output == selected_output) {
return;
}
struct dp_output *prev_selected_output = selected_output;
selected_output = output;
redraw(prev_selected_output);
}
static void layer_surface_handle_closed(void *data, struct zwlr_layer_surface_v1 *layer_surface) {
die("a layer surface was closed");
}
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layer_surface_handle_configure,
.closed = layer_surface_handle_closed,
};
static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y,
int32_t phys_width, int32_t phys_height, int32_t subpixel, const char *make,
const char *model, int32_t transform) {
struct dp_output *output = data;
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 dp_output *output = data;
output->frame_width = width;
output->frame_height = height;
}
static const struct wl_output_listener output_listener = {
.geometry = output_handle_geometry,
.mode = output_handle_mode,
};
static void seat_handle_capabilities(void *data, struct wl_seat *seat_, uint32_t caps) {
seat_caps = caps;
}
static void seat_handle_name(void *data, struct wl_seat *seat_, const char *name) {
// Ignored
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_handle_capabilities,
.name = seat_handle_name,
};
static void keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard_,
enum wl_keyboard_keymap_format format, int32_t fd, uint32_t size) {
void *keymap_buffer;
size_t keymap_len;
xkb_keymap_unref(xkb_keymap);
xkb_state_unref(xkb_state);
switch (format) {
case WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP:
xkb_keymap = xkb_keymap_new_from_names(xkb_context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS);
break;
case WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1:
keymap_len = size - 1;
keymap_buffer = mmap(NULL, keymap_len, PROT_READ, MAP_PRIVATE, fd, 0);
if (keymap_buffer == MAP_FAILED) {
die("mmap() for a keymap failed");
}
xkb_keymap = xkb_keymap_new_from_buffer(xkb_context, keymap_buffer, keymap_len,
XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap(keymap_buffer, keymap_len);
close(fd);
break;
}
xkb_state = xkb_state_new(xkb_keymap);
}
static void keyboard_handle_enter(void *data, struct wl_keyboard *keyboard_, uint32_t serial,
struct wl_surface *surface, struct wl_array *mods) {
// Ignored
}
static void keyboard_handle_leave(
void *data, struct wl_keyboard *keyboard_, uint32_t serial, struct wl_surface *surface) {
// Ignored
}
static void keyboard_handle_key(void *data, struct wl_keyboard *keyboard_, uint32_t serial,
uint32_t time_msec, uint32_t key, enum wl_keyboard_key_state state) {
if (xkb_state == NULL) {
return; // Shouldn't happen
}
if (state != WL_KEYBOARD_KEY_STATE_PRESSED) {
return;
}
xkb_keysym_t keysym = xkb_state_key_get_one_sym(xkb_state, key + 8);
switch (keysym) {
case XKB_KEY_q:
status = DP_QUIT;
break;
case XKB_KEY_s:
status = DP_SAVE;
break;
case XKB_KEY_w:
select_whole();
redraw(selected_output);
break;
case XKB_KEY_Tab:
set_selected_output(wl_container_of(
(selected_output->link.next != &outputs ? &selected_output->link : &outputs)->next,
selected_output, link));
select_whole();
redraw(selected_output);
break;
}
}
static void keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard_, uint32_t serial,
uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) {
if (xkb_state == NULL) {
return; // Shouldn't happen
}
xkb_state_update_mask(xkb_state, depressed, latched, locked, 0, 0, group);
}
static const struct wl_keyboard_listener keyboard_listener = {
.keymap = keyboard_handle_keymap,
.enter = keyboard_handle_enter,
.leave = keyboard_handle_leave,
.key = keyboard_handle_key,
.modifiers = keyboard_handle_modifiers,
};
static void convert_pos(wl_fixed_t x, wl_fixed_t y, int *out_x, int *out_y) {
assert(pointer_output != NULL);
*out_x = (int)wl_fixed_to_double(x) * pointer_output->px_width / pointer_output->width;
*out_y = (int)wl_fixed_to_double(y) * pointer_output->px_height / pointer_output->height;
}
static void pointer_handle_enter(void *data, struct wl_pointer *pointer_, uint32_t serial,
struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) {
pointer_output = wl_surface_get_user_data(surface);
assert(pointer_output != NULL);
convert_pos(sx, sy, &pointer_x, &pointer_y);
}
static void pointer_handle_leave(
void *data, struct wl_pointer *pointer_, uint32_t serial, struct wl_surface *surface) {
pointer_output = NULL;
selecting_rect = false;
}
static void pointer_handle_motion(
void *data, struct wl_pointer *pointer_, uint32_t serial, wl_fixed_t sx, wl_fixed_t sy) {
if (pointer_output == NULL) {
return; // Shouldn't happen
}
convert_pos(sx, sy, &pointer_x, &pointer_y);
if (!selecting_rect) {
return;
}
if (pointer_x < select_start_x) {
selected_x = pointer_x;
selected_width = select_start_x - pointer_x;
} else {
selected_x = select_start_x;
selected_width = pointer_x - select_start_x;
}
if (pointer_y < select_start_y) {
selected_y = pointer_y;
selected_height = select_start_y - pointer_y;
} else {
selected_y = select_start_y;
selected_height = pointer_y - select_start_y;
}
redraw(selected_output);
}
static void pointer_handle_button(void *data, struct wl_pointer *pointer_, uint32_t serial,
uint32_t time_msec, uint32_t button, enum wl_pointer_button_state state) {
if (pointer_output == NULL) {
return; // Shouldn't happen
}
if (state != WL_POINTER_BUTTON_STATE_PRESSED) {
selecting_rect = false;
if (quick) {
status = DP_SAVE;
}
return;
}
switch (button) {
case BTN_LEFT:
selecting_rect = true;
selected_x = pointer_x;
selected_y = pointer_y;
selected_width = 0;
selected_height = 0;
select_start_x = selected_x;
select_start_y = selected_y;
set_selected_output(pointer_output);
redraw(selected_output);
break;
case BTN_RIGHT:
set_selected_output(pointer_output);
select_whole();
if (quick) {
status = DP_SAVE;
return;
}
redraw(selected_output);
break;
}
}
static void pointer_handle_axis(void *data, struct wl_pointer *pointer_, uint32_t time_msec,
enum wl_pointer_axis axis, wl_fixed_t value) {
}
static const struct wl_pointer_listener pointer_listener = {
.enter = pointer_handle_enter,
.leave = pointer_handle_leave,
.motion = pointer_handle_motion,
.button = pointer_handle_button,
.axis = pointer_handle_axis,
};
static void frame_handle_buffer(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format,
uint32_t width, uint32_t height, uint32_t stride) {
struct dp_output *output = data;
assert(output->frame_buffer == NULL);
output->frame_format = format;
output->frame_stride = (int)stride;
output->frame_buffer = create_buffer(output->frame_width, output->frame_height,
output->frame_stride, output->frame_format, &output->frame_data);
zwlr_screencopy_frame_v1_copy(output->frame, output->frame_buffer);
}
static void frame_handle_flags(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) {
if (flags != 0) {
// TODO
die("who the fuck uses Y_INVERT");
}
}
static void 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 dp_output *output = data;
output->frame_ready = true;
}
static void frame_handle_failed(void *data, struct zwlr_screencopy_frame_v1 *frame) {
die("frame copying has failed");
}
static const struct zwlr_screencopy_frame_v1_listener frame_listener = {
.buffer = frame_handle_buffer,
.flags = frame_handle_flags,
.ready = frame_handle_ready,
.failed = frame_handle_failed,
};
static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
if (strcmp(interface, wl_compositor_interface.name) == 0) {
if (version >= 2) {
compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 2);
}
} else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1);
} else if (strcmp(interface, wp_viewporter_interface.name) == 0) {
viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1);
} else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) {
screencopy_manager =
wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, 1);
} else if (strcmp(interface, wl_output_interface.name) == 0) {
struct dp_output *output = xzalloc(sizeof(*output));
output->name = name;
output->global = wl_registry_bind(registry, name, &wl_output_interface, 1);
wl_output_add_listener(output->global, &output_listener, output);
wl_list_insert(&outputs, &output->link);
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
// No multiseat
if (seat == NULL) {
seat = wl_registry_bind(registry, name, &wl_seat_interface, 1);
wl_seat_add_listener(seat, &seat_listener, NULL);
}
}
}
static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
struct dp_output *output;
wl_list_for_each (output, &outputs, link) {
if (output->name == name) {
die("an output was removed");
}
}
}
static const struct wl_registry_listener registry_listener = {
.global = registry_handle_global,
.global_remove = registry_handle_global_remove,
};
static pixman_format_code_t shm_to_pixman(enum wl_shm_format shm_format) {
struct {
enum wl_shm_format shm;
pixman_format_code_t pixman;
} formats[] = {
#if DP_BIG_ENDIAN
{WL_SHM_FORMAT_ARGB8888, PIXMAN_b8g8r8a8},
{WL_SHM_FORMAT_XRGB8888, PIXMAN_b8g8r8x8},
{WL_SHM_FORMAT_ABGR8888, PIXMAN_r8g8b8a8},
{WL_SHM_FORMAT_XBGR8888, PIXMAN_r8g8b8x8},
{WL_SHM_FORMAT_BGRA8888, PIXMAN_a8r8g8b8},
{WL_SHM_FORMAT_BGRX8888, PIXMAN_x8r8g8b8},
{WL_SHM_FORMAT_RGBA8888, PIXMAN_a8b8g8r8},
{WL_SHM_FORMAT_RGBX8888, PIXMAN_x8b8g8r8},
#else
{WL_SHM_FORMAT_RGB332, PIXMAN_r3g3b2},
{WL_SHM_FORMAT_BGR233, PIXMAN_b2g3r3},
{WL_SHM_FORMAT_ARGB4444, PIXMAN_a4r4g4b4},
{WL_SHM_FORMAT_XRGB4444, PIXMAN_x4r4g4b4},
{WL_SHM_FORMAT_ABGR4444, PIXMAN_a4b4g4r4},
{WL_SHM_FORMAT_XBGR4444, PIXMAN_x4b4g4r4},
{WL_SHM_FORMAT_ARGB1555, PIXMAN_a1r5g5b5},
{WL_SHM_FORMAT_XRGB1555, PIXMAN_x1r5g5b5},
{WL_SHM_FORMAT_ABGR1555, PIXMAN_a1b5g5r5},
{WL_SHM_FORMAT_XBGR1555, PIXMAN_x1b5g5r5},
{WL_SHM_FORMAT_RGB565, PIXMAN_r5g6b5},
{WL_SHM_FORMAT_BGR565, PIXMAN_b5g6r5},
{WL_SHM_FORMAT_RGB888, PIXMAN_r8g8b8},
{WL_SHM_FORMAT_BGR888, PIXMAN_b8g8r8},
{WL_SHM_FORMAT_ARGB8888, PIXMAN_a8r8g8b8},
{WL_SHM_FORMAT_XRGB8888, PIXMAN_x8r8g8b8},
{WL_SHM_FORMAT_ABGR8888, PIXMAN_a8b8g8r8},
{WL_SHM_FORMAT_XBGR8888, PIXMAN_x8b8g8r8},
{WL_SHM_FORMAT_BGRA8888, PIXMAN_b8g8r8a8},
{WL_SHM_FORMAT_BGRX8888, PIXMAN_b8g8r8x8},
{WL_SHM_FORMAT_RGBA8888, PIXMAN_r8g8b8a8},
{WL_SHM_FORMAT_RGBX8888, PIXMAN_r8g8b8x8},
{WL_SHM_FORMAT_ARGB2101010, PIXMAN_a2r10g10b10},
{WL_SHM_FORMAT_ABGR2101010, PIXMAN_a2b10g10r10},
{WL_SHM_FORMAT_XRGB2101010, PIXMAN_x2r10g10b10},
{WL_SHM_FORMAT_XBGR2101010, PIXMAN_x2b10g10r10},
#endif
};
for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); i++) {
if (formats[i].shm == shm_format) {
return formats[i].pixman;
}
}
return 0;
}
int main(void) {
struct wl_display *display = wl_display_connect(NULL);
if (display == NULL) {
die("failed to connect to a Wayland compositor");
}
xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
wl_list_init(&outputs);
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, NULL);
// Collect globals
wl_display_roundtrip(display);
if (compositor == NULL) {
die("the compositor has no wl_compositor");
} else if (subcompositor == NULL) {
die("the compositor has no wl_subcompositor");
} else if (viewporter == NULL) {
die("the compositor has no wp_viewporter");
} else if (shm == NULL) {
die("the compositor has no wl_shm");
} else if (layer_shell == NULL) {
die("the compositor has no zwlr_layer_shell_v1");
} else if (screencopy_manager == NULL) {
die("the compositor has no zwlr_screencopy_manager_v1");
} else if (seat == NULL) {
die("the compositor has no wl_seat");
}
if (wl_list_empty(&outputs)) {
die("no outputs found");
}
struct dp_output *output;
// Get output/seat info
wl_display_roundtrip(display);
if ((seat_caps & WL_SEAT_CAPABILITY_KEYBOARD) == 0) {
die("the seat has no keyboard capabilities");
}
if ((seat_caps & WL_SEAT_CAPABILITY_POINTER) == 0) {
die("the seat has no pointer capabilities");
}
keyboard = wl_seat_get_keyboard(seat);
wl_keyboard_add_listener(keyboard, &keyboard_listener, NULL);
pointer = wl_seat_get_pointer(seat);
wl_pointer_add_listener(pointer, &pointer_listener, NULL);
wl_list_for_each (output, &outputs, link) {
if ((output->transform & WL_OUTPUT_TRANSFORM_90) != 0) {
output->px_width = output->frame_height;
output->px_height = output->frame_width;
} else {
output->px_width = output->frame_width;
output->px_height = output->frame_height;
}
output->frame = zwlr_screencopy_manager_v1_capture_output(
screencopy_manager, false, output->global);
zwlr_screencopy_frame_v1_add_listener(output->frame, &frame_listener, output);
}
// Get frames
wl_display_roundtrip(display);
wl_list_for_each (output, &outputs, link) {
if (output->frame_buffer == NULL) {
die("failed to create a buffer for an output");
}
while (!output->frame_ready && wl_display_dispatch(display) != -1) {
// Wait
}
}
// Init surfaces
wl_list_for_each (output, &outputs, link) {
output->surface = wl_compositor_create_surface(compositor);
output->layer_surface = zwlr_layer_shell_v1_get_layer_surface(layer_shell, output->surface,
output->global, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "dulcepan");
output->viewport = wp_viewporter_get_viewport(viewporter, output->surface);
wl_surface_set_buffer_transform(output->surface, output->transform);
struct wl_region *empty_region = wl_compositor_create_region(compositor);
wl_surface_set_input_region(output->surface, empty_region);
wl_region_destroy(empty_region);
zwlr_layer_surface_v1_add_listener(output->layer_surface, &layer_surface_listener, output);
zwlr_layer_surface_v1_set_anchor(output->layer_surface,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
zwlr_layer_surface_v1_set_keyboard_interactivity(
output->layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE);
zwlr_layer_surface_v1_set_exclusive_zone(output->layer_surface, -1);
output->ui_surface = wl_compositor_create_surface(compositor);
output->ui_subsurface =
wl_subcompositor_get_subsurface(subcompositor, output->ui_surface, output->surface);
output->ui_viewport = wp_viewporter_get_viewport(viewporter, output->ui_surface);
wl_subsurface_set_desync(output->ui_subsurface);
wl_surface_set_user_data(output->surface, output);
wl_surface_set_user_data(output->ui_surface, output);
for (size_t i = 0; i < SWAPCHAIN_LEN; i++) {
struct dp_swapchain_buffer *sc_buffer = &output->swapchain[i];
int stride = output->frame_width * 4;
sc_buffer->buffer = create_buffer(output->px_width, output->px_height, stride,
WL_SHM_FORMAT_ARGB8888, &sc_buffer->data);
sc_buffer->image = pixman_image_create_bits(
PIXMAN_a8r8g8b8, output->px_width, output->px_height, sc_buffer->data, stride);
wl_buffer_add_listener(sc_buffer->buffer, &sc_buffer_listener, sc_buffer);
}
wl_surface_commit(output->surface);
}
wl_list_for_each (output, &outputs, link) {
while (!output->configured && wl_display_dispatch(display) != -1) {
// Wait
}
}
unselected_fill_image = pixman_image_create_solid_fill(&(pixman_color_t){
.red = UINT16_MAX / 2,
.green = UINT16_MAX / 2,
.blue = UINT16_MAX / 2,
.alpha = UINT16_MAX / 2,
});
selected_fill_image = pixman_image_create_solid_fill(&(pixman_color_t){
.red = 0,
.green = 0,
.blue = 0,
.alpha = 0,
});
border_fill_image = pixman_image_create_solid_fill(&(pixman_color_t){
.red = UINT16_MAX,
.green = UINT16_MAX,
.blue = UINT16_MAX,
.alpha = UINT16_MAX,
});
// Select the "first" output, whatever that is
selected_output = wl_container_of(outputs.next, selected_output, link);
select_whole();
wl_list_for_each (output, &outputs, link) {
redraw(output);
}
while (wl_display_dispatch(display) != -1 && status == DP_RUNNING) {
// Wait
}
if (status == DP_SAVE) {
if (selected_width == 0 || selected_height == 0) {
die("selected region is empty");
}
pixman_format_code_t pixman_format = shm_to_pixman(selected_output->frame_format);
if (pixman_format == 0) {
die("failed to get a matching Pixman format for wl_shm format");
}
pixman_image_t *frame = pixman_image_create_bits(pixman_format,
selected_output->frame_width, selected_output->frame_height,
selected_output->frame_data, selected_output->frame_stride);
static const int sines[] = {0, -1, 0, 1, 0, 1, 0, -1};
static const int cosines[] = {1, 0, -1, 0, 1, 0, -1, 0};
static const int flips[] = {1, 1, 1, 1, -1, -1, -1, -1};
struct pixman_transform frame_transform;
pixman_transform_init_identity(&frame_transform);
pixman_transform_translate(&frame_transform, NULL,
pixman_int_to_fixed(-selected_output->px_width / 2),
pixman_int_to_fixed(-selected_output->px_height / 2));
pixman_transform_rotate(&frame_transform, NULL,
pixman_int_to_fixed(cosines[selected_output->transform]),
pixman_int_to_fixed(sines[selected_output->transform]));
pixman_transform_scale(&frame_transform, NULL,
pixman_int_to_fixed(flips[selected_output->transform]), pixman_fixed_1);
pixman_transform_translate(&frame_transform, NULL,
pixman_int_to_fixed(selected_output->frame_width / 2),
pixman_int_to_fixed(selected_output->frame_height / 2));
pixman_image_set_transform(frame, &frame_transform);
pixman_image_t *result =
pixman_image_create_bits(PIXMAN_a8r8g8b8, selected_width, selected_height, NULL, 0);
pixman_image_composite32(PIXMAN_OP_SRC, frame, NULL, result, selected_x, selected_y, 0, 0,
0, 0, selected_width, selected_height);
pixman_image_unref(frame);
// TODO: a better format
uint32_t *data = pixman_image_get_data(result);
fprintf(stdout, "P6\n%d %d\n255\n", selected_width, selected_height);
for (int y = 0; y < selected_height; y++) {
for (int x = 0; x < selected_width; x++) {
uint32_t pixel = *(data++);
char bytes[3] = {
(char)((pixel >> 16) & 0xff),
(char)((pixel >> 8) & 0xff),
(char)((pixel >> 0) & 0xff),
};
fwrite(bytes, 1, sizeof(bytes), stdout);
}
}
fflush(stdout);
pixman_image_unref(result);
}
wl_registry_destroy(registry);
wl_display_disconnect(display);
return 0;
}

View File

@ -44,13 +44,30 @@ wayland_client = dependency('wayland-client')
wayland_protos = dependency('wayland-protocols') wayland_protos = dependency('wayland-protocols')
pixman = dependency('pixman-1') pixman = dependency('pixman-1')
xkbcommon = dependency('xkbcommon') spng = dependency('spng')
xkbcommon = dependency(
'xkbcommon',
fallback: 'libxkbcommon',
default_options: [
'enable-tools=false',
'enable-x11=false',
'enable-docs=false',
'enable-xkbregistry=false',
],
)
sfdo_basedir = dependency(
'libsfdo-basedir',
version: '>=0.1.0',
fallback: 'libsfdo',
default_options: [
'default_library=static',
'examples=false',
],
)
subdir('protocols') subdir('protocols')
subdir('src')
src = files(
'main.c',
)
executable( executable(
meson.project_name(), meson.project_name(),
@ -59,7 +76,9 @@ executable(
client_protos, client_protos,
wayland_client, wayland_client,
pixman, pixman,
spng,
xkbcommon, xkbcommon,
sfdo_basedir,
], ],
install: true, install: true,
) )

View File

@ -16,7 +16,6 @@ wayland_scanner_client = generator(
client_protocols = [ client_protocols = [
wl_protocol_dir / 'stable/viewporter/viewporter.xml', wl_protocol_dir / 'stable/viewporter/viewporter.xml',
wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', # layer-shell dependency wl_protocol_dir / 'stable/xdg-shell/xdg-shell.xml', # layer-shell dependency
wl_protocol_dir / 'staging/fractional-scale/fractional-scale-v1.xml',
'wlr-layer-shell-unstable-v1.xml', 'wlr-layer-shell-unstable-v1.xml',
'wlr-screencopy-unstable-v1.xml', 'wlr-screencopy-unstable-v1.xml',
] ]

63
src/buffer.c Normal file
View File

@ -0,0 +1,63 @@
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <wayland-client-protocol.h>
#include "dulcepan.h"
static void fnv_1a_cont(uint64_t *h, const void *data, size_t len) {
const uint8_t *bytes = data;
for (size_t i = 0; i < len; i++) {
*h = (*h * 0x00000100000001B3) ^ bytes[i];
}
}
static void generate_name(char buffer[static 16]) {
uint64_t h = 0xcbf29ce484222325;
int pid = getpid();
fnv_1a_cont(&h, &pid, sizeof(pid));
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
fnv_1a_cont(&h, &ts.tv_sec, sizeof(&ts.tv_sec));
fnv_1a_cont(&h, &ts.tv_nsec, sizeof(&ts.tv_nsec));
for (size_t i = 16; i-- > 0;) {
buffer[i] = "0123456789abcdef"[h & 0xF];
h >>= 4;
}
}
struct wl_buffer *dp_buffer_create(struct dp_state *state, int32_t width, int32_t height,
int32_t stride, uint32_t format, void **data, size_t *size) {
*size = (size_t)(stride * height);
static const char template[] = "randfall-";
char name[sizeof(template) + 16] = {0};
memcpy(name, template, sizeof(template) - 1);
generate_name(&name[sizeof(template) - 1]);
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
if (fd < 0) {
dp_log_fatal("shm_open() failed");
}
shm_unlink(name);
if (ftruncate(fd, (off_t)*size) < 0) {
dp_log_fatal("ftruncate() failed");
}
struct wl_shm_pool *pool = wl_shm_create_pool(state->shm, fd, (int32_t)*size);
*data = mmap(NULL, (size_t)*size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
dp_log_fatal("mmap() failed");
}
struct wl_buffer *buffer =
wl_shm_pool_create_buffer(pool, 0, width, height, (int32_t)stride, format);
wl_shm_pool_destroy(pool);
close(fd);
return buffer;
}

180
src/config.c Normal file
View File

@ -0,0 +1,180 @@
#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <sfdo-basedir.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dulcepan.h"
#define CONFIG_SUBPATH "dulcepan.cfg"
#define COLOR_MUL (UINT16_MAX / UINT8_MAX)
static void bytes_to_color(uint32_t bytes[static 4], pixman_color_t *out) {
out->red = (uint16_t)(bytes[0] * bytes[3] * COLOR_MUL / UINT8_MAX);
out->green = (uint16_t)(bytes[1] * bytes[3] * COLOR_MUL / UINT8_MAX);
out->blue = (uint16_t)(bytes[2] * bytes[3] * COLOR_MUL / UINT8_MAX);
out->alpha = (uint16_t)(bytes[3] * COLOR_MUL);
}
static void load_color(const char *value, int line_idx, pixman_color_t *out) {
size_t len = strlen(value);
uint32_t bytes[4] = {0, 0, 0, 0};
if (len == 6 && len != 8) {
bytes[3] = UINT8_MAX;
} else if (len != 8) {
goto bad;
}
for (size_t i = 0; i < len; i++) {
uint32_t digit;
if (value[i] >= '0' && value[i] <= '9') {
digit = (uint32_t)value[i] - '0';
} else if (value[i] >= 'A' && value[i] <= 'F') {
digit = (uint32_t)value[i] - 'A' + 10;
} else if (value[i] >= 'a' && value[i] <= 'f') {
digit = (uint32_t)value[i] - 'a' + 10;
} else {
goto bad;
}
bytes[i / 2] = bytes[i / 2] * 16 + digit;
}
bytes_to_color(bytes, out);
return;
bad:
dp_log_fatal("Config: invalid color %s on line %d", value, line_idx);
}
static void load_int(const char *value, int line_idx, int *out) {
// Only nonnegative numbers
*out = 0;
for (size_t i = 0; value[i] != '\0'; i++) {
if ((value[i] < '0' && value[i] > 9) || *out > INT_MAX / 10) {
dp_log_fatal("Config: invalid number %s on line %d", value, line_idx);
}
*out = *out * 10 - '0' + value[i];
}
}
static void load_bool(const char *value, int line_idx, bool *out) {
if (strcmp(value, "true") == 0) {
*out = true;
} else if (strcmp(value, "false") == 0) {
*out = false;
} else {
dp_log_fatal("Config: invalid boolean value %s on line %d", value, line_idx);
}
}
void dp_config_load(struct dp_config *config, const char *user_path) {
FILE *fp = NULL;
*config = (struct dp_config){
.border_size = 2,
.quick_select = false,
};
bytes_to_color((uint32_t[]){0xff, 0xff, 0xff, 0x40}, &config->unselected_color);
bytes_to_color((uint32_t[]){0x00, 0x00, 0x00, 0xff}, &config->selected_color);
bytes_to_color((uint32_t[]){0xff, 0xff, 0xff, 0xff}, &config->border_color);
if (user_path != NULL) {
fp = fopen(user_path, "r");
if (fp == NULL) {
dp_log_fatal("Failed to open %s: %s", user_path, strerror(errno));
}
} else {
struct sfdo_basedir_ctx *basedir_ctx = sfdo_basedir_ctx_create();
size_t n_dirs;
const struct sfdo_string *dirs = sfdo_basedir_get_config_dirs(basedir_ctx, &n_dirs);
for (size_t i = 0; i < n_dirs && fp == NULL; i++) {
const struct sfdo_string *dir = &dirs[i];
size_t size = dir->len + sizeof(CONFIG_SUBPATH);
char *path = dp_zalloc(size);
memcpy(path, dir->data, dir->len);
memcpy(path + dir->len, CONFIG_SUBPATH, sizeof(CONFIG_SUBPATH));
fp = fopen(path, "r");
if (fp == NULL) {
if (errno != ENOENT) {
dp_log_fatal("Failed to open %s: %s", path, strerror(errno));
}
}
free(path);
}
sfdo_basedir_ctx_destroy(basedir_ctx);
}
if (fp == NULL) {
return;
}
int line_idx = 0;
char *line = NULL;
size_t line_cap = 0;
ssize_t line_len;
while ((line_len = getline(&line, &line_cap, fp)) != -1) {
++line_idx;
if (line[line_len - 1] == '\n') {
line[--line_len] = '\0';
}
// One of:
// [whitespace] [# comment]
// [whitespace] <key> [whitespace] = [whitespace] <value> [whitespace] [# comment]
ssize_t i = 0;
while (isspace(line[line_len]) && i++ < line_len) {
// Skip whitespace
}
if (line[i] == '\0' || line[i] == '#') {
continue;
}
char *key = &line[i];
while (!isspace(line[i]) && line[i] != '=' && line[i] != '#' && i++ < line_len) {
// Read key
}
char *key_end = &line[i];
while (isspace(line[i]) && i++ < line_len) {
// Skip whitespace
}
if (line[i++] != '=') {
dp_log_fatal("Config: invalid syntax on line %d", line_idx);
}
*key_end = '\0';
while (isspace(line[i]) && i++ < line_len) {
// Skip whitespace
}
char *value = &line[i];
while (!isspace(line[i]) && line[i] != '#' && i++ < line_len) {
// Read value
}
line[i] = '\0';
if (strcmp(key, "unselected-color") == 0) {
load_color(value, line_idx, &config->unselected_color);
} else if (strcmp(key, "selected-color") == 0) {
load_color(value, line_idx, &config->selected_color);
} else if (strcmp(key, "border-color") == 0) {
load_color(value, line_idx, &config->border_color);
} else if (strcmp(key, "border-size") == 0) {
load_int(value, line_idx, &config->border_size);
} else if (strcmp(key, "quick-select") == 0) {
load_bool(value, line_idx, &config->quick_select);
} else {
dp_log_fatal("Config: unknown key %s on line %d", key, line_idx);
}
}
free(line);
fclose(fp);
}

173
src/dulcepan.h Normal file
View File

@ -0,0 +1,173 @@
#ifndef DULCEPAN_H
#define DULCEPAN_H
#include <pixman.h>
#include <stdbool.h>
#include <stddef.h>
#include <wayland-util.h>
// Per-output
#define DP_SWAPCHAIN_LEN 2
enum dp_status {
DP_STATUS_RUNNING,
DP_STATUS_SAVED,
DP_STATUS_QUIT,
};
struct dp_buffer {
struct wl_buffer *wl_buffer;
pixman_image_t *image;
void *data;
size_t size;
bool used;
};
struct dp_output {
struct dp_state *state;
uint32_t name;
struct wl_output *wl_output;
struct wl_surface *main_surface;
struct zwlr_layer_surface_v1 *main_layer_surface;
struct wp_viewport *main_viewport;
struct wl_surface *select_surface;
struct wl_subsurface *select_subsurface;
struct wp_viewport *select_viewport;
int width, height;
int32_t transform;
bool has_geom;
int transformed_width, transformed_height;
int effective_width, effective_height;
bool initialized;
struct wl_callback *redraw_callback;
bool needs_redraw;
struct zwlr_screencopy_frame_v1 *frame;
// XXX: this is completely untested
bool y_invert;
struct wl_buffer *frame_buffer;
int32_t frame_stride;
uint32_t frame_format;
void *frame_data;
size_t frame_size;
struct dp_buffer swapchain[DP_SWAPCHAIN_LEN];
struct wl_list link;
};
struct dp_seat {
struct dp_state *state;
uint32_t name;
struct wl_seat *wl_seat;
struct wl_keyboard *keyboard;
struct wl_pointer *pointer;
struct xkb_keymap *xkb_keymap;
struct xkb_state *xkb_state;
// The output the pointer is on
struct dp_output *ptr_output;
// In buffer space
int ptr_x, ptr_y;
struct wl_list link;
};
struct dp_selection {
struct dp_output *output; // May be NULL
int x, y, width, height;
struct dp_output *interactive_output; // NULL if not interactively selecting
int interactive_x, interactive_y;
bool interactive_moved;
};
enum dp_file_format {
DP_FILE_UNKNOWN = 0,
DP_FILE_PNG,
DP_FILE_PPM,
};
struct dp_config {
pixman_color_t unselected_color;
pixman_color_t selected_color;
pixman_color_t border_color;
int border_size; // 0 if disabled
bool quick_select;
};
struct dp_state {
enum dp_status status;
struct wl_display *display;
struct wl_registry *registry;
struct wl_compositor *compositor;
struct wl_subcompositor *subcompositor;
struct wp_viewporter *viewporter;
struct wl_shm *shm;
struct zwlr_layer_shell_v1 *layer_shell;
struct zwlr_screencopy_manager_v1 *screencopy_manager;
bool initialized;
struct xkb_context *xkb_context;
struct wl_list outputs;
struct wl_list seats;
struct dp_selection selection;
struct dp_config config;
const char *output_path; // May be NULL
enum dp_file_format output_format;
pixman_image_t *unselected_fill_image;
pixman_image_t *selected_fill_image;
pixman_image_t *border_fill_image;
};
void dp_config_load(struct dp_config *config, const char *user_path);
// When done, data must be unmapped
struct wl_buffer *dp_buffer_create(struct dp_state *state, int32_t width, int32_t height,
int32_t stride, uint32_t format, void **data, size_t *size);
void dp_output_create(struct dp_state *state, uint32_t name, struct wl_output *wl_output);
void dp_output_destroy(struct dp_output *output);
void dp_output_redraw(struct dp_output *output);
void dp_output_hide_surface(struct dp_output *output);
void dp_seat_create(struct dp_state *state, uint32_t name, struct wl_seat *wl_seat);
void dp_seat_destroy(struct dp_seat *seat);
void dp_select_interactive_start(
struct dp_selection *selection, struct dp_output *output, int x, int y);
void dp_select_interactive_move(struct dp_selection *selection, int x, int y);
void dp_select_interactive_stop(struct dp_selection *selection);
void dp_select_whole(struct dp_selection *selection, struct dp_output *output);
void dp_save(struct dp_state *state);
void dp_log_error(const char *fmt, ...);
void dp_log_fatal(const char *fmt, ...);
void *dp_zalloc(size_t size);
const char *dp_ext_from_path(const char *path);
enum dp_file_format dp_ext_to_format(const char *ext);
#endif

230
src/main.c Normal file
View File

@ -0,0 +1,230 @@
#include <getopt.h>
#include <stdlib.h>
#include <string.h>
#include <wayland-client-core.h>
#include <wayland-client-protocol.h>
#include <xkbcommon/xkbcommon.h>
#include "dulcepan.h"
#include "viewporter-protocol.h"
#include "wlr-layer-shell-unstable-v1-protocol.h"
#include "wlr-screencopy-unstable-v1-protocol.h"
static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t name,
const char *interface, uint32_t version) {
struct dp_state *state = data;
if (strcmp(interface, wl_compositor_interface.name) == 0) {
if (version >= 2) {
state->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 2);
}
} else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
state->subcompositor = wl_registry_bind(registry, name, &wl_subcompositor_interface, 1);
} else if (strcmp(interface, wp_viewporter_interface.name) == 0) {
state->viewporter = wl_registry_bind(registry, name, &wp_viewporter_interface, 1);
} else if (strcmp(interface, wl_shm_interface.name) == 0) {
state->shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
} else if (strcmp(interface, zwlr_layer_shell_v1_interface.name) == 0) {
state->layer_shell = wl_registry_bind(registry, name, &zwlr_layer_shell_v1_interface, 1);
} else if (strcmp(interface, zwlr_screencopy_manager_v1_interface.name) == 0) {
state->screencopy_manager =
wl_registry_bind(registry, name, &zwlr_screencopy_manager_v1_interface, 1);
} else if (strcmp(interface, wl_output_interface.name) == 0) {
if (state->initialized) {
dp_log_fatal("An output was added after initialization");
} else if (version < 2) {
dp_log_fatal("An output has too low version (%d)", version);
}
struct wl_output *global = wl_registry_bind(registry, name, &wl_output_interface, 2);
dp_output_create(state, name, global);
} else if (strcmp(interface, wl_seat_interface.name) == 0) {
struct wl_seat *global = wl_registry_bind(registry, name, &wl_seat_interface, 1);
dp_seat_create(state, name, global);
}
}
static void registry_handle_global_remove(void *data, struct wl_registry *registry, uint32_t name) {
struct dp_state *state = data;
struct dp_output *output;
wl_list_for_each (output, &state->outputs, link) {
if (output->name == name) {
dp_log_fatal("An output was removed");
}
}
struct dp_seat *seat;
wl_list_for_each (seat, &state->seats, link) {
if (seat->name == name) {
dp_seat_destroy(seat);
return;
}
}
}
static const struct wl_registry_listener registry_listener = {
.global = registry_handle_global,
.global_remove = registry_handle_global_remove,
};
static void run(struct dp_state *state) {
struct wl_registry *registry = wl_display_get_registry(state->display);
wl_registry_add_listener(registry, &registry_listener, state);
// Collect globals
wl_display_roundtrip(state->display);
if (state->compositor == NULL) {
dp_log_fatal("The compositor has no wl_compositor");
} else if (state->subcompositor == NULL) {
dp_log_fatal("The compositor has no wl_subcompositor");
} else if (state->viewporter == NULL) {
dp_log_fatal("The compositor has no wp_viewporter");
} else if (state->shm == NULL) {
dp_log_fatal("The compositor has no wl_shm");
} else if (state->layer_shell == NULL) {
dp_log_fatal("The compositor has no zwlr_layer_shell_v1");
} else if (state->screencopy_manager == NULL) {
dp_log_fatal("The compositor has no zwlr_screencopy_manager_v1");
}
if (wl_list_empty(&state->outputs)) {
dp_log_fatal("No outputs found");
} else if (wl_list_empty(&state->seats)) {
dp_log_fatal("No seats found");
}
state->initialized = true;
struct dp_output *output;
wl_list_for_each (output, &state->outputs, link) {
while (!output->initialized && wl_display_dispatch(state->display) != -1) {
// Wait for output to fully initialize
}
}
wl_list_for_each (output, &state->outputs, link) {
dp_output_redraw(output);
}
while (wl_display_dispatch(state->display) != -1 && state->status == DP_STATUS_RUNNING) {
// Wait
}
if (state->status == DP_STATUS_SAVED) {
// Hide everything before saving
wl_list_for_each (output, &state->outputs, link) {
dp_output_hide_surface(output);
}
wl_display_flush(state->display);
dp_save(state);
}
struct dp_seat *seat, *seat_tmp;
wl_list_for_each_safe (seat, seat_tmp, &state->seats, link) {
dp_seat_destroy(seat);
}
struct dp_output *output_tmp;
wl_list_for_each_safe (output, output_tmp, &state->outputs, link) {
dp_output_destroy(output);
}
wl_compositor_destroy(state->compositor);
wl_subcompositor_destroy(state->subcompositor);
wp_viewporter_destroy(state->viewporter);
wl_shm_destroy(state->shm);
zwlr_layer_shell_v1_destroy(state->layer_shell);
zwlr_screencopy_manager_v1_destroy(state->screencopy_manager);
wl_registry_destroy(registry);
}
static void help(const char *prog) {
fprintf(stderr,
"Usage: %s [options]\n"
"\n"
" -h Show this help message and quit.\n"
" -c <path> Specify the configuration file path.\n"
" -f <format> Specify the output file format.\n"
" -o <path> Specify the output file path.\n"
"\n"
"If the output file path is not specified, the resuling image will be printed\n"
"to the standard output. If the output file format is not specified, it is\n"
"guessed from the output file path if it's specified, and assumed to be PNG\n"
"otherwise.\n"
"\n"
"Supported formats: png, ppm.\n",
prog);
}
int main(int argc, char **argv) {
struct dp_state state = {0};
const char *config_path = NULL;
int opt;
while ((opt = getopt(argc, argv, "hc:f:o:")) != -1) {
switch (opt) {
case 'h':
help(argv[0]);
return 0;
case 'c':
config_path = optarg;
break;
case 'f':
state.output_format = dp_ext_to_format(optarg);
if (state.output_format == DP_FILE_UNKNOWN) {
dp_log_fatal("Unknown format %s", optarg);
}
break;
case 'o':
state.output_path = optarg;
break;
default: // '?'
help(argv[0]);
return 1;
}
}
dp_config_load(&state.config, config_path);
if (state.output_format == DP_FILE_UNKNOWN) {
if (state.output_path != NULL) {
const char *ext = dp_ext_from_path(state.output_path);
state.output_format = dp_ext_to_format(ext);
if (state.output_format == DP_FILE_UNKNOWN) {
dp_log_fatal("Unknown format of %s", state.output_path);
}
} else {
// Default format
state.output_format = DP_FILE_PNG;
}
}
wl_list_init(&state.outputs);
wl_list_init(&state.seats);
state.xkb_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
state.display = wl_display_connect(NULL);
if (state.display == NULL) {
dp_log_fatal("failed to connect to a Wayland compositor");
}
state.unselected_fill_image = pixman_image_create_solid_fill(&state.config.unselected_color);
state.selected_fill_image = pixman_image_create_solid_fill(&state.config.selected_color);
state.border_fill_image = pixman_image_create_solid_fill(&state.config.border_color);
run(&state);
pixman_image_unref(state.unselected_fill_image);
pixman_image_unref(state.selected_fill_image);
pixman_image_unref(state.border_fill_image);
xkb_context_unref(state.xkb_context);
wl_display_disconnect(state.display);
return 0;
}

10
src/meson.build Normal file
View File

@ -0,0 +1,10 @@
src = files(
'buffer.c',
'config.c',
'main.c',
'output.c',
'save.c',
'seat.c',
'select.c',
'util.c',
)

345
src/output.c Normal file
View File

@ -0,0 +1,345 @@
#include <assert.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <wayland-client-protocol.h>
#include "dulcepan.h"
#include "viewporter-protocol.h"
#include "wlr-layer-shell-unstable-v1-protocol.h"
#include "wlr-screencopy-unstable-v1-protocol.h"
// Initialiation flow:
// - Wait for mode+transform info
// - Wait for done, compute transformed size
// - Prepare surfaces, get a frame, wait for it to be ready
// - Commit the main surface, wait for a configure event
// - Save effective size, put the frame buffer on the main surface
// - The output is initialized
static void layer_surface_handle_configure(void *data, struct zwlr_layer_surface_v1 *layer_surface,
uint32_t serial, uint32_t width, uint32_t height) {
struct dp_output *output = data;
// Thanks layer-shell
int i_width = (int)width, i_height = (int)height;
zwlr_layer_surface_v1_ack_configure(layer_surface, serial);
if (output->initialized) {
if (i_width != output->effective_width || i_height != output->effective_height) {
dp_log_fatal("Layer surface size has changed: %dx%d => %dx%d", output->effective_width,
output->effective_height, i_width, i_height);
}
wl_surface_commit(output->main_surface);
return;
}
output->effective_width = (int)width;
output->effective_height = (int)height;
wp_viewport_set_destination(
output->main_viewport, output->effective_width, output->effective_height);
wp_viewport_set_destination(
output->select_viewport, output->effective_width, output->effective_height);
if (output->y_invert) {
wl_surface_set_buffer_transform(output->main_surface, WL_OUTPUT_TRANSFORM_FLIPPED_180);
}
wl_surface_attach(output->main_surface, output->frame_buffer, 0, 0);
wl_surface_commit(output->main_surface);
output->initialized = true;
}
static void layer_surface_handle_closed(void *data, struct zwlr_layer_surface_v1 *layer_surface) {
dp_log_fatal("A layer surface was closed");
}
static const struct zwlr_layer_surface_v1_listener layer_surface_listener = {
.configure = layer_surface_handle_configure,
.closed = layer_surface_handle_closed,
};
static void frame_handle_buffer(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t format,
uint32_t width, uint32_t height, uint32_t stride) {
struct dp_output *output = data;
if (output->frame_buffer != NULL) {
wl_buffer_destroy(output->frame_buffer);
}
output->frame_format = format;
output->frame_stride = (int)stride;
output->frame_buffer = dp_buffer_create(output->state, output->width, output->height,
output->frame_stride, output->frame_format, &output->frame_data, &output->frame_size);
zwlr_screencopy_frame_v1_copy(output->frame, output->frame_buffer);
}
static void frame_handle_flags(void *data, struct zwlr_screencopy_frame_v1 *frame, uint32_t flags) {
struct dp_output *output = data;
if ((flags & ZWLR_SCREENCOPY_FRAME_V1_FLAGS_Y_INVERT) != 0) {
output->y_invert = true;
}
}
static void 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 dp_output *output = data;
zwlr_screencopy_frame_v1_destroy(output->frame);
output->frame = NULL;
wl_surface_commit(output->main_surface);
}
static void frame_handle_failed(void *data, struct zwlr_screencopy_frame_v1 *frame) {
dp_log_fatal("Failed to copy a frame");
}
static const struct zwlr_screencopy_frame_v1_listener frame_listener = {
.buffer = frame_handle_buffer,
.flags = frame_handle_flags,
.ready = frame_handle_ready,
.failed = frame_handle_failed,
};
static void buffer_handle_release(void *data, struct wl_buffer *wl_buffer) {
struct dp_buffer *buffer = data;
buffer->used = false;
}
static const struct wl_buffer_listener buffer_listener = {
.release = buffer_handle_release,
};
static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y,
int32_t phys_width, int32_t phys_height, int32_t subpixel, const char *make,
const char *model, int32_t transform) {
struct dp_output *output = data;
if (output->has_geom) {
if (transform != output->transform) {
dp_log_fatal("Output transform has changed: %d (%#x) => %d (%#x)", output->transform,
output->transform, transform, transform);
}
}
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 dp_output *output = data;
if (output->has_geom) {
if (width != output->width || height != output->height) {
dp_log_fatal("Output mode has changed: %dx%d => %dx%d", output->width, output->height,
width, height);
}
}
output->width = width;
output->height = height;
}
static void output_handle_done(void *data, struct wl_output *wl_output) {
struct dp_output *output = data;
if (output->has_geom) {
return;
}
if ((output->transform & WL_OUTPUT_TRANSFORM_90) != 0) {
output->transformed_width = output->height;
output->transformed_height = output->width;
} else {
output->transformed_width = output->width;
output->transformed_height = output->height;
}
output->has_geom = true;
output->frame = zwlr_screencopy_manager_v1_capture_output(
output->state->screencopy_manager, false, output->wl_output);
zwlr_screencopy_frame_v1_add_listener(output->frame, &frame_listener, output);
struct dp_state *state = output->state;
output->main_surface = wl_compositor_create_surface(state->compositor);
output->main_layer_surface = zwlr_layer_shell_v1_get_layer_surface(state->layer_shell,
output->main_surface, output->wl_output, ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY, "dulcepan");
output->main_viewport = wp_viewporter_get_viewport(state->viewporter, output->main_surface);
wl_surface_set_buffer_transform(output->main_surface, output->transform);
struct wl_region *empty_region = wl_compositor_create_region(state->compositor);
wl_surface_set_input_region(output->main_surface, empty_region);
wl_region_destroy(empty_region);
zwlr_layer_surface_v1_add_listener(output->main_layer_surface, &layer_surface_listener, output);
zwlr_layer_surface_v1_set_anchor(output->main_layer_surface,
ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM |
ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT);
zwlr_layer_surface_v1_set_keyboard_interactivity(
output->main_layer_surface, ZWLR_LAYER_SURFACE_V1_KEYBOARD_INTERACTIVITY_EXCLUSIVE);
zwlr_layer_surface_v1_set_exclusive_zone(output->main_layer_surface, -1);
output->select_surface = wl_compositor_create_surface(state->compositor);
output->select_subsurface = wl_subcompositor_get_subsurface(
state->subcompositor, output->select_surface, output->main_surface);
output->select_viewport = wp_viewporter_get_viewport(state->viewporter, output->select_surface);
wl_subsurface_set_desync(output->select_subsurface);
wl_surface_set_user_data(output->select_surface, output);
for (size_t i = 0; i < DP_SWAPCHAIN_LEN; i++) {
struct dp_buffer *buffer = &output->swapchain[i];
int stride = output->transformed_width * 4;
buffer->wl_buffer =
dp_buffer_create(state, output->transformed_width, output->transformed_height,
stride, WL_SHM_FORMAT_ARGB8888, &buffer->data, &buffer->size);
buffer->image = pixman_image_create_bits(PIXMAN_a8r8g8b8, output->transformed_width,
output->transformed_height, buffer->data, stride);
wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer);
}
}
static void output_handle_scale(void *data, struct wl_output *wl_output, int32_t scale) {
// Ignored
}
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 redraw(struct dp_output *output);
static void redraw_callback_handle_done(
void *data, struct wl_callback *callback, uint32_t callback_data) {
struct dp_output *output = data;
wl_callback_destroy(output->redraw_callback);
output->redraw_callback = NULL;
if (output->needs_redraw) {
redraw(output);
}
}
static const struct wl_callback_listener redraw_callback_listener = {
.done = redraw_callback_handle_done,
};
static void redraw(struct dp_output *output) {
assert(output->redraw_callback == NULL);
struct dp_buffer *buffer = NULL;
for (size_t i = 0; i < DP_SWAPCHAIN_LEN; i++) {
struct dp_buffer *iter = &output->swapchain[i];
if (!iter->used) {
buffer = iter;
break;
}
}
if (buffer == NULL) {
dp_log_error("No free buffers in a swapchain\n");
return;
}
buffer->used = true;
struct dp_state *state = output->state;
pixman_image_composite32(PIXMAN_OP_SRC, state->unselected_fill_image, NULL, buffer->image, 0, 0,
0, 0, 0, 0, output->transformed_width, output->transformed_height);
struct dp_selection *selection = &state->selection;
if (output == selection->output) {
int border_size = state->config.border_size;
if (border_size != 0) {
pixman_image_composite32(PIXMAN_OP_SRC, state->border_fill_image, NULL, buffer->image,
0, 0, 0, 0, selection->x - border_size, selection->y - border_size,
selection->width + border_size * 2, border_size);
pixman_image_composite32(PIXMAN_OP_SRC, state->border_fill_image, NULL, buffer->image,
0, 0, 0, 0, selection->x - border_size, selection->y + selection->height,
selection->width + border_size * 2, border_size);
pixman_image_composite32(PIXMAN_OP_SRC, state->border_fill_image, NULL, buffer->image,
0, 0, 0, 0, selection->x - border_size, selection->y, border_size,
selection->height);
pixman_image_composite32(PIXMAN_OP_SRC, state->border_fill_image, NULL, buffer->image,
0, 0, 0, 0, selection->x + selection->width, selection->y, border_size,
selection->height);
}
pixman_image_composite32(PIXMAN_OP_SRC, state->selected_fill_image, NULL, buffer->image, 0,
0, 0, 0, selection->x, selection->y, selection->width, selection->height);
}
wl_surface_attach(output->select_surface, buffer->wl_buffer, 0, 0);
wl_surface_damage(
output->select_surface, 0, 0, output->transformed_width, output->transformed_height);
output->redraw_callback = wl_surface_frame(output->select_surface);
wl_callback_add_listener(output->redraw_callback, &redraw_callback_listener, output);
output->needs_redraw = false;
wl_surface_commit(output->select_surface);
}
void dp_output_redraw(struct dp_output *output) {
if (output->redraw_callback != NULL) {
output->needs_redraw = true;
return;
}
redraw(output);
}
void dp_output_hide_surface(struct dp_output *output) {
wl_surface_attach(output->main_surface, NULL, 0, 0);
wl_surface_commit(output->main_surface);
}
void dp_output_create(struct dp_state *state, uint32_t name, struct wl_output *wl_output) {
struct dp_output *output = dp_zalloc(sizeof(*output));
output->state = state;
output->name = name;
output->wl_output = wl_output;
wl_output_add_listener(output->wl_output, &output_listener, output);
wl_list_insert(&state->outputs, &output->link);
}
void dp_output_destroy(struct dp_output *output) {
wl_output_release(output->wl_output);
assert(output->frame == NULL);
wl_buffer_destroy(output->frame_buffer);
munmap(output->frame_data, output->frame_size);
if (output->redraw_callback != NULL) {
wl_callback_destroy(output->redraw_callback);
}
for (size_t i = 0; i < DP_SWAPCHAIN_LEN; i++) {
struct dp_buffer *buffer = &output->swapchain[i];
wl_buffer_destroy(buffer->wl_buffer);
pixman_image_unref(buffer->image);
munmap(buffer->data, buffer->size);
}
wl_subsurface_destroy(output->select_subsurface);
wp_viewport_destroy(output->select_viewport);
wl_surface_destroy(output->select_surface);
zwlr_layer_surface_v1_destroy(output->main_layer_surface);
wp_viewport_destroy(output->main_viewport);
wl_surface_destroy(output->main_surface);
wl_list_remove(&output->link);
free(output);
}

170
src/save.c Normal file
View File

@ -0,0 +1,170 @@
#include <errno.h>
#include <spng.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wayland-client-core.h>
#include <wayland-client-protocol.h>
#include "dulcepan.h"
static pixman_format_code_t shm_to_pixman(enum wl_shm_format shm_format) {
struct {
enum wl_shm_format shm;
pixman_format_code_t pixman;
} formats[] = {
#if DP_BIG_ENDIAN
{WL_SHM_FORMAT_ARGB8888, PIXMAN_b8g8r8a8},
{WL_SHM_FORMAT_XRGB8888, PIXMAN_b8g8r8x8},
{WL_SHM_FORMAT_ABGR8888, PIXMAN_r8g8b8a8},
{WL_SHM_FORMAT_XBGR8888, PIXMAN_r8g8b8x8},
{WL_SHM_FORMAT_BGRA8888, PIXMAN_a8r8g8b8},
{WL_SHM_FORMAT_BGRX8888, PIXMAN_x8r8g8b8},
{WL_SHM_FORMAT_RGBA8888, PIXMAN_a8b8g8r8},
{WL_SHM_FORMAT_RGBX8888, PIXMAN_x8b8g8r8},
#else
{WL_SHM_FORMAT_RGB332, PIXMAN_r3g3b2},
{WL_SHM_FORMAT_BGR233, PIXMAN_b2g3r3},
{WL_SHM_FORMAT_ARGB4444, PIXMAN_a4r4g4b4},
{WL_SHM_FORMAT_XRGB4444, PIXMAN_x4r4g4b4},
{WL_SHM_FORMAT_ABGR4444, PIXMAN_a4b4g4r4},
{WL_SHM_FORMAT_XBGR4444, PIXMAN_x4b4g4r4},
{WL_SHM_FORMAT_ARGB1555, PIXMAN_a1r5g5b5},
{WL_SHM_FORMAT_XRGB1555, PIXMAN_x1r5g5b5},
{WL_SHM_FORMAT_ABGR1555, PIXMAN_a1b5g5r5},
{WL_SHM_FORMAT_XBGR1555, PIXMAN_x1b5g5r5},
{WL_SHM_FORMAT_RGB565, PIXMAN_r5g6b5},
{WL_SHM_FORMAT_BGR565, PIXMAN_b5g6r5},
{WL_SHM_FORMAT_RGB888, PIXMAN_r8g8b8},
{WL_SHM_FORMAT_BGR888, PIXMAN_b8g8r8},
{WL_SHM_FORMAT_ARGB8888, PIXMAN_a8r8g8b8},
{WL_SHM_FORMAT_XRGB8888, PIXMAN_x8r8g8b8},
{WL_SHM_FORMAT_ABGR8888, PIXMAN_a8b8g8r8},
{WL_SHM_FORMAT_XBGR8888, PIXMAN_x8b8g8r8},
{WL_SHM_FORMAT_BGRA8888, PIXMAN_b8g8r8a8},
{WL_SHM_FORMAT_BGRX8888, PIXMAN_b8g8r8x8},
{WL_SHM_FORMAT_RGBA8888, PIXMAN_r8g8b8a8},
{WL_SHM_FORMAT_RGBX8888, PIXMAN_r8g8b8x8},
{WL_SHM_FORMAT_ARGB2101010, PIXMAN_a2r10g10b10},
{WL_SHM_FORMAT_ABGR2101010, PIXMAN_a2b10g10r10},
{WL_SHM_FORMAT_XRGB2101010, PIXMAN_x2r10g10b10},
{WL_SHM_FORMAT_XBGR2101010, PIXMAN_x2b10g10r10},
#endif
};
for (size_t i = 0; i < sizeof(formats) / sizeof(*formats); i++) {
if (formats[i].shm == shm_format) {
return formats[i].pixman;
}
}
dp_log_fatal("failed to get a matching Pixman format for wl_shm format");
return 0; // Unreachable, actually
}
static void write_png(FILE *fp, pixman_image_t *image, int width, int height) {
void *data = pixman_image_get_data(image);
size_t size = (size_t)(width * height * 4);
struct spng_ctx *spng_ctx = spng_ctx_new(SPNG_CTX_ENCODER);
struct spng_ihdr ihdr = {
.width = (uint32_t)width,
.height = (uint32_t)height,
.bit_depth = 8,
.color_type = SPNG_COLOR_TYPE_TRUECOLOR_ALPHA,
};
spng_set_ihdr(spng_ctx, &ihdr);
spng_set_png_file(spng_ctx, fp);
int err = spng_encode_image(spng_ctx, data, size, SPNG_FMT_PNG, SPNG_ENCODE_FINALIZE);
if (err != 0) {
dp_log_fatal("spng_encode_image() failed: %s", spng_strerror(err));
}
spng_ctx_free(spng_ctx);
}
static void write_ppm(FILE *fp, pixman_image_t *image, int width, int height) {
uint32_t *data = pixman_image_get_data(image);
size_t size = (size_t)(width * height);
fprintf(fp, "P6\n%d %d\n255\n", width, height);
for (size_t i = 0; i < size; i++) {
uint32_t pixel = data[i];
char bytes[3] = {
(char)((pixel >> 0) & 0xff),
(char)((pixel >> 8) & 0xff),
(char)((pixel >> 16) & 0xff),
};
fwrite(bytes, 1, sizeof(bytes), fp);
}
fflush(fp);
if (ferror(fp) != 0) {
dp_log_fatal("Failed to write the output file\n");
}
}
void dp_save(struct dp_state *state) {
struct dp_selection *selection = &state->selection;
struct dp_output *output = selection->output;
if (output == NULL || selection->width == 0 || selection->height == 0) {
dp_log_fatal("The selection is empty");
}
pixman_format_code_t pixman_format = shm_to_pixman(output->frame_format);
pixman_image_t *frame_image = pixman_image_create_bits(
pixman_format, output->width, output->height, output->frame_data, output->frame_stride);
static const pixman_fixed_t cosines[] = {pixman_fixed_1, 0, -pixman_fixed_1, 0};
pixman_fixed_t scale_x = (output->transform & WL_OUTPUT_TRANSFORM_FLIPPED) != 0
? -pixman_fixed_1
: pixman_fixed_1;
pixman_fixed_t scale_y = output->y_invert ? -pixman_fixed_1 : pixman_fixed_1;
struct pixman_transform frame_transform;
pixman_transform_init_identity(&frame_transform);
pixman_transform_translate(&frame_transform, NULL,
-pixman_int_to_fixed(output->transformed_width) / 2,
-pixman_int_to_fixed(output->transformed_height) / 2);
pixman_transform_scale(&frame_transform, NULL, scale_x, scale_y);
pixman_transform_rotate(&frame_transform, NULL, cosines[output->transform % 4],
cosines[(output->transform + 1) % 4]);
pixman_transform_translate(&frame_transform, NULL, pixman_int_to_fixed(output->width) / 2,
pixman_int_to_fixed(output->height) / 2);
pixman_image_set_transform(frame_image, &frame_transform);
pixman_image_t *out_image =
pixman_image_create_bits(PIXMAN_x8b8g8r8, selection->width, selection->height, NULL, 0);
pixman_image_composite32(PIXMAN_OP_SRC, frame_image, NULL, out_image, selection->x,
selection->y, 0, 0, 0, 0, selection->width, selection->height);
pixman_image_unref(frame_image);
FILE *fp = NULL;
if (state->output_path == NULL) {
fp = stdout;
} else {
fp = fopen(state->output_path, "w");
if (fp == NULL) {
dp_log_fatal("Failed to open %s: %s", state->output_path, strerror(errno));
}
}
switch (state->output_format) {
case DP_FILE_UNKNOWN:
abort(); // Unreachable
case DP_FILE_PNG:
write_png(fp, out_image, selection->width, selection->height);
break;
case DP_FILE_PPM:
write_ppm(fp, out_image, selection->width, selection->height);
break;
}
pixman_image_unref(out_image);
fclose(fp);
}

216
src/seat.c Normal file
View File

@ -0,0 +1,216 @@
#include <assert.h>
#include <linux/input-event-codes.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <wayland-client-protocol.h>
#include <xkbcommon/xkbcommon.h>
#include "dulcepan.h"
static void keyboard_handle_keymap(void *data, struct wl_keyboard *wl_keyboard,
enum wl_keyboard_keymap_format format, int32_t fd, uint32_t size) {
struct dp_seat *seat = data;
void *keymap_buffer;
size_t keymap_len;
xkb_keymap_unref(seat->xkb_keymap);
xkb_state_unref(seat->xkb_state);
switch (format) {
case WL_KEYBOARD_KEYMAP_FORMAT_NO_KEYMAP:
seat->xkb_keymap = xkb_keymap_new_from_names(
seat->state->xkb_context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS);
break;
case WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1:
keymap_len = size - 1;
keymap_buffer = mmap(NULL, keymap_len, PROT_READ, MAP_PRIVATE, fd, 0);
if (keymap_buffer == MAP_FAILED) {
dp_log_fatal("mmap() for a keymap failed");
}
seat->xkb_keymap = xkb_keymap_new_from_buffer(seat->state->xkb_context, keymap_buffer,
keymap_len, XKB_KEYMAP_FORMAT_TEXT_V1, XKB_KEYMAP_COMPILE_NO_FLAGS);
munmap(keymap_buffer, keymap_len);
close(fd);
break;
}
seat->xkb_state = xkb_state_new(seat->xkb_keymap);
}
static void keyboard_handle_enter(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
struct wl_surface *surface, struct wl_array *keys) {
// Ignored
}
static void keyboard_handle_leave(
void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, struct wl_surface *surface) {
// Ignored
}
static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
uint32_t time_msec, uint32_t keycode, enum wl_keyboard_key_state key_state) {
struct dp_seat *seat = data;
if (key_state != WL_KEYBOARD_KEY_STATE_PRESSED) {
return;
}
xkb_keysym_t keysym = xkb_state_key_get_one_sym(seat->xkb_state, keycode + 8);
// TODO: configurable
// TODO: more actions
switch (keysym) {
case XKB_KEY_q:
seat->state->status = DP_STATUS_QUIT;
break;
case XKB_KEY_s:
seat->state->status = DP_STATUS_SAVED;
break;
}
}
static void keyboard_handle_modifiers(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial,
uint32_t depressed, uint32_t latched, uint32_t locked, uint32_t group) {
struct dp_seat *seat = data;
xkb_state_update_mask(seat->xkb_state, depressed, latched, locked, 0, 0, group);
}
static const struct wl_keyboard_listener keyboard_listener = {
.keymap = keyboard_handle_keymap,
.enter = keyboard_handle_enter,
.leave = keyboard_handle_leave,
.key = keyboard_handle_key,
.modifiers = keyboard_handle_modifiers,
};
static void save_position(struct dp_seat *seat, wl_fixed_t x, wl_fixed_t y) {
struct dp_output *output = seat->ptr_output;
seat->ptr_x =
(int)(wl_fixed_to_double(x) * output->transformed_width / output->effective_width);
seat->ptr_y =
(int)(wl_fixed_to_double(y) * output->transformed_height / output->effective_height);
}
static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
struct wl_surface *surface, wl_fixed_t sx, wl_fixed_t sy) {
struct dp_seat *seat = data;
seat->ptr_output = wl_surface_get_user_data(surface);
assert(seat->ptr_output != NULL);
save_position(seat, sx, sy);
}
static void pointer_handle_leave(
void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {
struct dp_seat *seat = data;
seat->ptr_output = NULL;
}
static void pointer_handle_motion(
void *data, struct wl_pointer *wl_pointer, uint32_t serial, wl_fixed_t sx, wl_fixed_t sy) {
struct dp_seat *seat = data;
if (seat->ptr_output == NULL) {
return; // Shouldn't happen
}
save_position(seat, sx, sy);
dp_select_interactive_move(&seat->state->selection, seat->ptr_x, seat->ptr_y);
}
static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
uint32_t time_msec, uint32_t button, enum wl_pointer_button_state button_state) {
struct dp_seat *seat = data;
struct dp_state *state = seat->state;
struct dp_selection *selection = &state->selection;
if (button_state != WL_POINTER_BUTTON_STATE_PRESSED) {
if (selection->interactive_moved && state->config.quick_select) {
state->status = DP_STATUS_SAVED;
}
dp_select_interactive_stop(selection);
return;
}
if (seat->ptr_output == NULL) {
return; // Shouldn't happen
}
switch (button) {
case BTN_LEFT:
dp_select_interactive_start(selection, seat->ptr_output, seat->ptr_x, seat->ptr_y);
break;
case BTN_RIGHT:
dp_select_whole(selection, seat->ptr_output);
if (state->config.quick_select) {
state->status = DP_STATUS_SAVED;
}
break;
case BTN_MIDDLE:
state->status = DP_STATUS_SAVED;
return;
}
}
static void pointer_handle_axis(void *data, struct wl_pointer *wl_pointer, uint32_t time_msec,
enum wl_pointer_axis axis, wl_fixed_t value) {
// Ignored
}
static const struct wl_pointer_listener pointer_listener = {
.enter = pointer_handle_enter,
.leave = pointer_handle_leave,
.motion = pointer_handle_motion,
.button = pointer_handle_button,
.axis = pointer_handle_axis,
};
static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, uint32_t caps) {
struct dp_seat *seat = data;
if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) != 0 && seat->keyboard == NULL) {
seat->keyboard = wl_seat_get_keyboard(wl_seat);
wl_keyboard_add_listener(seat->keyboard, &keyboard_listener, seat);
}
if ((caps & WL_SEAT_CAPABILITY_POINTER) != 0 && seat->pointer == NULL) {
seat->pointer = wl_seat_get_pointer(wl_seat);
wl_pointer_add_listener(seat->pointer, &pointer_listener, seat);
}
}
static void seat_handle_name(void *data, struct wl_seat *wl_seat, const char *name) {
// Ignored
}
static const struct wl_seat_listener seat_listener = {
.capabilities = seat_handle_capabilities,
.name = seat_handle_name,
};
void dp_seat_create(struct dp_state *state, uint32_t name, struct wl_seat *wl_seat) {
struct dp_seat *seat = dp_zalloc(sizeof(*seat));
seat->state = state;
seat->name = name;
seat->wl_seat = wl_seat;
seat->xkb_keymap =
xkb_keymap_new_from_names(seat->state->xkb_context, NULL, XKB_KEYMAP_COMPILE_NO_FLAGS);
seat->xkb_state = xkb_state_new(seat->xkb_keymap);
wl_seat_add_listener(seat->wl_seat, &seat_listener, seat);
wl_list_insert(&state->seats, &seat->link);
}
void dp_seat_destroy(struct dp_seat *seat) {
wl_seat_release(seat->wl_seat);
if (seat->keyboard != NULL) {
wl_keyboard_release(seat->keyboard);
}
if (seat->pointer != NULL) {
wl_pointer_release(seat->pointer);
}
xkb_keymap_unref(seat->xkb_keymap);
xkb_state_unref(seat->xkb_state);
wl_list_remove(&seat->link);
free(seat);
}

63
src/select.c Normal file
View File

@ -0,0 +1,63 @@
#include "dulcepan.h"
static void set_selected_output(struct dp_selection *selection, struct dp_output *output) {
if (selection->output == output) {
return;
}
struct dp_output *prev = selection->output;
selection->output = output;
if (prev != NULL) {
dp_output_redraw(prev);
}
}
void dp_select_interactive_start(
struct dp_selection *selection, struct dp_output *output, int x, int y) {
selection->interactive_output = output;
selection->interactive_x = x;
selection->interactive_y = y;
selection->interactive_moved = false;
dp_output_redraw(output);
}
void dp_select_interactive_move(struct dp_selection *selection, int x, int y) {
if (selection->interactive_output == NULL) {
return;
}
set_selected_output(selection, selection->interactive_output);
selection->interactive_moved = true;
if (x < selection->interactive_x) {
selection->x = x;
selection->width = selection->interactive_x - x;
} else {
selection->x = selection->interactive_x;
selection->width = x - selection->interactive_x;
}
if (y < selection->interactive_y) {
selection->y = y;
selection->height = selection->interactive_y - y;
} else {
selection->y = selection->interactive_y;
selection->height = y - selection->interactive_y;
}
dp_output_redraw(selection->output);
}
void dp_select_interactive_stop(struct dp_selection *selection) {
selection->interactive_output = NULL;
selection->interactive_moved = false;
}
void dp_select_whole(struct dp_selection *selection, struct dp_output *output) {
set_selected_output(selection, output);
selection->x = 0;
selection->y = 0;
selection->width = output->transformed_width;
selection->height = output->transformed_height;
dp_output_redraw(output);
}

57
src/util.c Normal file
View File

@ -0,0 +1,57 @@
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "dulcepan.h"
static void vlog_prefixed(const char *prefix, const char *fmt, va_list args) {
fprintf(stderr, "[%s] ", prefix);
vfprintf(stderr, fmt, args);
fprintf(stderr, "\n");
}
void dp_log_error(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vlog_prefixed("error", fmt, args);
va_end(args);
}
void dp_log_fatal(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
vlog_prefixed("FATAL", fmt, args);
va_end(args);
exit(1);
}
void *dp_zalloc(size_t size) {
void *ptr = calloc(1, size);
if (ptr == NULL) {
dp_log_fatal("Failed to allocate %zu bytes", size);
}
return ptr;
}
const char *dp_ext_from_path(const char *path) {
size_t len = strlen(path);
for (size_t i = len; i-- > 0;) {
if (path[i] == '.') {
return path + i + 1;
} else if (path[i] == '/') {
break;
}
}
return path + len;
}
enum dp_file_format dp_ext_to_format(const char *ext) {
if (strcasecmp(ext, "ppm") == 0) {
return DP_FILE_PPM;
} else if (strcasecmp(ext, "png") == 0) {
return DP_FILE_PNG;
}
return DP_FILE_UNKNOWN;
}