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
2 changes: 1 addition & 1 deletion images/chromium-headful/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ RUN --mount=type=cache,target=/tmp/cache/ffmpeg,sharing=locked,id=$CACHEIDPREFIX
EOT

FROM ghcr.io/kernel/neko/base:3.0.8-v1.4.0 AS neko
# ^--- now has event.SYSTEM_PONG with legacy support to keepalive
# TODO: update to xi2-scroll tag once kernel/neko#12 is merged and tagged
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will update this once neko PR is merged

FROM node:22-bullseye-slim AS node-22
FROM docker.io/ubuntu:22.04

Expand Down
76 changes: 46 additions & 30 deletions images/chromium-headful/client/src/components/video.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
:style="{ pointerEvents: hosting ? 'auto' : 'none' }"
@click.stop.prevent
@contextmenu.stop.prevent
@wheel.stop.prevent="onWheel"
@mousemove.stop.prevent="onMouseMove"
@mousedown.stop.prevent="onMouseDown"
@mouseup.stop.prevent="onMouseUp"
Expand Down Expand Up @@ -248,6 +247,8 @@
@Ref('player') readonly _player!: HTMLElement
@Ref('video') readonly _video!: HTMLVideoElement
@Ref('resolution') readonly _resolution!: Resolution

private _wheelHandler: ((e: WheelEvent) => void) | null = null
@Ref('clipboard') readonly _clipboard!: Clipboard

// all controls are hidden (e.g. for cast mode)
Expand Down Expand Up @@ -527,6 +528,14 @@
this.$nextTick(() => { this.isVideoSyncing = false })
})

this._wheelHandler = (e: WheelEvent) => {
if (!this.hosting) return
e.preventDefault()
if (this.locked) return
this.onWheel(e)
}
this._overlay.addEventListener('wheel', this._wheelHandler, { passive: false })

/* Initialize Guacamole Keyboard */
this.keyboard.onkeydown = (key: number) => {
if (!this.hosting || this.locked) {
Expand All @@ -552,6 +561,14 @@
}

beforeDestroy() {
if (this._wheelHandler) {
this._overlay.removeEventListener('wheel', this._wheelHandler)
this._wheelHandler = null
}
if (this._scrollRaf !== null) {
cancelAnimationFrame(this._scrollRaf)
this._scrollRaf = null
}
this.observer.disconnect()
this.$accessor.video.setPlayable(false)
/* Guacamole Keyboard does not provide destroy functions */
Expand Down Expand Up @@ -708,46 +725,45 @@
})
}

wheelThrottle = false
private _scrollAccX = 0
private _scrollAccY = 0
private _scrollCtrl = false
private _scrollRaf: number | null = null

onWheel(e: WheelEvent) {
if (!this.hosting || this.locked) {
return
}
this.sendMousePos(e)

let x = e.deltaX
let y = e.deltaY

// Normalize to pixel units. deltaMode 1 = lines, 2 = pages; convert
// both to approximate pixel values so the divisor below works uniformly.
if (e.deltaMode !== 0) {
x *= WHEEL_LINE_HEIGHT
y *= WHEEL_LINE_HEIGHT
}

if (this.scroll_invert) {
x = x * -1
y = y * -1
}

// The server sends one XTestFakeButtonEvent per unit we pass here,
// and each event scrolls Chromium by ~120 px. Raw pixel deltas from
// trackpads are already in pixels (~120 per notch), so dividing by
// PIXELS_PER_TICK converts them to discrete scroll "ticks". The
// result is clamped to [-scroll, scroll] (the user-facing sensitivity
// setting) so fast swipes don't over-scroll.
const PIXELS_PER_TICK = 120
x = x === 0 ? 0 : Math.min(Math.max(Math.round(x / PIXELS_PER_TICK) || Math.sign(x), -this.scroll), this.scroll)
y = y === 0 ? 0 : Math.min(Math.max(Math.round(y / PIXELS_PER_TICK) || Math.sign(y), -this.scroll), this.scroll)

this.sendMousePos(e)

if (!this.wheelThrottle) {
this.wheelThrottle = true
this.$client.sendData('wheel', { x, y })

window.setTimeout(() => {
this.wheelThrottle = false
}, 100)
x *= -1
y *= -1
}

const sensitivity = this.scroll / 10
this._scrollAccX += x * sensitivity
this._scrollAccY += y * sensitivity
this._scrollCtrl = e.ctrlKey || e.metaKey

if (this._scrollRaf === null) {
this._scrollRaf = requestAnimationFrame(() => {
this._scrollRaf = null
const dx = Math.max(-32767, Math.min(32767, Math.round(this._scrollAccX)))
const dy = Math.max(-32767, Math.min(32767, Math.round(this._scrollAccY)))
const ctrl = this._scrollCtrl
this._scrollAccX -= dx
this._scrollAccY -= dy
this._scrollCtrl = false
if (dx !== 0 || dy !== 0) {
this.$client.sendData('wheel', { x: dx, y: dy, controlKey: ctrl })
}
})
}
}

