Skip to content
Merged
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
12 changes: 10 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,25 @@ jobs:
container:
image: archlinux:base-devel
steps:
- uses: actions/checkout@v4

- name: Install formatter
run: |
pacman -Syu --noconfirm --needed \
clang \
cloc \
git \
make

- uses: actions/checkout@v4

- name: Check formatting
run: make format-check

- name: Check LoC update
run: |
cp README.md /tmp/README.before
make update-loc
cmp -s README.md /tmp/README.before

build:
runs-on: ubuntu-latest
container:
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
wlroots-based Wayland compositor with virtual outputs and physical cursor continuity.
Originally forked from dwl.

`LOC: 7332 total, 2815 vwl.c`
`LOC: 7441 total, 2878 vwl.c`

## Features

Expand Down
26 changes: 26 additions & 0 deletions contrib/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Contrib

Helper scripts and integration snippets that are useful with `vwl` but are not required by the compositor itself.

## Waybar

- `waybar/vwl-reveal`: auto hide/reveal helper driven by `vwlctl subscribe`.

Install:

```sh
install -Dm755 contrib/waybar/vwl-reveal ~/.config/waybar/scripts/vwl-reveal
```

Run it from your session startup (for example `~/.config/vwl/run`) after starting Waybar:

```sh
waybar &
sh ~/.config/waybar/scripts/vwl-reveal >/dev/null 2>&1 &
```

Waybar config requirements:

- `"mode": "hide"`
- `"start_hidden": true`

85 changes: 85 additions & 0 deletions contrib/waybar/vwl-reveal
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/bin/sh

# Expects Waybar to start hidden; toggles visibility on reveal-edge state changes.
visible=0
pidfile="${XDG_RUNTIME_DIR:-/tmp}/vwl-waybar-reveal.pid"
waybar_config="${WAYBAR_CONFIG:-$HOME/.config/waybar/config.jsonc}"
reveal_edge="${VWL_REVEAL_EDGE:-}"

if [ -f "$pidfile" ]; then
oldpid=$(cat "$pidfile" 2>/dev/null)
if [ -n "$oldpid" ] && kill -0 "$oldpid" 2>/dev/null; then
if ps -p "$oldpid" -o args= 2>/dev/null | grep -q "vwl-reveal"; then
exit 0
fi
fi
fi
echo "$$" > "$pidfile"
cleanup() {
rm -f "$pidfile"
}
trap cleanup EXIT INT TERM

if command -v vwlctl >/dev/null 2>&1; then
VWLCTL=vwlctl
elif [ -x /var/home/wegel/work/perso/vwl/vwlctl ]; then
VWLCTL=/var/home/wegel/work/perso/vwl/vwlctl
else
exit 0
fi

if ! command -v jq >/dev/null 2>&1; then
exit 0
fi

if [ -z "$reveal_edge" ] && [ -r "$waybar_config" ]; then
reveal_edge=$(sed -n 's/^[[:space:]]*"position"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$waybar_config" | head -n1)
fi
case "$reveal_edge" in
top|bottom|left|right)
;;
*)
reveal_edge=top
;;
esac

"$VWLCTL" subscribe \
| jq --unbuffered -r '
.state.pointer as $p
| if ($p.reveal_edge? != null) then
("edge:" + $p.reveal_edge)
elif ($p.reveal_hover? != null) then
("hover:" + ($p.reveal_hover | tostring))
else
empty
end
' \
| while read -r state; do
case "$state" in
edge:*)
edge=${state#edge:}
if [ "$edge" = "$reveal_edge" ]; then
target=1
else
target=0
fi
;;
hover:true)
target=1
;;
hover:false)
target=0
;;
*)
continue
;;
esac

if [ "$target" -eq "$visible" ]; then
continue
fi

pkill -USR1 -x waybar >/dev/null 2>&1 || true
visible="$target"
done

7 changes: 7 additions & 0 deletions docs/ipc.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,17 @@ Errors use:

The snapshot/event state is structured around:

- `pointer`: pointer location metadata for subscribers (for example reveal-hover state)
- `outputs`: physical outputs, geometry, active/focused state, active window info
- `virtual_outputs`: vout ids, names, workspace mapping, layout, regions
- `workspaces`: flat workspace list with visibility/focus/assignment metadata

