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
46 changes: 46 additions & 0 deletions build/guest-tests/gui-check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
# GT CONSENT-CODE 1 NN type NN and Enter (the question's code)
# GT CONSENT-WAIT 2 type y and Enter (not the code: refused)
# GT POINTER-ZONE0, GT POINTER-UNTRUSTED move the pointer onto the newest window
# GT TOUCH-HOVER-ZONE0 move the pointer right, onto zone 0's window
# GT TOUCH-UNTRUSTED tap the screen's left half (the newest window)
# GT SCREENSHOT-POPUP take a screenshot
# GT KEY-MENU-POPUP press Alt+p (the chrome menu)
# GT KEY-FOCUS-POPUP press Alt+j
Expand Down Expand Up @@ -1184,6 +1186,50 @@ if since_mark cursor untrusted | grep -q 'asked for the text cursor by name' &&
else
fail "zone-cursor-by-name" "$(since_mark cursor untrusted | tail -4 | tr '\n' ' ')"
fi
# --- a touchscreen drives the pointer -------------------------------------------------------
# dwl takes a touchscreen as the pointer, so a tap is a click: it gives a window the keyboard as
# a click does, and zones are offered no touch. The VM's touchscreen is QEMU's virtio-multitouch
# (run-ovmf.sh --touch). untrusted's window maps beside zone 0's, unasked, where dwl puts the
# newest: the screen's left half, which the host taps.
stop_zone untrusted
as_user "/usr/libexec/kryptik/wlprobe cursor 40" > "$LOG/touch-zone0.out" 2>&1 &
touch0=$!
wait_for 20 grep -q committed "$LOG/touch-zone0.out"
wait_for 10 grep -qx 'zone=0' "$RT/kryptik/focus"
mark touch untrusted
launch_plain untrusted "/usr/libexec/kryptik/wlprobe cursor 30" > "$LOG/launch-touch.out" 2>&1
wait_for 20 probe_committed touch
sleep 2
# The pointer rests on zone 0's window, the right half, when the tap comes: the press must
# reach the window tapped, not the one under the pointer. zone 0's probe says each entry.
entered="$(grep -c 'set a ' "$LOG/touch-zone0.out")"
zone0_entered() { [[ "$(grep -c 'set a ' "$LOG/touch-zone0.out")" -gt "$entered" ]]; }
echo "GT TOUCH-HOVER-ZONE0"
wait_for 10 zone0_entered
hovered="$(grep -c 'set a ' "$LOG/touch-zone0.out")"
touch_before="$(grep '^zone=' "$RT/kryptik/focus" 2>/dev/null)"
echo "GT TOUCH-UNTRUSTED"
if ! grep -q 'Name="QEMU Virtio MultiTouch"' /proc/bus/input/devices; then
fail "touch-picks-window" "no touchscreen in this guest: $(grep '^N: ' /proc/bus/input/devices | tr '\n' ' ')"
elif [[ "$touch_before" != "zone=0" ]]; then
fail "touch-picks-window" "zone 0's window did not have the keyboard before the tap: ${touch_before:-no focus record}"
elif wait_for 10 grep -qx 'zone=untrusted' "$RT/kryptik/focus"; then
pass "touch-picks-window" "a tap on untrusted's window gave it the keyboard, as a click does"
else
fail "touch-picks-window" "after the tap: $(tr '\n' ' ' < "$RT/kryptik/focus" 2>/dev/null); $(since_mark touch untrusted | tail -2 | tr '\n' ' ')"
fi
touch_pressed() { since_mark touch untrusted | grep -q 'pointer button 272 released'; }
if [[ "$hovered" -le "$entered" ]]; then
fail "touch-presses-window" "the pointer never came onto zone 0's window before the tap, so nothing was shown: $(tail -3 "$LOG/touch-zone0.out" | tr '\n' ' ')"
elif ! wait_for 10 touch_pressed; then
fail "touch-presses-window" "untrusted's window was sent no press and release: $(since_mark touch untrusted | tail -3 | tr '\n' ' '); zone 0's: $(grep 'pointer button' "$LOG/touch-zone0.out" | tr '\n' ' ')"
elif grep -q 'pointer button' "$LOG/touch-zone0.out"; then
fail "touch-presses-window" "the tap's button also went to zone 0's window, where the pointer rested: $(grep 'pointer button' "$LOG/touch-zone0.out" | tr '\n' ' ')"
else
pass "touch-presses-window" "the tap's press and release went to untrusted's window, and none to zone 0's, under the pointer before"
fi
pkill -u "$USER_NAME" -f 'wlprobe cursor 40' 2>/dev/null; wait "$touch0" 2>/dev/null