Expand Down
8 changes: 5 additions & 3 deletions images/chromium-headful/client/src/neko/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
this._id = ''
}

public sendData(event: 'wheel' | 'mousemove', data: { x: number; y: number }): void
public sendData(event: 'wheel', data: { x: number; y: number; controlKey?: boolean }): void
public sendData(event: 'mousemove', data: { x: number; y: number }): void
public sendData(event: 'mousedown' | 'mouseup' | 'keydown' | 'keyup', data: { key: number }): void
public sendData(event: string, data: any) {
if (!this.connected) {
Expand All @@ -156,12 +157,13 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
payload.setUint16(5, data.y, true)
break
case 'wheel':
buffer = new ArrayBuffer(7)
buffer = new ArrayBuffer(8)
payload = new DataView(buffer)
payload.setUint8(0, OPCODE.SCROLL)
payload.setUint16(1, 4, true)
payload.setUint16(1, 5, true)
payload.setInt16(3, data.x, true)
payload.setInt16(5, data.y, true)
payload.setUint8(7, data.controlKey ? 1 : 0)
break
case 'keydown':
case 'mousedown':
Expand Down
3 changes: 3 additions & 0 deletions images/chromium-headful/neko.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@

desktop:
screen: "1920x1080@25"
input:
enabled: true
socket: "/tmp/xf86-input-neko.sock"

member:
provider: multiuser
Expand Down
76 changes: 49 additions & 27 deletions images/chromium-headful/xorg-deps/xf86-input-neko/src/neko.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,15 @@
#include <X11/keysym.h>
#include <mipointer.h>
#include <xserver-properties.h>
#include <inputstr.h>
#include <pthread.h>

#define MAX_USED_VALUATORS 3 /* x, y, pressure */
#define TOUCH_MAX_SLOTS 10 /* max number of simultaneous touches */
#define MAX_USED_VALUATORS 5 /* x, y, pressure, v-scroll, h-scroll */
#define TOUCH_VALUATORS 3 /* touch only uses x, y, pressure */
#define TOUCH_MAX_SLOTS 10 /* max number of simultaneous touches */

#define NEKO_SCROLL 0x80
#define SCROLL_INCREMENT 120.0

struct neko_message
{
Expand Down Expand Up @@ -149,16 +154,25 @@ ReadInput(InputInfoPtr pInfo)
ValuatorMask *m = priv->valuators;
valuator_mask_zero(m);

// do not send valuators if x and y are -1
if (msg.x != -1 && msg.y != -1)
if (msg.type == NEKO_SCROLL)
{
valuator_mask_set_double(m, 0, msg.x);
valuator_mask_set_double(m, 1, msg.y);
valuator_mask_set_double(m, 2, msg.pressure);
if (msg.y != 0)
valuator_mask_set_double(m, 3, (double)msg.y);
if (msg.x != 0)
valuator_mask_set_double(m, 4, (double)msg.x);
xf86PostMotionEventM(pInfo->dev, FALSE, m);
}
else
{
// do not send valuators if x and y are -1
if (msg.x != -1 && msg.y != -1)
{
valuator_mask_set_double(m, 0, msg.x);
valuator_mask_set_double(m, 1, msg.y);
valuator_mask_set_double(m, 2, msg.pressure);
}
xf86PostTouchEvent(pInfo->dev, msg.touchId, msg.type, 0, m);
}

// TODO: extend to other types, such as keyboard and mouse
xf86PostTouchEvent(pInfo->dev, msg.touchId, msg.type, 0, m);
}

/* Close socket. */
Expand All @@ -181,11 +195,11 @@ InitTouch(InputInfoPtr pInfo)
struct neko_priv *priv = pInfo->private;

const int nbtns = 11;
const int naxes = 3;
const int naxes = MAX_USED_VALUATORS; /* x, y, pressure, v-scroll, h-scroll */

unsigned char map[nbtns + 1];
Atom btn_labels[nbtns];
Atom axis_labels[naxes];
Atom axis_labels[MAX_USED_VALUATORS];

// init button map
memset(map, 0, sizeof(map));
Expand All @@ -209,10 +223,12 @@ InitTouch(InputInfoPtr pInfo)
btn_labels[10] = XIGetKnownProperty(BTN_LABEL_PROP_BTN_BACK);

// init axis labels
memset(axis_labels, 0, ARRAY_SIZE(axis_labels) * sizeof(Atom));
memset(axis_labels, 0, sizeof(axis_labels));
axis_labels[0] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_MT_POSITION_X);
axis_labels[1] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_MT_POSITION_Y);
axis_labels[2] = XIGetKnownProperty(AXIS_LABEL_PROP_ABS_MT_PRESSURE);
axis_labels[3] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_VSCROLL);
axis_labels[4] = XIGetKnownProperty(AXIS_LABEL_PROP_REL_HSCROLL);