`pointer` currently includes:

- `output`: output name under the pointer (or `null`)
- `reveal_hover`: compositor-provided hover state intended for bar/panel reveal automation
- `reveal_edge`: `top`, `bottom`, `left`, `right`, or `null`

Virtual outputs currently expose a single region, but the schema uses `regions[]` so it can represent spanning virtual outputs later without a schema break.

## CLI
Expand Down
6 changes: 6 additions & 0 deletions docs/waybar-howto.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ The IPC already exposes the data a bar needs:

- focused output
- active window title/appid
- pointer reveal-hover state
- pointer reveal edge (`top`/`bottom`/`left`/`right`)
- physical outputs
- virtual outputs
- workspace to virtual-output mapping

## Auto Hide/Reveal

Use the helper at `contrib/waybar/vwl-reveal` for edge-hover hide/reveal integration with Waybar.

## Requirements

- `vwlctl` in `$PATH`
Expand Down
37 changes: 37 additions & 0 deletions ipc.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include <sys/un.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_cursor.h>
#include <wlr/types/wlr_fractional_scale_v1.h>
#include <wlr/types/wlr_output_management_v1.h>

Expand Down Expand Up @@ -192,6 +193,23 @@ json_write_box(FILE *fp, const struct wlr_box *box)
fprintf(fp, "{\"x\":%d,\"y\":%d,\"width\":%d,\"height\":%d}", box->x, box->y, box->width, box->height);
}

static const char *
pointer_reveal_edge_name(int edge)
{
switch (edge) {
case POINTER_REVEAL_EDGE_TOP:
return "top";
case POINTER_REVEAL_EDGE_BOTTOM:
return "bottom";
case POINTER_REVEAL_EDGE_LEFT:
return "left";
case POINTER_REVEAL_EDGE_RIGHT:
return "right";
default:
return NULL;
}
}

