mirror of
https://codeberg.org/vyivel/dulcepan/
synced 2025-03-12 18:59:15 +02:00
Make it good™
This commit is contained in:
parent
8609265acf
commit
1436162663
20
.editorconfig
Normal file
20
.editorconfig
Normal 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
|
13
README.md
13
README.md
@ -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
13
dulcepan.cfg
Normal 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
855
main.c
@ -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, ®istry_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;
|
|
||||||
}
|
|
29
meson.build
29
meson.build
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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
63
src/buffer.c
Normal 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
180
src/config.c
Normal 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
173
src/dulcepan.h
Normal 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
230
src/main.c
Normal 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, ®istry_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
10
src/meson.build
Normal 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
345
src/output.c
Normal 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
170
src/save.c
Normal 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
216
src/seat.c
Normal 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
63
src/select.c
Normal 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
57
src/util.c
Normal 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;
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user