/* initialize mouse emulation valuators */
if (InitPointerDeviceStruct((DevicePtr)pInfo->dev,
Expand Down Expand Up @@ -274,22 +290,28 @@ InitTouch(InputInfoPtr pInfo)
priv->pmax + 1, /* max_res */
Absolute);

/*
The mode field is either XIDirectTouch for direct−input touch devices
such as touchscreens or XIDependentTouch for indirect input devices such
as touchpads. For XIDirectTouch devices, touch events are sent to window
at the position the touch occured. For XIDependentTouch devices, touch
events are sent to the window at the position of the device's sprite.

The num_touches field defines the maximum number of simultaneous touches
the device supports. A num_touches of 0 means the maximum number of
simultaneous touches is undefined or unspecified. This field should be
used as a guide only, devices will lie about their capabilities.
*/
/* scroll valuator axes — relative, so min=max=0 */
xf86InitValuatorAxisStruct(pInfo->dev, 3,
axis_labels[3],
NO_AXIS_LIMITS, NO_AXIS_LIMITS, /* no limits for scroll */
0, 0, 0,
Relative);
SetScrollValuator(pInfo->dev, 3, SCROLL_TYPE_VERTICAL,
SCROLL_INCREMENT, SCROLL_FLAG_PREFERRED);

xf86InitValuatorAxisStruct(pInfo->dev, 4,
axis_labels[4],
NO_AXIS_LIMITS, NO_AXIS_LIMITS,
0, 0, 0,
Relative);
SetScrollValuator(pInfo->dev, 4, SCROLL_TYPE_HORIZONTAL,
SCROLL_INCREMENT, SCROLL_FLAG_PREFERRED);

/* Touch class only uses the first 3 axes (x, y, pressure). */
if (InitTouchClassDeviceStruct(pInfo->dev,
priv->slots,
XIDirectTouch,
naxes) == FALSE)
TOUCH_VALUATORS) == FALSE)
{
xf86IDrvMsg(pInfo, X_ERROR,
"unable to allocate TouchClassDeviceStruct\n");
Expand Down Expand Up @@ -354,7 +376,7 @@ PreInit(__attribute__ ((unused)) InputDriverPtr drv,
return BadValue;
}

pInfo->type_name = (char*)XI_TOUCHSCREEN;
pInfo->type_name = (char*)XI_MOUSE;
pInfo->device_control = DeviceControl;
pInfo->read_input = NULL;
pInfo->control_proc = NULL;
Expand Down
Loading