static void
workspace_stats(Workspace *ws, unsigned int *count, int *urgent)
{
Expand All @@ -215,6 +233,7 @@ build_snapshot(void)
size_t size = 0;
FILE *fp = open_memstream(&buf, &size);
Monitor *m;
Monitor *pointer_mon;
VirtualOutput *vout;
int i;

Expand All @@ -241,6 +260,24 @@ build_snapshot(void)
else
fputs("null", fp);

pointer_mon = cursor ? xytomon(cursor->x, cursor->y) : NULL;
fputs(",\"pointer\":{", fp);
fputs("\"output\":", fp);
if (pointer_mon && pointer_mon->wlr_output)
json_write_escaped(fp, pointer_mon->wlr_output->name);
else
fputs("null", fp);
fprintf(fp, ",\"reveal_hover\":%s", ipc_pointer_reveal_hover ? "true" : "false");
fputs(",\"reveal_edge\":", fp);
{
const char *edge_name = pointer_reveal_edge_name(ipc_pointer_reveal_edge);
if (edge_name)
json_write_escaped(fp, edge_name);
else
fputs("null", fp);
}
fputc('}', fp);

fputs(",\"outputs\":[", fp);
{
bool first = true;
Expand Down
2 changes: 2 additions & 0 deletions plumbing.c
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ Workspace workspaces[WORKSPACE_COUNT];
Workspace *selws;
VirtualOutput *selvout;
HoverState hover;
int ipc_pointer_reveal_hover;
int ipc_pointer_reveal_edge;
KeyboardGroup *kb_group;
unsigned int cursor_mode;
Client *grabc;
Expand Down
77 changes: 76 additions & 1 deletion vwl.c
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ static void wsload(VirtualOutput *vout, Workspace *ws);
static Client *focustopvout(VirtualOutput *vout);
static Client *focustoptiledvout(VirtualOutput *vout);
static void cursorwarptovout(VirtualOutput *vout);
static int pointer_reveal_edge_for_cursor(Monitor *m, int current_edge);
static void update_pointer_reveal_state(void);
/* removed unused voname function */

/* variables */
Expand All @@ -243,6 +245,8 @@ static struct wlr_session_lock_manager_v1 *session_lock_mgr;

static struct wlr_box sgeom;
static bool vt_recovery_mode = false; /* Track if we're recovering from VT switch */
static const int pointer_reveal_trigger_px = 2;
static const int pointer_reveal_hold_px = 40;

/* Global event handlers are now in plumbing.c */
extern struct wl_listener cursor_axis;
Expand Down Expand Up @@ -1545,6 +1549,71 @@ maximizenotify(struct wl_listener *listener, void *data)
wlr_xdg_surface_schedule_configure(c->surface.xdg);
}

static int
pointer_reveal_edge_for_cursor(Monitor *m, int current_edge)
{
double rel_x, rel_y;
double distances[4];
int edges[4];
double best = DBL_MAX;
int best_edge = POINTER_REVEAL_EDGE_NONE;
int i;

if (!m || !cursor || m->monitor_area.width <= 0 || m->monitor_area.height <= 0)
return POINTER_REVEAL_EDGE_NONE;

rel_x = cursor->x - m->monitor_area.x;
rel_y = cursor->y - m->monitor_area.y;
if (rel_x < 0.0 || rel_y < 0.0 || rel_x > m->monitor_area.width || rel_y > m->monitor_area.height)
return POINTER_REVEAL_EDGE_NONE;

distances[0] = rel_y;
distances[1] = m->monitor_area.height - rel_y;
distances[2] = rel_x;
distances[3] = m->monitor_area.width - rel_x;

edges[0] = POINTER_REVEAL_EDGE_TOP;
edges[1] = POINTER_REVEAL_EDGE_BOTTOM;
edges[2] = POINTER_REVEAL_EDGE_LEFT;
edges[3] = POINTER_REVEAL_EDGE_RIGHT;

for (i = 0; i < 4; i++) {
int edge = edges[i];
double threshold = (edge == current_edge) ? pointer_reveal_hold_px : pointer_reveal_trigger_px;
double d = distances[i];

if (d < 0.0 || d > threshold)
continue;
if (d < best || (d == best && edge == current_edge)) {
best = d;
best_edge = edge;
}
}

return best_edge;
}

static void
update_pointer_reveal_state(void)
{
Monitor *m;
int next_edge = POINTER_REVEAL_EDGE_NONE;
int next_hover = 0;

if (cursor) {
m = xytomon(cursor->x, cursor->y);
next_edge = pointer_reveal_edge_for_cursor(m, ipc_pointer_reveal_edge);
}
next_hover = next_edge != POINTER_REVEAL_EDGE_NONE;

if (next_hover == ipc_pointer_reveal_hover && next_edge == ipc_pointer_reveal_edge)
return;

ipc_pointer_reveal_hover = next_hover;
ipc_pointer_reveal_edge = next_edge;
updateipc();
}

void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel)
{
Expand Down Expand Up @@ -1586,6 +1655,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
if (enable_physical_cursor_gap_jumps && prev_mon && cursor_mode == CurNormal && !seat->drag &&
(!active_constraint || active_constraint->type != WLR_POINTER_CONSTRAINT_V1_LOCKED) &&
cursorgap(prev_mon, prev_mm_x, prev_mm_y)) {
update_pointer_reveal_state();
return;
}

Expand All @@ -1602,8 +1672,10 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
dy = sy_confined - sy;
}

if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED)
if (active_constraint->type == WLR_POINTER_CONSTRAINT_V1_LOCKED) {
update_pointer_reveal_state();
return;
}
}
}

Expand Down Expand Up @@ -1645,6 +1717,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
.height = grabc->geom.height},
1);
cursorsync();
update_pointer_reveal_state();
return;
} else if (cursor_mode == CurResize) {
resize(grabc,
Expand All @@ -1654,6 +1727,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
.height = (int)round(cursor->y) - grabc->geom.y},
1);
cursorsync();
update_pointer_reveal_state();
return;
}

Expand All @@ -1665,6 +1739,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d

pointerfocus(c, surface, sx, sy, time);
cursorsync();
update_pointer_reveal_state();
}

void
Expand Down
Loading
Loading