1
0
mirror of https://codeberg.org/vyivel/dulcepan/ synced 2025-06-24 22:44:18 +03:00

16 Commits

Author SHA1 Message Date
9879f3ad69 config: allow to bind multiple keys to the same action
Allows for both Space and Enter to save the selection, for example.
2025-01-31 23:09:41 +03:00
51f744c876 config: unconstify value in loaders 2025-01-31 23:09:33 +03:00
640b015053 util: allow size 0 in dp_zalloc() 2025-01-31 23:09:33 +03:00
0ccb868b39 seat: use crosshair cursor when the whole output is selected
Closes: https://codeberg.org/vyivel/dulcepan/issues/19
2025-01-31 20:07:17 +00:00
7cc26b8e38 select: redraw on interaction start
This fixes a mismatch between the actual selection and its visual
representation when the right mouse button is clicked but not moved
(easier to reproduce with animations disabled).
2025-01-30 22:27:28 +03:00
d2620c60c5 main: refuse to dump the result into tty
Closes: https://codeberg.org/vyivel/dulcepan/issues/17
2024-11-06 23:38:01 +03:00
c343fc1f73 main: validate args before loading everything else 2024-11-06 23:27:05 +03:00
f9665e2661 meson: bump version to 1.0.2 2024-09-09 14:36:14 +03:00
fccc2e2e97 Fix processing of multiple output modes 2024-09-09 06:24:33 +03:00
5bbfbd4d84 config: document where the persistent state is stored 2024-08-09 16:06:26 +03:00
e797e5a324 output: make swapchain lazy 2024-08-09 15:58:04 +03:00
bdf8308c4e seat: make cursor-shape-v1 support optional 2024-08-09 15:58:00 +03:00
6158dd112b meson: bump version to 1.0.1 2024-08-09 15:47:20 +03:00
6f7dfab735 seat: use correct serial when updating cursor shape 2024-08-09 15:46:37 +03:00
553147259b Explain controls in the help message 2024-07-05 07:45:06 +03:00
e81e274380 output: don't redraw if there's nothing to animate 2024-07-03 13:34:35 +03:00
9 changed files with 183 additions and 68 deletions

View File

@ -33,12 +33,14 @@ animation-duration = 0
# or when a whole output is selected with a mouse button. # or when a whole output is selected with a mouse button.
quick-select = false quick-select = false
# If true, dulcepan will remember selection between runs # If true, dulcepan will remember selection between runs.
# The state is stored at $XDG_CACHE_HOME/dulcepan.
persistence = true persistence = true
# PNG (zlib) compression level, 0-9 # PNG (zlib) compression level, 0-9
png-compression = 6 png-compression = 6
# Key bindings # Key bindings. Each binding is a comma-separated list of key names; empty names
# are ignored. A binding may be empty.
quit-key = Escape quit-key = Escape
save-key = Space save-key = Space,Enter

View File

