Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ To enable to select a target and click, it has four different modes:
- [`tile`](#tile-mode) — which uses a grid to select areas,
- [`bisect`](#bisect-mode) — which enables to bisect an area,
- [`split`](#split-mode) — which enables to successively split an area,
- and [`click`](#click-mode) — which triggers a click in the middle of an area.
- [`click`](#click-mode) — which triggers a click in the middle of an area.

These are set with the `modes` configuration field and can be chained, e.g. `wl-kbptr -o modes=tile,bisect`.

Expand Down Expand Up @@ -52,6 +52,23 @@ Just like the `bisect` mode, a left, right and middle click can be made by press

The `click` mode simply triggers a click in the middle of the selection area.

## Drag flag

The `--drag` flag (or `-d`) changes the action from a single click to a click-and-drag between two selected positions. It is designed to be used with any mode chain and requires **two selections**:

1. **Start**: the first selection's left-center is stored as the drag start point. A marker is drawn at that position and the selection resets so you can pick again.
2. **End**: the second selection's right-center becomes the drag end point. The pointer then presses and holds the left button at the start, moves to the end, and releases.

This makes it useful for selecting text (start at word/line beginning, end at word/line end) or dragging UI elements.

```
wl-kbptr -o modes=tile,click --drag
wl-kbptr -o modes=floating,click --drag
wl-kbptr -o modes=bisect,click --drag
wl-kbptr -o modes=split,click --drag
wl-kbptr -o modes=tile,bisect,click --drag
```

## Supported compositors

For `wl-kbptr` to work, it requires the following protocols:
Expand Down
6 changes: 6 additions & 0 deletions config.example
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,9 @@ history_border_color=#3339

[mode_click]
button=left

[mode_drag]
start_marker_color=#fd0d
start_marker_size=8
# Shapes: circle, caret, rectangle
start_marker_shape=circle
27 changes: 27 additions & 0 deletions src/config.c
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,25 @@ static int parse_click(void *dest, char *value) {
return 0;
}

static int parse_drag_marker_shape(void *dest, char *value) {
enum drag_marker_shape *out = dest;
if (strcmp(value, "circle") == 0) {
*out = DRAG_MARKER_CIRCLE;
} else if (strcmp(value, "caret") == 0) {
*out = DRAG_MARKER_CARET;
} else if (strcmp(value, "rectangle") == 0) {
*out = DRAG_MARKER_RECTANGLE;
} else {
LOG_ERR(
"Invalid drag marker shape '%s'. Should be 'circle', 'caret' or 'rectangle'.",
value
);
return 1;
}

return 0;
}

static void free_home_row_keys(void *field_value) {
char ***home_row_keys_ptr = field_value;
if (*home_row_keys_ptr == NULL) {
Expand Down Expand Up @@ -352,6 +371,8 @@ struct section_def {
FIELD(struct mode_split_config, name, default_value, parse, free)
#define MC_FIELD(name, default_value, parse, free) \
FIELD(struct mode_click_config, name, default_value, parse, free)
#define MD_FIELD(name, default_value, parse, free) \
FIELD(struct mode_drag_config, name, default_value, parse, free)

static void noop() {}

Expand Down Expand Up @@ -415,6 +436,12 @@ static struct section_def section_defs[] = {
MS_FIELD(history_border_color, "#3339", parse_color, noop)
),
SECTION(mode_click, MC_FIELD(button, "left", parse_click, noop)),
SECTION(
mode_drag,
MD_FIELD(start_marker_color, "#f50d", parse_color, noop),
MD_FIELD(start_marker_size, "8", parse_double, noop),
MD_FIELD(start_marker_shape, "caret", parse_drag_marker_shape, noop)
),
};
#pragma GCC diagnostic pop

Expand Down
13 changes: 13 additions & 0 deletions src/config.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,26 @@ struct mode_click_config {
enum click button;
};

enum drag_marker_shape {
DRAG_MARKER_CIRCLE,
DRAG_MARKER_CARET,
DRAG_MARKER_RECTANGLE,
};

struct mode_drag_config {
uint32_t start_marker_color;
double start_marker_size;
enum drag_marker_shape start_marker_shape;
};

struct config {
struct general_config general;
struct mode_tile_config mode_tile;
struct mode_floating_config mode_floating;
struct mode_bisect_config mode_bisect;
struct mode_split_config mode_split;
struct mode_click_config mode_click;
struct mode_drag_config mode_drag;
};

/**
Expand Down
34 changes: 27 additions & 7 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -623,6 +623,9 @@ static void print_result(struct state *state) {
case CLICK_RIGHT_BTN:
click = 'r';
break;
case CLICK_DRAG:
click = 'd';
break;
case CLICK_NONE:
click = 'n';
}
Expand All @@ -633,7 +636,6 @@ static void print_result(struct state *state) {
state->current_output->y, click
);
}

static void print_usage() {
puts("wl-kbptr [OPTION...]\n");

Expand All @@ -645,6 +647,7 @@ static void print_usage() {
puts(" -o, --option set configuration option");
puts(" -O, --output specify display output to use");
puts(" -p, --only-print only print, don't move the cursor or click");
puts(" -d, --drag perform a click-and-drag between two selections");
}

static void print_version() {
Expand Down Expand Up @@ -676,7 +679,11 @@ int main(int argc, char **argv) {
.result = (struct rect){-1, -1, -1, -1},
.initial_area = (struct rect){-1, -1, -1, -1},
.home_row = (char *[]){"", "", "", "", "", "", "", "", "", "", ""},
.click = CLICK_NONE,
.click = CLICK_NONE,
.drag = false,
.drag_start_x = 0,
.drag_start_y = 0,
.drag_phase = 0,
};

config_set_default(&state.config);
Expand All @@ -691,6 +698,7 @@ int main(int argc, char **argv) {
{"config", required_argument, 0, 'c'},
{"output", required_argument, 0, 'O'},
{"only-print", no_argument, 0, 'p'},
{"drag", no_argument, 0, 'd'},
{NULL, 0, NULL, 0}
};

Expand All @@ -703,7 +711,7 @@ int main(int argc, char **argv) {
char *selected_output_name = NULL;
bool only_print = false;
while ((option_char = getopt_long(
argc, argv, "hvr:o:c:O:Rp", long_options, &option_index
argc, argv, "hvr:o:c:O:Rpd", long_options, &option_index
)) != -1) {
switch (option_char) {
case 'h':
Expand Down Expand Up @@ -753,6 +761,10 @@ int main(int argc, char **argv) {
only_print = true;
break;

case 'd':
state.drag = true;
break;

default:
LOG_ERR("Unknown argument.");
config_free_values(&state.config);
Expand Down Expand Up @@ -922,10 +934,18 @@ int main(int argc, char **argv) {
if (state.result.x != -1) {
print_result(&state);
if (!only_print) {
move_pointer(
&state, state.result.x + state.result.w / 2,
state.result.y + state.result.h / 2, state.click
);
if (state.click == CLICK_DRAG) {
drag_pointer(
&state, state.drag_start_x, state.drag_start_y,
state.result.x + state.result.w / 2,
state.result.y + state.result.h / 2
);
} else {
move_pointer(
&state, state.result.x + state.result.w / 2,
state.result.y + state.result.h / 2, state.click
);
}
}
} else {
status_code = state.config.general.cancellation_status_code;
Expand Down
82 changes: 80 additions & 2 deletions src/mode.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#include "mode.h"

#include "log.h"
#include "utils_cairo.h"

#include <math.h>
#include <stdlib.h>
#include <string.h>

Expand Down Expand Up @@ -64,6 +66,14 @@ int load_modes(struct state *state, char *modes) {
return 0;
}

static bool chain_includes_floating(struct state *state) {
for (int i = 0; i < MAX_NUM_MODES; i++) {
if (state->mode_interfaces[i] == NULL) break;
if (strcmp(state->mode_interfaces[i]->name, "floating") == 0) return true;
}
return false;
}

void enter_next_mode(struct state *state, struct rect area) {
if (has_last_mode_returned(state)) {
return;
Expand All @@ -72,7 +82,31 @@ void enter_next_mode(struct state *state, struct rect area) {
state->current_mode += 1;

if (has_last_mode_returned(state)) {
memcpy(&state->result, &area, sizeof(struct rect));
if (state->drag && state->drag_phase == 0) {
bool floating = chain_includes_floating(state);
state->drag_start_x = floating ? area.x : area.x + area.w / 2;
state->drag_start_y = area.y + area.h / 2;
state->drag_phase = 1;
for (int i = 0; i < state->current_mode; i++) {
state->mode_interfaces[i]->restart(
state, state->mode_states[i]
);
}
state->current_mode = 0;
} else if (state->drag && state->drag_phase == 1) {
bool floating = chain_includes_floating(state);
state->drag_phase = 0;
state->click = CLICK_DRAG;
struct rect end_point = {
.x = floating ? area.x + area.w - 1 : area.x + area.w / 2,
.y = area.y + area.h / 2,
.w = 1,
.h = 1,
};
memcpy(&state->result, &end_point, sizeof(struct rect));
} else {
memcpy(&state->result, &area, sizeof(struct rect));
}
return;
}

Expand Down Expand Up @@ -132,7 +166,51 @@ void mode_render(struct state *state, cairo_t *cairo) {
return;
}

return state->mode_interfaces[state->current_mode]->render(
state->mode_interfaces[state->current_mode]->render(
state, state->mode_states[state->current_mode], cairo
);

if (state->drag_phase == 1) {
double s = state->config.mode_drag.start_marker_size;
enum drag_marker_shape shape = state->config.mode_drag.start_marker_shape;
double x = state->drag_start_x;
double y = state->drag_start_y;

cairo_set_operator(cairo, CAIRO_OPERATOR_OVER);
cairo_set_source_u32(cairo, state->config.mode_drag.start_marker_color);

if (shape == DRAG_MARKER_CIRCLE) {
cairo_arc(cairo, x, y, s, 0, 2 * M_PI);
cairo_fill(cairo);
} else if (shape == DRAG_MARKER_RECTANGLE) {
// Rectangle: plain vertical bar
double bar_w = s / 4.0 < 1.5 ? 1.5 : s / 4.0;
double bar_h = s * 2.0;
cairo_set_line_width(cairo, bar_w);
cairo_set_line_cap(cairo, CAIRO_LINE_CAP_SQUARE);
cairo_move_to(cairo, x, y - bar_h / 2);
cairo_line_to(cairo, x, y + bar_h / 2);
cairo_stroke(cairo);
} else {
// Caret: a vertical bar with serifs, like a text insertion cursor.
// Bar: width = s/4 (min 1.5), height = s*2, centred on (x, y).
double bar_w = s / 4.0 < 1.5 ? 1.5 : s / 4.0;
double bar_h = s * 2.0;
double serif_w = s * 0.75;
cairo_set_line_width(cairo, bar_w);
cairo_set_line_cap(cairo, CAIRO_LINE_CAP_SQUARE);
// Vertical stroke
cairo_move_to(cairo, x, y - bar_h / 2);
cairo_line_to(cairo, x, y + bar_h / 2);
cairo_stroke(cairo);
// Top serif
cairo_move_to(cairo, x - serif_w / 2, y - bar_h / 2);
cairo_line_to(cairo, x + serif_w / 2, y - bar_h / 2);
cairo_stroke(cairo);
// Bottom serif
cairo_move_to(cairo, x - serif_w / 2, y + bar_h / 2);
cairo_line_to(cairo, x + serif_w / 2, y + bar_h / 2);
cairo_stroke(cairo);
}
}
}
4 changes: 4 additions & 0 deletions src/mode.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ struct mode_interface {
bool (*key)(struct state *, void *mode_state, xkb_keysym_t, char *text);
void (*render)(struct state *, void *mode_state, cairo_t *);
void (*free)(void *mode_state);
// Called when the selection is restarted for a second drag pick.
// Should reset the mode to its initial state without re-doing setup
// (e.g., clear label selections but keep loaded areas).
void (*restart)(struct state *, void *mode_state);
};

extern struct mode_interface *mode_interfaces[];
Expand Down
7 changes: 7 additions & 0 deletions src/mode_bisect.c
Original file line number Diff line number Diff line change
Expand Up @@ -515,11 +515,18 @@ void bisect_mode_free(void *mode_state) {
free(ms);
}

static void bisect_mode_restart(struct state *state, void *mode_state) {
struct bisect_mode_state *ms = mode_state;
ms->current = 0;
bisect_mode_move_pointer(state, ms);
}

struct mode_interface bisect_mode_interface = {
.name = "bisect",
.enter = bisect_mode_enter,
.reenter = bisect_mode_reenter,
.key = bisect_mode_key,
.render = bisect_mode_render,
.free = bisect_mode_free,
.restart = bisect_mode_restart,
};
3 changes: 3 additions & 0 deletions src/mode_click.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,14 @@ click_mode_render(struct state *state, void *mode_state, cairo_t *cairo) {}

static void click_mode_free(void *mode_state) {}

static void click_mode_restart(struct state *state, void *mode_state) {}

struct mode_interface click_mode_interface = {
.name = "click",
.enter = click_mode_enter,
.reenter = click_mode_reenter,
.key = click_mode_key,
.render = click_mode_render,
.free = click_mode_free,
.restart = click_mode_restart,
};
6 changes: 6 additions & 0 deletions src/mode_floating.c
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,17 @@ void floating_mode_free(void *mode_state) {
free(ms);
}

static void floating_mode_restart(struct state *state, void *mode_state) {
struct floating_mode_state *ms = mode_state;
label_selection_clear(ms->label_selection);
}

struct mode_interface floating_mode_interface = {
.name = "floating",
.enter = floating_mode_enter,
.reenter = floating_mode_reenter,
.key = floating_mode_key,
.render = floating_mode_render,
.free = floating_mode_free,
.restart = floating_mode_restart,
};
Loading