# --- a zone's window hears the scale it is shown at ------------------------------------------
# wp_fractional_scale_v1 through the proxy: the compositor tells each window its output's scale,
# in 120ths, so a browser draws text sharp at 1.5 instead of being scaled down from 2. This
Expand Down
5 changes: 4 additions & 1 deletion docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,10 @@ The menu shows the zone's glyph and label and the window's title, which the
zone's proxy prefixes with the zone's name, as `[untrusted] ...`. A zone's
window that opens while another window has the keyboard does not take it,
nor does it get it when that window closes: Alt+j, Alt+k, a click or the
pointer moving onto it moves the keyboard there. Keys (Alt is the modifier):
pointer moving onto it moves the keyboard there. A touchscreen works as the
pointer, on the built-in screen when there is one: a tap is a click and a
drag drags with one finger, and zones are given pointer events only, not
touch. Keys (Alt is the modifier):

| keys | what |
| --- | --- |
Expand Down
148 changes: 148 additions & 0 deletions tools/desktop/dwl-zone-borders.py
Original file line number Diff line number Diff line change
Expand Up @@ -1271,6 +1271,154 @@
\tif (lidshut && undocktimer)
\t\twl_event_source_timer_update(undocktimer, 5000);
}
"""),
# A touchscreen drives the pointer, where dwl 0.8 drops touch devices. A finger down moves
# the pointer there and presses its left button, moving drags, and lifting releases it: a tap
# clicks through dwl's own pointer path, its focus rules and the lock's. Zones are offered no
# touch, only those pointer events, so the proxy and its policy stay as they are.
("""#include <wlr/types/wlr_switch.h>
""",
"""#include <wlr/types/wlr_switch.h>
#include <wlr/types/wlr_touch.h>
"""),
("""static struct wl_listener cursor_motion_absolute = {.notify = motionabsolute};
""",
"""static struct wl_listener cursor_motion_absolute = {.notify = motionabsolute};
static void touchdown(struct wl_listener *listener, void *data);
static void touchmotion(struct wl_listener *listener, void *data);
static void touchup(struct wl_listener *listener, void *data);
static void touchcancel(struct wl_listener *listener, void *data);
static struct wl_listener cursor_touch_down = {.notify = touchdown};
static struct wl_listener cursor_touch_motion = {.notify = touchmotion};
static struct wl_listener cursor_touch_up = {.notify = touchup};
static struct wl_listener cursor_touch_cancel = {.notify = touchcancel};
static struct wl_listener cursor_touch_frame = {.notify = cursorframe};
"""),
("""\twl_signal_add(&cursor->events.frame, &cursor_frame);
""",
"""\twl_signal_add(&cursor->events.frame, &cursor_frame);
\twl_signal_add(&cursor->events.touch_down, &cursor_touch_down);
\twl_signal_add(&cursor->events.touch_motion, &cursor_touch_motion);
\twl_signal_add(&cursor->events.touch_up, &cursor_touch_up);
\twl_signal_add(&cursor->events.touch_cancel, &cursor_touch_cancel);
\twl_signal_add(&cursor->events.touch_frame, &cursor_touch_frame);
"""),
("""\twl_list_remove(&cursor_motion_absolute.link);
""",
"""\twl_list_remove(&cursor_motion_absolute.link);
\twl_list_remove(&cursor_touch_down.link);
\twl_list_remove(&cursor_touch_motion.link);
\twl_list_remove(&cursor_touch_up.link);
\twl_list_remove(&cursor_touch_cancel.link);
\twl_list_remove(&cursor_touch_frame.link);
"""),
("""\tcase WLR_INPUT_DEVICE_SWITCH:
\t\tcreateswitch(wlr_switch_from_input_device(device));
\t\tbreak;
""",
"""\tcase WLR_INPUT_DEVICE_SWITCH:
\t\tcreateswitch(wlr_switch_from_input_device(device));
\t\tbreak;
\tcase WLR_INPUT_DEVICE_TOUCH:
\t\twlr_cursor_attach_input_device(cursor, device);
\t\tbreak;
"""),
("""void
cursorframe(struct wl_listener *listener, void *data)
{
""",
"""/* Kryptik: the finger that drives the pointer, -1 while none is down; a second is ignored. */
static int32_t touchid = -1;

/* Kryptik: the pointer to where a touch is: on the built-in panel when there is one, as a
* laptop's touchscreen is its own, and over every screen otherwise. One touchscreen is taken
* to be the laptop's, so a touch monitor on a desk drives the panel too. 0, the touch dropped,
* while the panel is there but off: dark, or shut away, a touch on it lands where no one sees. */
static int
touchpoint(struct wlr_input_device *device, uint32_t time, double x, double y)
{
\tstruct wlr_box box = {0};
\tdouble lx, ly;
\tint panel = 0;
\tMonitor *m;

\twl_list_for_each(m, &mons, link) {
\t\tconst char *n = m->wlr_output->name;
\t\tif (strncmp(n, "eDP", 3) && strncmp(n, "LVDS", 4) && strncmp(n, "DSI", 3))
\t\t\tcontinue;
\t\tpanel = 1;
\t\tif (m->wlr_output->enabled)
\t\t\tbox = m->m;
\t}
\tif (panel && wlr_box_empty(&box))
\t\treturn 0;
\tif (!wlr_box_empty(&box)) {
\t\tlx = box.x + x * box.width;
\t\tly = box.y + y * box.height;
\t} else {
\t\twlr_cursor_absolute_to_layout_coords(cursor, device, x, y, &lx, &ly);
\t}
\tmotionnotify(time, device, lx - cursor->x, ly - cursor->y, lx - cursor->x, ly - cursor->y);
\t/* motionnotify picks the surface before it moves the cursor, which a mouse's small steps
\t * never show. A touch jumps, so the surface is picked again where it landed, or the press
\t * would go to whatever the pointer rested on, another zone's window as likely as not. */
\tmotionnotify(0, NULL, 0, 0, 0, 0);
\treturn 1;
}

/* Kryptik: the left button, as buttonpress takes it from a pointer. */
static void
touchbutton(uint32_t time, enum wl_pointer_button_state state)
{
\tstruct wlr_pointer_button_event event = {.time_msec = time, .button = BTN_LEFT, .state = state};
\tbuttonpress(NULL, &event);
}

void
touchdown(struct wl_listener *listener, void *data)
{
\tstruct wlr_touch_down_event *event = data;
\t/* Through a lid shut on a dock, a touch is no one's: it would click, and take the keyboard,
\t * on a screen no one sees, and hold off the idle lock. */
\tif (touchid != -1 || lidshut)
\t\treturn;
\tif (!touchpoint(&event->touch->base, event->time_msec, event->x, event->y))
\t\treturn;
\ttouchid = event->touch_id;
\ttouchbutton(event->time_msec, WL_POINTER_BUTTON_STATE_PRESSED);
}

void
touchmotion(struct wl_listener *listener, void *data)
{
\tstruct wlr_touch_motion_event *event = data;
\tif (event->touch_id == touchid)
\t\ttouchpoint(&event->touch->base, event->time_msec, event->x, event->y);
}

void
touchup(struct wl_listener *listener, void *data)
{
\tstruct wlr_touch_up_event *event = data;
\tif (event->touch_id != touchid)
\t\treturn;
\ttouchid = -1;
\ttouchbutton(event->time_msec, WL_POINTER_BUTTON_STATE_RELEASED);
}

void
touchcancel(struct wl_listener *listener, void *data)
{
\tstruct wlr_touch_cancel_event *event = data;
\tif (event->touch_id != touchid)
\t\treturn;
\ttouchid = -1;
\ttouchbutton(event->time_msec, WL_POINTER_BUTTON_STATE_RELEASED);
}

void
cursorframe(struct wl_listener *listener, void *data)
{
"""),
# Locked, dwl heeds no key at all, so a laptop that starts to play aloud under the lock
# is silenced only by unlocking. It heeds config.h's lockedkeys instead: the volume and
Expand Down
7 changes: 6 additions & 1 deletion tools/desktop/wlprobe.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
* its middle: drawn, it covers the whole screen.
* Says when the window, and when the cursor image,
* enters an output: the second only if the
* compositor took the image. With shape, also ask
* compositor took the image, and each pointer
* button it is sent. With shape, also ask
* for the text cursor by name (wp_cursor_shape_v1)
* wlprobe animate SECONDS as oversize 0, and once drawn commit again on
* every frame callback for SECONDS; then say how
Expand Down Expand Up @@ -616,6 +617,10 @@ static int handle_one(void)
draw_popup();
} else if (cursor && object == pointer_id && opcode == 0) {
set_cursor(get32(body)); /* wl_pointer.enter(serial, surface, x, y) */
} else if (cursor && object == pointer_id && opcode == 3) {
/* wl_pointer.button(serial, time, button, state) */
printf("pointer button %u %s\n", get32(body + 8), get32(body + 12) ? "pressed" : "released");
fflush(stdout);
} else if (cursor && opcode == 0 && (object == SURFACE || (cursor_surf && object == cursor_surf))) {
/* wl_surface.enter(output) */
printf("%s entered an output\n", object == SURFACE ? "the window" : "the cursor image");
Expand Down
6 changes: 4 additions & 2 deletions tools/image/gui-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ install_disk gui-install "$USB" --vars clean && green "installed" || { red "inst
# ----------------------------------------------------------------- step 2 --
step "step 2: the desktop, driven"
rm -f "$SHOT" "$SHOT_FS" "$SHOT_OVER" "$SHOT_HEAD" "$SHOT_POPUP" "$SHOT_LOCKED"
start_vm gui-p2 --net user --gpu --second-head --mem 3072
start_vm gui-p2 --net user --gpu --second-head --touch --mem 3072
DRIVE_TIMEOUT=600 drive --qmp "$QMP" \
"expect:KRYPTIK_SMOKE: END" "seen:kryptik-firstboot: created user '${TUSER}'" "login:${TUSER}:${TPASS}" \
"send:su - root -c 'bash /usr/lib/kryptik/guest-tests/gui-check.sh ${TUSER} ${TPASS} 2>&1 | tee /var/log/kryptik/gui-check.log; echo GCHECK-DONE'" \
Expand Down Expand Up @@ -116,6 +116,8 @@ DRIVE_TIMEOUT=600 drive --qmp "$QMP" \
"expect:GT CONSENT-WAIT 2" "key:y" "key:ret" \
"expect:GT POINTER-ZONE0" "pointer:-4000,-4000" "pointer:120,120" \
"expect:GT POINTER-UNTRUSTED" "pointer:-4000,-4000" "pointer:120,120" \
"expect:GT TOUCH-HOVER-ZONE0" "pointer:4000,0" "pointer:-80,0" "pointer:-1,0" \
"expect:GT TOUCH-UNTRUSTED" "touch:250,500" \
"expect:GT SCREENSHOT-POPUP" "sleep:2" "screendump:${SHOT_POPUP}" \
"expect:GT KEY-MENU-POPUP" "key:alt+p" \
"expect:GT KEY-FOCUS-POPUP" "key:alt+j" \
Expand Down Expand Up @@ -183,7 +185,7 @@ for name in session-socket compositor-running desktop-memory-bound chrome-focus-
second-head-scale-chosen zone-sees-heads-unnamed \
vt-admin-login vt-desktop-back vt-desktop-takes-keys dwl-execs-nothing dwl-without-group seat-is-dwls dwl-confined-to-its-files \
volume-key lock-passes-volume-keys lock-holds-other-keys \
zone0-cursor-set zone0-cursor-shown zone-cursor-asked zone-hears-of-outputs zone-cursor-not-shown zone-cursor-by-name zone-fractional-scale compositor-socket-unreached \
zone0-cursor-set zone0-cursor-shown zone-cursor-asked zone-hears-of-outputs zone-cursor-not-shown zone-cursor-by-name touch-picks-window touch-presses-window zone-fractional-scale compositor-socket-unreached \
zone0-frame-clock zone-window-animates \
status-network-line-user \
zone0-fullscreen-granted zone0-fullscreen-needs-focus fullscreen-by-key marks-fullscreen zone-child-mapped child-focused parent-focused parent-fullscreen fullscreen-keeps-focus keyboard-stays-on-fullscreen zoom-keeps-keyboard zoom-twice-keeps-keyboard zone0-over-fullscreen-gets-keyboard zone0-window-ends-fullscreen child-ends-fullscreen; do
Expand Down
17 changes: 14 additions & 3 deletions tools/image/run-ovmf.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
# [--disk FILE]... [--testctl FILE] [--vars clean|enrolled|ms|FILE]
# [--vars-file FILE] [--mode console|smoke|serve] [--timeout N]
# [--log FILE] [--net none|user] [--mem MB] [--cpus N] [--gpu]
# [--second-head] [--s3] [--sound WAV] [--usb-port] [--allow-reboot]
# [--second-head] [--touch] [--s3] [--sound WAV] [--usb-port] [--allow-reboot]
# [--until REGEX] [--name TAG]
#
# --usb IMG the medium as a USB mass-storage device (removable),
Expand Down Expand Up @@ -38,6 +38,8 @@
# --second-head with --gpu in serve mode: a second output on the GPU, off
# until a VNC client on the printed socket asks for a size
# (vm-drive.py's head: step), which plugs a monitor in
# --touch with --gpu: a touchscreen over the first output, QEMU's
# virtio-multitouch (8.1 on), which vm-drive.py's touch: step taps
# --s3 offer suspend to RAM, which QEMU otherwise hides: the guest
# sleeps until vm-drive.py's wakeup step
# --sound WAV two HD Audio cards: the first only plays, into the file WAV
Expand All @@ -54,7 +56,7 @@ SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SELF}/../../build/lib/common.sh"

USB=""; ISO=""; NOMEDIA=0; DISKS=(); TESTCTL=""; VARS="clean"; VARS_FILE=""
MODE="smoke"; TIMEOUT=300; LOG=""; NET="none"; MEM=2048; CPUS=2; GPU=0; HEAD2=0; S3=0; SOUND=""; ALLOW_REBOOT=0; NAME="vm"
MODE="smoke"; TIMEOUT=300; LOG=""; NET="none"; MEM=2048; CPUS=2; GPU=0; HEAD2=0; TOUCH=0; S3=0; SOUND=""; ALLOW_REBOOT=0; NAME="vm"
DISK_RO=0; DISK_4K=0; BLKDEBUG=""; UNTIL=""; USB_PORT=0; USB_RO=on
while [[ "$#" -gt 0 ]]; do
case "$1" in
Expand All @@ -77,13 +79,14 @@ while [[ "$#" -gt 0 ]]; do
--cpus) CPUS="${2:?}"; shift 2 ;;
--gpu) GPU=1; shift ;;
--second-head) HEAD2=1; shift ;;
--touch) TOUCH=1; shift ;;
--s3) S3=1; shift ;;
--sound) SOUND="${2:?}"; shift 2 ;;
--usb-port) USB_PORT=1; shift ;;
--allow-reboot) ALLOW_REBOOT=1; shift ;;
--until) UNTIL="${2:?}"; shift 2 ;;
--name) NAME="${2:?}"; shift 2 ;;
-h|--help) sed -n '2,49p' "${BASH_SOURCE[0]}"; exit 0 ;;
-h|--help) sed -n '2,51p' "${BASH_SOURCE[0]}"; exit 0 ;;
*) die "unknown argument: $1" ;;
esac
done
Expand All @@ -93,6 +96,7 @@ done
[[ "$NOMEDIA" -eq 1 && "${#DISKS[@]}" -eq 0 ]] && die "--no-media needs at least one --disk"
[[ -n "$UNTIL" && "$MODE" != smoke ]] && die "--until is for --mode smoke"
[[ "$HEAD2" -eq 1 && ( "$GPU" -ne 1 || "$MODE" != serve ) ]] && die "--second-head is for --gpu in --mode serve"
[[ "$TOUCH" -eq 1 && "$GPU" -ne 1 ]] && die "--touch is for --gpu: the touchscreen covers its first output"
[[ "$HEAD2" -eq 1 && "$S3" -eq 1 ]] && die "--second-head needs virtio-gpu, which --s3 gives up for std VGA's framebuffer"

# Files only. This hands paths to a process that writes to them.
Expand Down Expand Up @@ -171,6 +175,13 @@ elif [[ "$GPU" -eq 1 ]]; then
ARGS+=( -display none -vga none -device virtio-vga,id=gpu0 )
fi
ARGS+=( -device virtio-keyboard-pci -device virtio-mouse-pci )
if [[ "$TOUCH" -eq 1 ]]; then
"$QEMU" -device help 2>/dev/null | grep -q '"virtio-multitouch-pci"' \
|| die "--touch: this QEMU has no virtio-multitouch-pci (QEMU 8.1 or later has)"
# Bound to the GPU's first head, so an input-send-event naming that head reaches it
# before the mouse, which takes button events too.
ARGS+=( -device virtio-multitouch-pci,id=touch0,display=gpu0,head=0 )
fi
else
ARGS+=( -display none -vga none )
fi
Expand Down
Loading
Loading