@ -1,7 +1,7 @@
project( project(
'dulcepan', 'dulcepan',
'c', 'c',
version: '1.0.0', version: '1.0.2',
license: 'GPL-3.0-only', license: 'GPL-3.0-only',
default_options: [ default_options: [
'c_std=c11', 'c_std=c11',

View File

@ -15,7 +15,22 @@ static inline void bytes_to_color(uint8_t bytes[static 4], float out[static 4])
} }
} }
static void load_color(const char *value, int line_idx, float out[static 4]) { static void keybinding_init(struct dp_keybinding *kb, xkb_keysym_t *syms, size_t n_syms) {
*kb = (struct dp_keybinding){
.syms = dp_zalloc(sizeof(*kb->syms) * n_syms),
.n_syms = n_syms,
};
for (size_t i = 0; i < n_syms; i++) {
xkb_keysym_t sym = syms[i];
kb->syms[i] = sym;
}
}
static void keybinding_finish(struct dp_keybinding *kb) {
free(kb->syms);
}
static void load_color(char *value, int line_idx, float out[static 4]) {
size_t len = strlen(value); size_t len = strlen(value);
uint8_t bytes[4] = {0, 0, 0, 0}; uint8_t bytes[4] = {0, 0, 0, 0};
@ -46,7 +61,7 @@ bad:
dp_log_fatal("Config: invalid color %s on line %d", value, line_idx); dp_log_fatal("Config: invalid color %s on line %d", value, line_idx);
} }
static void load_int(const char *value, int line_idx, int min, int max, int *out) { static void load_int(char *value, int line_idx, int min, int max, int *out) {
const char *p = value; const char *p = value;
int mul = 1; int mul = 1;
if (*p == '-') { if (*p == '-') {
@ -68,7 +83,7 @@ static void load_int(const char *value, int line_idx, int min, int max, int *out
} }
} }
static void load_bool(const char *value, int line_idx, bool *out) { static void load_bool(char *value, int line_idx, bool *out) {
if (strcmp(value, "true") == 0) { if (strcmp(value, "true") == 0) {
*out = true; *out = true;
} else if (strcmp(value, "false") == 0) { } else if (strcmp(value, "false") == 0) {
@ -78,19 +93,31 @@ static void load_bool(const char *value, int line_idx, bool *out) {
} }
} }
static void load_key(const char *value, int line_idx, xkb_keysym_t *out) { static void load_key(char *value, int line_idx, struct dp_keybinding *out) {
*out = xkb_keysym_from_name(value, XKB_KEYSYM_CASE_INSENSITIVE); size_t n_syms = 0;
if (*out == XKB_KEY_NoSymbol) { xkb_keysym_t syms[32];
dp_log_fatal("Config: unknown key %s on line %d", value, line_idx);
char *save_ptr = NULL;
for (char *name; (name = strtok_r(value, ",", &save_ptr)) != NULL; value = NULL) {
if (n_syms == sizeof(syms) / sizeof(*syms)) {
// chill out
dp_log_fatal("Config: too many keys on line %d", line_idx);
}
xkb_keysym_t sym = xkb_keysym_from_name(name, XKB_KEYSYM_CASE_INSENSITIVE);
if (sym == XKB_KEY_NoSymbol) {
dp_log_fatal("Config: unknown key \"%s\" on line %d", name, line_idx);
}
syms[n_syms++] = sym;
} }
keybinding_finish(out);
keybinding_init(out, syms, n_syms);
} }
void dp_config_load(struct dp_state *state, const char *user_path) { void dp_config_load(struct dp_state *state, const char *user_path) {
struct dp_config *config = &state->config; struct dp_config *config = &state->config;
*config = (struct dp_config){ *config = (struct dp_config){
.quit_key = XKB_KEY_Escape,
.save_key = XKB_KEY_space,
.border_size = 2, .border_size = 2,
.border_gradient = DP_BORDER_GRADIENT_NONE, .border_gradient = DP_BORDER_GRADIENT_NONE,
.gradient_angle = 45, .gradient_angle = 45,
@ -100,11 +127,15 @@ void dp_config_load(struct dp_state *state, const char *user_path) {
.quick_select = false, .quick_select = false,
.persistence = true, .persistence = true,
}; };
bytes_to_color((uint8_t[]){0xff, 0xff, 0xff, 0x40}, config->unselected_color); bytes_to_color((uint8_t[]){0xff, 0xff, 0xff, 0x40}, config->unselected_color);
bytes_to_color((uint8_t[]){0x00, 0x00, 0x00, 0x00}, config->selected_color); bytes_to_color((uint8_t[]){0x00, 0x00, 0x00, 0x00}, config->selected_color);
bytes_to_color((uint8_t[]){0xff, 0xff, 0xff, 0xff}, config->border_color); bytes_to_color((uint8_t[]){0xff, 0xff, 0xff, 0xff}, config->border_color);
bytes_to_color((uint8_t[]){0x00, 0x00, 0x00, 0xff}, config->border_secondary_color); bytes_to_color((uint8_t[]){0x00, 0x00, 0x00, 0xff}, config->border_secondary_color);
keybinding_init(&config->quit_kb, (xkb_keysym_t[]){XKB_KEY_Escape}, 1);
keybinding_init(&config->save_kb, (xkb_keysym_t[]){XKB_KEY_space, XKB_KEY_Return}, 2);
FILE *fp = NULL; FILE *fp = NULL;
if (user_path != NULL) { if (user_path != NULL) {
fp = fopen(user_path, "r"); fp = fopen(user_path, "r");
@ -215,9 +246,9 @@ void dp_config_load(struct dp_state *state, const char *user_path) {
} else if (strcmp(key, "persistence") == 0) { } else if (strcmp(key, "persistence") == 0) {
load_bool(value, line_idx, &config->persistence); load_bool(value, line_idx, &config->persistence);
} else if (strcmp(key, "quit-key") == 0) { } else if (strcmp(key, "quit-key") == 0) {
load_key(value, line_idx, &config->quit_key); load_key(value, line_idx, &config->quit_kb);
} else if (strcmp(key, "save-key") == 0) { } else if (strcmp(key, "save-key") == 0) {
load_key(value, line_idx, &config->save_key); load_key(value, line_idx, &config->save_kb);
} else { } else {
dp_log_error("Config: unknown key %s on line %d", key, line_idx); dp_log_error("Config: unknown key %s on line %d", key, line_idx);
} }
@ -226,3 +257,10 @@ void dp_config_load(struct dp_state *state, const char *user_path) {
fclose(fp); fclose(fp);
} }
void dp_config_finish(struct dp_state *state) {
struct dp_config *config = &state->config;
keybinding_finish(&config->quit_kb);
keybinding_finish(&config->save_kb);
}

View File

@ -71,6 +71,7 @@ struct dp_output {
void *frame_data; void *frame_data;
size_t frame_size; size_t frame_size;
// wl_buffer is NULL if uninitialized
struct dp_buffer swapchain[DP_SWAPCHAIN_LEN]; struct dp_buffer swapchain[DP_SWAPCHAIN_LEN];
struct wl_list link; struct wl_list link;
@ -88,6 +89,7 @@ struct dp_seat {
struct xkb_keymap *xkb_keymap; struct xkb_keymap *xkb_keymap;
struct xkb_state *xkb_state; struct xkb_state *xkb_state;
uint32_t pointer_serial;
struct wp_cursor_shape_device_v1 *cursor_shape_device; struct wp_cursor_shape_device_v1 *cursor_shape_device;
// The output the pointer is on // The output the pointer is on
@ -139,6 +141,11 @@ enum dp_border_gradient {
DP_BORDER_GRADIENT_LOOP, DP_BORDER_GRADIENT_LOOP,
}; };
struct dp_keybinding {
xkb_keysym_t *syms;
size_t n_syms;
};
struct dp_config { struct dp_config {
// RGBA, not premultiplied // RGBA, not premultiplied
float unselected_color[4]; float unselected_color[4];
@ -146,8 +153,8 @@ struct dp_config {
float border_color[4]; float border_color[4];
float border_secondary_color[4]; float border_secondary_color[4];
xkb_keysym_t quit_key; struct dp_keybinding quit_kb;
xkb_keysym_t save_key; struct dp_keybinding save_kb;
int border_size; // 0 if disabled int border_size; // 0 if disabled
enum dp_border_gradient border_gradient; enum dp_border_gradient border_gradient;
@ -172,6 +179,8 @@ struct dp_state {
struct wl_shm *shm; struct wl_shm *shm;
struct zwlr_layer_shell_v1 *layer_shell; struct zwlr_layer_shell_v1 *layer_shell;
struct zwlr_screencopy_manager_v1 *screencopy_manager; struct zwlr_screencopy_manager_v1 *screencopy_manager;
// Optional
struct wp_cursor_shape_manager_v1 *cursor_shape_manager; struct wp_cursor_shape_manager_v1 *cursor_shape_manager;
bool initialized; bool initialized;
@ -200,6 +209,7 @@ struct dp_state {
}; };
void dp_config_load(struct dp_state *state, const char *user_path); void dp_config_load(struct dp_state *state, const char *user_path);
void dp_config_finish(struct dp_state *state);
// When done, data must be unmapped // When done, data must be unmapped
struct wl_buffer *dp_buffer_create(struct dp_state *state, int32_t width, int32_t height, struct wl_buffer *dp_buffer_create(struct dp_state *state, int32_t width, int32_t height,

View File

@ -1,6 +1,7 @@
#include <getopt.h> #include <getopt.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <unistd.h>
#include <wayland-client-core.h> #include <wayland-client-core.h>
#include <wayland-client-protocol.h> #include <wayland-client-protocol.h>
#include <xkbcommon/xkbcommon.h> #include <xkbcommon/xkbcommon.h>
@ -89,8 +90,6 @@ static void run(struct dp_state *state) {
dp_log_fatal("The compositor has no zwlr_layer_shell_v1"); dp_log_fatal("The compositor has no zwlr_layer_shell_v1");
} else if (state->screencopy_manager == NULL) { } else if (state->screencopy_manager == NULL) {
dp_log_fatal("The compositor has no zwlr_screencopy_manager_v1"); dp_log_fatal("The compositor has no zwlr_screencopy_manager_v1");
} else if (state->cursor_shape_manager == NULL) {
dp_log_fatal("The compositor has no wp_cursor_shape_manager_v1");
} }
if (wl_list_empty(&state->outputs)) { if (wl_list_empty(&state->outputs)) {
@ -150,7 +149,10 @@ static void run(struct dp_state *state) {
wl_shm_destroy(state->shm); wl_shm_destroy(state->shm);
zwlr_layer_shell_v1_destroy(state->layer_shell); zwlr_layer_shell_v1_destroy(state->layer_shell);
zwlr_screencopy_manager_v1_destroy(state->screencopy_manager); zwlr_screencopy_manager_v1_destroy(state->screencopy_manager);
wp_cursor_shape_manager_v1_destroy(state->cursor_shape_manager);
if (state->cursor_shape_manager) {
wp_cursor_shape_manager_v1_destroy(state->cursor_shape_manager);
}
wl_registry_destroy(registry); wl_registry_destroy(registry);
} }
@ -165,12 +167,16 @@ static void help(const char *prog) {
" -f <format> Specify the output file format.\n" " -f <format> Specify the output file format.\n"
" -o <path> Specify the output file path.\n" " -o <path> Specify the output file path.\n"
"\n" "\n"
"If the output file path is not specified, the resuling image will be printed\n" "If the output file path is not specified, the resuling image will be printed to\n"
"to the standard output. If the output file format is not specified, it is\n" "the standard output, which is expected to not be a terminal. If the output file\n"
"guessed from the output file path if it's specified, and assumed to be PNG\n" "format is not specified, it is guessed from the output file path if it's\n"
"otherwise.\n" "specified, and assumed to be PNG otherwise.\n"
"\n" "\n"
"Supported formats: png, ppm.\n", "Supported formats: png, ppm.\n"
"\n"
"Use the left or right mouse button to select a part of the output. A selection\n"
"can also be moved and resized with the left mouse button. Clicking the middle\n"
"mouse button selects the entire output.\n",
prog); prog);
} }
@ -206,9 +212,13 @@ int main(int argc, char **argv) {
} }
} }
state.basedir_ctx = sfdo_basedir_ctx_create(); if (state.output_path == NULL && isatty(STDOUT_FILENO)) {
fprintf(stderr,
dp_config_load(&state, config_path); "Refusing to run as the standard output is a terminal and there's no output file\n"
"path specified.\n\n");
help(argv[0]);
exit(1);
}
if (state.output_format == DP_FILE_UNKNOWN) { if (state.output_format == DP_FILE_UNKNOWN) {
if (state.output_path != NULL) { if (state.output_path != NULL) {
@ -223,6 +233,10 @@ int main(int argc, char **argv) {
} }
} }
state.basedir_ctx = sfdo_basedir_ctx_create();
dp_config_load(&state, config_path);
wl_list_init(&state.outputs); wl_list_init(&state.outputs);
wl_list_init(&state.seats); wl_list_init(&state.seats);
@ -264,6 +278,8 @@ int main(int argc, char **argv) {
run(&state); run(&state);
dp_config_finish(&state);
cairo_pattern_destroy(state.border_pattern); cairo_pattern_destroy(state.border_pattern);
xkb_context_unref(state.xkb_context); xkb_context_unref(state.xkb_context);

View File

@ -79,6 +79,12 @@ static void frame_handle_buffer(void *data, struct zwlr_screencopy_frame_v1 *fra
wl_buffer_destroy(output->frame_buffer); wl_buffer_destroy(output->frame_buffer);
} }
if ((int32_t)width != output->width || (int32_t)height != output->height) {
dp_log_fatal("Output/buffer size mismatch: mode=%" PRIi32 "x%" PRIi32 ", buffer=%" PRIu32
"x%" PRIu32,
output->width, output->height, width, height);
}
output->frame_format = format; output->frame_format = format;
output->frame_stride = (int)stride; output->frame_stride = (int)stride;
output->frame_buffer = dp_buffer_create(output->state, output->width, output->height, output->frame_buffer = dp_buffer_create(output->state, output->width, output->height,
@ -126,6 +132,19 @@ static const struct wl_buffer_listener buffer_listener = {
.release = buffer_handle_release, .release = buffer_handle_release,
}; };
static void init_buffer(struct dp_output *output, struct dp_buffer *buffer) {
int stride = output->transformed_width * 4;
buffer->wl_buffer =
dp_buffer_create(output->state, output->transformed_width, output->transformed_height,
stride, WL_SHM_FORMAT_ARGB8888, &buffer->data, &buffer->size);
buffer->cairo_surface = cairo_image_surface_create_for_data(buffer->data, CAIRO_FORMAT_ARGB32,
output->transformed_width, output->transformed_height, stride);
buffer->cairo = cairo_create(buffer->cairo_surface);
wl_buffer_add_listener(buffer->wl_buffer, &buffer_listener, buffer);
}
static void output_handle_geometry(void *data, struct wl_output *wl_output, int32_t x, int32_t y, 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, int32_t phys_width, int32_t phys_height, int32_t subpixel, const char *make,
const char *model, int32_t transform) { const char *model, int32_t transform) {
@ -143,6 +162,10 @@ static void output_handle_geometry(void *data, struct wl_output *wl_output, int3
static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags, static void output_handle_mode(void *data, struct wl_output *wl_output, uint32_t flags,
int32_t width, int32_t height, int32_t refresh) { int32_t width, int32_t height, int32_t refresh) {
if ((flags & WL_OUTPUT_MODE_CURRENT) == 0) {
return;
}
struct dp_output *output = data; struct dp_output *output = data;
if (output->has_geom) { if (output->has_geom) {
@ -165,6 +188,7 @@ static void output_handle_done(void *data, struct wl_output *wl_output) {
assert(output->name != NULL); assert(output->name != NULL);
assert(output->width > 0 && output->height > 0);
if ((output->transform & WL_OUTPUT_TRANSFORM_90) != 0) { if ((output->transform & WL_OUTPUT_TRANSFORM_90) != 0) {
output->transformed_width = output->height; output->transformed_width = output->height;
output->transformed_height = output->width; output->transformed_height = output->width;
@ -208,19 +232,6 @@ static void output_handle_done(void *data, struct wl_output *wl_output) {
wl_subsurface_set_desync(output->select_subsurface); wl_subsurface_set_desync(output->select_subsurface);
wl_surface_set_user_data(output->select_surface, output); 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->cairo_surface = cairo_image_surface_create_for_data(buffer->data,
CAIRO_FORMAT_ARGB32, output->transformed_width, output->transformed_height, stride);
buffer->cairo = cairo_create(buffer->cairo_surface);
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) { static void output_handle_scale(void *data, struct wl_output *wl_output, int32_t scale) {
@ -330,6 +341,8 @@ static void redraw(struct dp_output *output) {
if (buffer == NULL) { if (buffer == NULL) {
dp_log_error("Output %s: no free buffers in a swapchain\n", output->name); dp_log_error("Output %s: no free buffers in a swapchain\n", output->name);
return; return;
} else if (buffer->wl_buffer == NULL) {
init_buffer(output, buffer);
} }
buffer->used = true; buffer->used = true;
@ -371,15 +384,22 @@ static void redraw(struct dp_output *output) {
} }
cairo_stroke(buffer->cairo); cairo_stroke(buffer->cairo);
if (config->border_gradient != DP_BORDER_GRADIENT_NONE &&
config->animation_duration != 0) {
bool whole = selection->x == 0 && selection->y == 0 &&
selection->width == output->effective_width &&
selection->height == output->effective_height;
if (!whole) {
// The border is animated and visible
output->needs_redraw = true;
}
}
} }
cairo_rectangle(buffer->cairo, x, y, width, height); cairo_rectangle(buffer->cairo, x, y, width, height);
set_cairo_color(buffer->cairo, config->selected_color); set_cairo_color(buffer->cairo, config->selected_color);
cairo_fill(buffer->cairo); cairo_fill(buffer->cairo);
if (config->border_gradient != DP_BORDER_GRADIENT_NONE && config->animation_duration != 0) {
output->needs_redraw = true;
}
} }
wl_surface_attach(output->select_surface, buffer->wl_buffer, 0, 0); wl_surface_attach(output->select_surface, buffer->wl_buffer, 0, 0);
@ -431,10 +451,12 @@ void dp_output_destroy(struct dp_output *output) {
for (size_t i = 0; i < DP_SWAPCHAIN_LEN; i++) { for (size_t i = 0; i < DP_SWAPCHAIN_LEN; i++) {
struct dp_buffer *buffer = &output->swapchain[i]; struct dp_buffer *buffer = &output->swapchain[i];
wl_buffer_destroy(buffer->wl_buffer); if (buffer->wl_buffer != NULL) {
cairo_destroy(buffer->cairo); wl_buffer_destroy(buffer->wl_buffer);
cairo_surface_destroy(buffer->cairo_surface); cairo_destroy(buffer->cairo);
munmap(buffer->data, buffer->size); cairo_surface_destroy(buffer->cairo_surface);
munmap(buffer->data, buffer->size);
}
} }
wl_subsurface_destroy(output->select_subsurface); wl_subsurface_destroy(output->select_subsurface);

View File

@ -50,6 +50,15 @@ static void keyboard_handle_leave(
// Ignored // Ignored
} }
static bool match_keybinding(struct dp_keybinding *kb, uint32_t sym) {
for (size_t i = 0; i < kb->n_syms; i++) {
if (sym == kb->syms[i]) {
return true;
}
}
return false;
}
static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, uint32_t serial, 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) { uint32_t time_msec, uint32_t keycode, enum wl_keyboard_key_state key_state) {
struct dp_seat *seat = data; struct dp_seat *seat = data;
@ -62,9 +71,9 @@ static void keyboard_handle_key(void *data, struct wl_keyboard *wl_keyboard, uin
struct dp_state *state = seat->state; struct dp_state *state = seat->state;
struct dp_config *config = &state->config; struct dp_config *config = &state->config;
if (keysym == config->quit_key) { if (match_keybinding(&config->quit_kb, keysym)) {
state->status = DP_STATUS_QUIT; state->status = DP_STATUS_QUIT;
} else if (keysym == config->save_key) { } else if (match_keybinding(&config->save_kb, keysym)) {
state->status = DP_STATUS_SAVED; state->status = DP_STATUS_SAVED;
} }
} }
@ -86,10 +95,10 @@ static const struct wl_keyboard_listener keyboard_listener = {
static enum wp_cursor_shape_device_v1_shape get_cursor_shape(struct dp_selection *selection) { static enum wp_cursor_shape_device_v1_shape get_cursor_shape(struct dp_selection *selection) {
switch (selection->action) { switch (selection->action) {
case DP_SELECTION_ACTION_NONE: case DP_SELECTION_ACTION_NONE:
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR; break;
case DP_SELECTION_ACTION_RESIZING: case DP_SELECTION_ACTION_RESIZING:
if (selection->width == 0 || selection->height == 0) { if (selection->width == 0 || selection->height == 0) {
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR; break;
} }
switch (selection->resize_edges) { switch (selection->resize_edges) {
case DP_EDGE_TOP: case DP_EDGE_TOP:
@ -116,24 +125,27 @@ static enum wp_cursor_shape_device_v1_shape get_cursor_shape(struct dp_selection
selection->width == selection->output->effective_width && selection->width == selection->output->effective_width &&
selection->height == selection->output->effective_height) { selection->height == selection->output->effective_height) {
// Moving is impossible // Moving is impossible
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT; break;
} }
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE; return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_MOVE;
} }
abort(); // Unreachable // The default cursor
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_CROSSHAIR;
} }
static void update_cursor(struct dp_seat *seat, uint32_t serial) { static void update_cursor(struct dp_seat *seat) {
wp_cursor_shape_device_v1_set_shape( if (seat->cursor_shape_device != NULL) {
seat->cursor_shape_device, serial, get_cursor_shape(&seat->state->selection)); wp_cursor_shape_device_v1_set_shape(seat->cursor_shape_device, seat->pointer_serial,
get_cursor_shape(&seat->state->selection));
}
} }
static void process_position(struct dp_seat *seat, wl_fixed_t x, wl_fixed_t y, uint32_t serial) { static void process_position(struct dp_seat *seat, wl_fixed_t x, wl_fixed_t y) {
struct dp_output *output = seat->ptr_output; struct dp_output *output = seat->ptr_output;
seat->ptr_x = wl_fixed_to_double(x); seat->ptr_x = wl_fixed_to_double(x);
seat->ptr_y = wl_fixed_to_double(y); seat->ptr_y = wl_fixed_to_double(y);
dp_select_notify_pointer_position(&seat->state->selection, output, seat->ptr_x, seat->ptr_y); dp_select_notify_pointer_position(&seat->state->selection, output, seat->ptr_x, seat->ptr_y);
update_cursor(seat, serial); update_cursor(seat);
} }
static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial, static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
@ -141,22 +153,24 @@ static void pointer_handle_enter(void *data, struct wl_pointer *wl_pointer, uint
struct dp_seat *seat = data; struct dp_seat *seat = data;
seat->ptr_output = wl_surface_get_user_data(surface); seat->ptr_output = wl_surface_get_user_data(surface);
assert(seat->ptr_output != NULL); assert(seat->ptr_output != NULL);
process_position(seat, sx, sy, serial); seat->pointer_serial = serial;
process_position(seat, sx, sy);
} }
static void pointer_handle_leave( static void pointer_handle_leave(
void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) { void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface) {
struct dp_seat *seat = data; struct dp_seat *seat = data;
seat->ptr_output = NULL; seat->ptr_output = NULL;
seat->pointer_serial = serial;
} }
static void pointer_handle_motion( static void pointer_handle_motion(void *data, struct wl_pointer *wl_pointer, uint32_t time_msec,
void *data, struct wl_pointer *wl_pointer, uint32_t serial, wl_fixed_t sx, wl_fixed_t sy) { wl_fixed_t sx, wl_fixed_t sy) {
struct dp_seat *seat = data; struct dp_seat *seat = data;
if (seat->ptr_output == NULL) { if (seat->ptr_output == NULL) {
return; // Shouldn't happen return; // Shouldn't happen
} }
process_position(seat, sx, sy, serial); process_position(seat, sx, sy);
} }
static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial, static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uint32_t serial,
@ -164,6 +178,8 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uin
struct dp_seat *seat = data; struct dp_seat *seat = data;
struct dp_state *state = seat->state; struct dp_state *state = seat->state;
seat->pointer_serial = serial;
struct dp_selection *selection = &state->selection; struct dp_selection *selection = &state->selection;
if (button_state != WL_POINTER_BUTTON_STATE_PRESSED) { if (button_state != WL_POINTER_BUTTON_STATE_PRESSED) {
if (selection->width > 0 && selection->height > 0 && state->config.quick_select) { if (selection->width > 0 && selection->height > 0 && state->config.quick_select) {
@ -182,14 +198,14 @@ static void pointer_handle_button(void *data, struct wl_pointer *wl_pointer, uin
case BTN_RIGHT: case BTN_RIGHT:
dp_select_start_interactive( dp_select_start_interactive(
selection, seat->ptr_output, seat->ptr_x, seat->ptr_y, button == BTN_LEFT); selection, seat->ptr_output, seat->ptr_x, seat->ptr_y, button == BTN_LEFT);
update_cursor(seat, serial); update_cursor(seat);
break; break;
case BTN_MIDDLE: case BTN_MIDDLE:
dp_select_whole(selection, seat->ptr_output); dp_select_whole(selection, seat->ptr_output);
// dp_select_whole() doesn't invalidate the interactive state, so do it manually // dp_select_whole() doesn't invalidate the interactive state, so do it manually
dp_select_notify_pointer_position(selection, seat->ptr_output, seat->ptr_x, seat->ptr_y); dp_select_notify_pointer_position(selection, seat->ptr_output, seat->ptr_x, seat->ptr_y);
update_cursor(seat, serial); update_cursor(seat);
if (state->config.quick_select) { if (state->config.quick_select) {
state->status = DP_STATUS_SAVED; state->status = DP_STATUS_SAVED;
@ -220,8 +236,11 @@ static void seat_handle_capabilities(void *data, struct wl_seat *wl_seat, uint32
if ((caps & WL_SEAT_CAPABILITY_POINTER) != 0 && seat->pointer == NULL) { if ((caps & WL_SEAT_CAPABILITY_POINTER) != 0 && seat->pointer == NULL) {
seat->pointer = wl_seat_get_pointer(wl_seat); seat->pointer = wl_seat_get_pointer(wl_seat);
wl_pointer_add_listener(seat->pointer, &pointer_listener, seat); wl_pointer_add_listener(seat->pointer, &pointer_listener, seat);
seat->cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(
seat->state->cursor_shape_manager, seat->pointer); if (seat->state->cursor_shape_manager != NULL) {
seat->cursor_shape_device = wp_cursor_shape_manager_v1_get_pointer(
seat->state->cursor_shape_manager, seat->pointer);
}
} }
} }
@ -256,6 +275,8 @@ void dp_seat_destroy(struct dp_seat *seat) {
} }
if (seat->pointer != NULL) { if (seat->pointer != NULL) {
wl_pointer_release(seat->pointer); wl_pointer_release(seat->pointer);
}
if (seat->cursor_shape_device != NULL) {
wp_cursor_shape_device_v1_destroy(seat->cursor_shape_device); wp_cursor_shape_device_v1_destroy(seat->cursor_shape_device);
} }

View File

@ -187,6 +187,8 @@ void dp_select_start_interactive(struct dp_selection *selection, struct dp_outpu
selection->resize_edges = DP_EDGE_BOTTOM | DP_EDGE_RIGHT; selection->resize_edges = DP_EDGE_BOTTOM | DP_EDGE_RIGHT;
init_resize(selection, x, y); init_resize(selection, x, y);
dp_output_redraw(output);
} }
void dp_select_stop_interactive(struct dp_selection *selection) { void dp_select_stop_interactive(struct dp_selection *selection) {

View File

@ -28,6 +28,10 @@ void dp_log_fatal(const char *fmt, ...) {
} }
void *dp_zalloc(size_t size) { void *dp_zalloc(size_t size) {
if (size == 0) {
return NULL;
}
void *ptr = calloc(1, size); void *ptr = calloc(1, size);
if (ptr == NULL) { if (ptr == NULL) {
dp_log_fatal("Failed to allocate %zu bytes", size); dp_log_fatal("Failed to allocate %zu bytes", size);