mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 01:00:06 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1fdb1fd3e5 | ||
|
|
9da8bd5500 | ||
|
|
7816633353 |
No files matched your search
@@ -13,6 +13,8 @@ Two settings apps come with it: Frametop Display Settings for the screens, profi
|
||||
|
||||
Frametop is an independent project, not made by or affiliated with Valve.
|
||||
|
||||
Join the [Frametop Discord](https://discord.gg/W3X9f7z3Bc) for questions, ideas, and help with your setup.
|
||||
|
||||
## Install on the headset
|
||||
|
||||
You need a Steam Frame with an internet connection, a keyboard (Bluetooth, or the on-screen one), and about 3 GB of free space.
|
||||
@@ -138,7 +140,7 @@ In a terminal on the headset, run:
|
||||
cd ~/frametop && scripts/report.sh
|
||||
```
|
||||
|
||||
This writes `frametop-report-<date>.txt` with version numbers, service states, settings, and recent logs. Bluetooth addresses and the headset's serial number are masked. Then [open an issue](https://github.com/DeeJanuz/frametop/issues), describe what you did, what you expected, and what happened, and attach the file.
|
||||
This writes `frametop-report-<date>.txt` with version numbers, service states, settings, and recent logs. Bluetooth addresses and the headset's serial number are masked. Then [open an issue](https://github.com/DeeJanuz/frametop/issues), describe what you did, what you expected, and what happened, and attach the file. Quick questions can go to [Discord](https://discord.gg/W3X9f7z3Bc) instead.
|
||||
|
||||
## Update
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# Controller desktop click stability
|
||||
|
||||
Trigger presses reach KDE immediately, but controller motion within 8 logical
|
||||
pixels of the press stays at that position until release. Releasing without a motion outside this
|
||||
zone delivers the click at the original position, even if the hand moved during
|
||||
release. Moving outside the zone begins a normal drag immediately; returning to
|
||||
the zone does not turn it back into a click. There is no hold-duration timer.
|
||||
|
||||
This filters overlay pointer content events on desktop monitors only. Native mouse
|
||||
input, SteamVR UI, separate screen grab bars and floating-app title-bar carrying
|
||||
are unaffected. Multi-button gestures keep their existing behavior. A motion
|
||||
onto another desktop monitor starts a drag; cross-monitor motion is not stabilized.
|
||||
|
||||
CLI (runtime preferences, reset to 8 on desktop restart):
|
||||
|
||||
```sh
|
||||
input/ft-clickctl status
|
||||
input/ft-clickctl threshold 8
|
||||
input/ft-clickctl threshold 0 # disable without a restart
|
||||
```
|
||||
|
||||
Thresholds are 0–64 logical pixels, normalized to each panel's KDE scale.
|
||||
Status reports held state, suppressed motions, stabilized clicks and drags.
|
||||
Changing the threshold while a controller button is held is refused.
|
||||
|
||||
This is a separate contribution from desktop mouse/controller ownership. Its
|
||||
hardware validation must check small controls, intentional text selection,
|
||||
long presses, cross-monitor dragging and simultaneous mouse use. The existing
|
||||
renderer laser remains tracked; this change stabilizes desktop input rather
|
||||
than smoothing the visual laser. Default threshold is a starting point to test.
|
||||
|
||||
Run `scripts/test-controller-click.sh` for the isolated gesture-state tests.
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Controller desktop click stability over local IPC; no SteamVR client."""
|
||||
import argparse
|
||||
import json
|
||||
import math
|
||||
import socket
|
||||
|
||||
def request(command):
|
||||
with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as client:
|
||||
client.bind(''); client.settimeout(2)
|
||||
client.sendto(command.encode(), '\0ft_screens')
|
||||
response=client.recv(8192).decode()
|
||||
if response.startswith('error'): raise RuntimeError(response)
|
||||
return response
|
||||
|
||||
def main():
|
||||
parser=argparse.ArgumentParser(description=__doc__)
|
||||
sub=parser.add_subparsers(dest='command', required=True)
|
||||
sub.add_parser('status')
|
||||
sub.add_parser('threshold').add_argument('pixels', type=float)
|
||||
args=parser.parse_args()
|
||||
try:
|
||||
if args.command == 'threshold':
|
||||
if not math.isfinite(args.pixels) or not 0 <= args.pixels <= 64:
|
||||
raise ValueError('Threshold must be 0–64 logical pixels; 0 disables stabilization')
|
||||
if request(f'controller-click {args.pixels:g}') != 'ok': raise RuntimeError('Threshold rejected')
|
||||
print(json.dumps(json.loads(request('controller-click?')), indent=2))
|
||||
except (OSError, ValueError, RuntimeError) as error: parser.exit(1, str(error)+'\n')
|
||||
if __name__ == '__main__': main()
|
||||
+20
-1
@@ -58,6 +58,7 @@
|
||||
#include <wlr/util/log.h>
|
||||
|
||||
#include "vr.h"
|
||||
#include "controller-click.h"
|
||||
|
||||
#define MAX_SCREENS 24 // screens and spare outputs
|
||||
|
||||
@@ -104,6 +105,7 @@ struct server {
|
||||
struct wl_list buffers; // tracked_buffer
|
||||
struct wl_event_source *tick;
|
||||
struct screen *pointer_focus;
|
||||
struct ft_controller_click controller_click;
|
||||
pid_t child;
|
||||
// Where typing goes: the screens after a click on one, Steam after a click on another
|
||||
// panel. The input relay grabs the keyboards while it's the screens (see keys_update).
|
||||
@@ -292,6 +294,11 @@ static void handle_vr_event(const struct ft_event *e, void *data) {
|
||||
return;
|
||||
}
|
||||
if (e->screen < 0 || e->screen >= MAX_SCREENS || !s->screens[e->screen]) return;
|
||||
struct ft_event filtered = *e;
|
||||
if (e->screen < s->n_config &&
|
||||
!ft_controller_click_filter(&s->controller_click, &filtered, s->scale[e->screen])) return;
|
||||
e = &filtered;
|
||||
if (e->screen < 0 || e->screen >= MAX_SCREENS || !s->screens[e->screen]) return;
|
||||
struct screen *sc = s->screens[e->screen];
|
||||
struct wlr_surface *surface = sc->toplevel->base->surface;
|
||||
const uint32_t t = now_ms();
|
||||
@@ -514,7 +521,18 @@ static int control_readable(int fd, uint32_t mask, void *data) {
|
||||
unsigned code;
|
||||
int value, index, w, h;
|
||||
double scale;
|
||||
if (sscanf(buf, "size %d %d %d", &index, &w, &h) == 3) {
|
||||
char tail;
|
||||
if (strcmp(buf, "controller-click?") == 0) {
|
||||
snprintf(reply, sizeof reply,
|
||||
"{\"supported\":true,\"threshold\":%.3f,\"held\":%s,\"dragging\":%s,\"suppressedMotions\":%lu,\"clicks\":%lu,\"drags\":%lu}",
|
||||
s->controller_click.threshold, s->controller_click.held ? "true" : "false",
|
||||
s->controller_click.dragging ? "true" : "false", s->controller_click.suppressed,
|
||||
s->controller_click.clicks, s->controller_click.drags);
|
||||
} else if (sscanf(buf, "controller-click %lf %c", &scale, &tail) == 1) {
|
||||
if (!isfinite(scale) || scale < 0 || scale > 64 || s->controller_click.buttons)
|
||||
snprintf(reply, sizeof reply, "error threshold or held controller button");
|
||||
else { s->controller_click.threshold = scale; snprintf(reply, sizeof reply, "ok"); }
|
||||
} else if (sscanf(buf, "size %d %d %d", &index, &w, &h) == 3) {
|
||||
// A new resolution for a screen, live: KWin resizes the screen to match. (KWin makes
|
||||
// it this size times its scale; ft-floatd sends spares' sizes divided by theirs.)
|
||||
const int min_w = index - 1 < s->n_config ? 320 : 64, min_h = index - 1 < s->n_config ? 200 : 64;
|
||||
@@ -641,6 +659,7 @@ static bool setup_dmabuf(struct server *s) {
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
struct server s = {0};
|
||||
s.controller_click.threshold = 8;
|
||||
for (int i = 0; i < MAX_SCREENS; ++i) s.scale[i] = 1;
|
||||
s.kb_screen = -1;
|
||||
const char *socket_name = "ft-screens-0", *control_name = "ft_screens";
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
#ifndef FT_CONTROLLER_CLICK_H
|
||||
#define FT_CONTROLLER_CLICK_H
|
||||
#include <math.h>
|
||||
#include <linux/input-event-codes.h>
|
||||
#include "vr.h"
|
||||
struct ft_controller_click {
|
||||
bool held, dragging;
|
||||
int screen;
|
||||
uint32_t buttons;
|
||||
double x, y, threshold, radius;
|
||||
unsigned long suppressed, clicks, drags;
|
||||
};
|
||||
// Hold the desktop position at press time until movement exceeds a logical-pixel
|
||||
// radius. No timer, delayed button-down, or change to native mouse input.
|
||||
static inline bool ft_controller_click_filter(struct ft_controller_click *c,
|
||||
struct ft_event *e, double scale) {
|
||||
if (e->type == FT_BUTTON && e->button >= BTN_LEFT && e->button < BTN_LEFT+8) {
|
||||
uint32_t bit = 1u << (e->button-BTN_LEFT);
|
||||
if (e->pressed) c->buttons |= bit; else c->buttons &= ~bit;
|
||||
}
|
||||
if (e->type == FT_BUTTON && e->pressed) {
|
||||
if (e->button == BTN_LEFT && !c->held && c->threshold > 0) {
|
||||
c->held = true; c->dragging = false; c->screen = e->screen;
|
||||
c->x = e->x; c->y = e->y; c->radius = c->threshold * scale;
|
||||
} else if (e->button != BTN_LEFT) {
|
||||
c->held = false; // Multi-button gestures retain their usual semantics.
|
||||
}
|
||||
} else if (e->type == FT_MOTION && c->held && !c->dragging) {
|
||||
if (e->screen == c->screen && hypot(e->x-c->x, e->y-c->y) <= c->radius) {
|
||||
++c->suppressed;
|
||||
return false;
|
||||
}
|
||||
c->dragging = true; ++c->drags;
|
||||
} else if (e->type == FT_BUTTON && e->button == BTN_LEFT && !e->pressed && c->held) {
|
||||
if (!c->dragging) {
|
||||
e->screen = c->screen; e->x = c->x; e->y = c->y;
|
||||
++c->clicks;
|
||||
}
|
||||
c->held = false; c->dragging = false;
|
||||
} else if (e->type == FT_LEAVE && c->held) {
|
||||
return false; // Preserve the implicit grab through tiny edge excursions.
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,37 @@
|
||||
#include <assert.h>
|
||||
#include "../controller-click.h"
|
||||
static struct ft_event event(enum ft_event_type t, bool down, double x, double y) {
|
||||
return (struct ft_event){.type=t,.screen=0,.button=BTN_LEFT,.pressed=down,.x=x,.y=y};
|
||||
}
|
||||
int main(void) {
|
||||
struct ft_controller_click c={.threshold=8};
|
||||
struct ft_event e=event(FT_BUTTON,true,100,100);
|
||||
assert(ft_controller_click_filter(&c,&e,2));
|
||||
e=event(FT_MOTION,false,112,108);
|
||||
assert(!ft_controller_click_filter(&c,&e,2));
|
||||
assert(c.held && !c.dragging && c.suppressed==1);
|
||||
e=event(FT_BUTTON,false,116,111);
|
||||
assert(ft_controller_click_filter(&c,&e,2));
|
||||
assert(e.x==100 && e.y==100 && c.clicks==1 && !c.held);
|
||||
e=event(FT_BUTTON,true,100,100);ft_controller_click_filter(&c,&e,1);
|
||||
e=event(FT_MOTION,false,109,100);assert(ft_controller_click_filter(&c,&e,1));
|
||||
assert(c.dragging && c.drags==1);
|
||||
e=event(FT_MOTION,false,101,100);assert(ft_controller_click_filter(&c,&e,1));
|
||||
e=event(FT_BUTTON,false,103,100);assert(ft_controller_click_filter(&c,&e,1));
|
||||
assert(e.x==103 && c.clicks==1 && !c.held && !c.dragging);
|
||||
e=event(FT_BUTTON,true,100,100);ft_controller_click_filter(&c,&e,1);
|
||||
e=event(FT_LEAVE,false,0,0);assert(!ft_controller_click_filter(&c,&e,1));
|
||||
e=event(FT_BUTTON,false,800,900);e.screen=1;ft_controller_click_filter(&c,&e,1);
|
||||
assert(e.screen==0 && e.x==100 && c.clicks==2);
|
||||
e=event(FT_BUTTON,true,100,100);ft_controller_click_filter(&c,&e,1);
|
||||
e=event(FT_MOTION,false,101,100);e.screen=1;
|
||||
assert(ft_controller_click_filter(&c,&e,1) && c.dragging);
|
||||
e=event(FT_BUTTON,false,101,100);ft_controller_click_filter(&c,&e,1);
|
||||
c.threshold=0;
|
||||
e=event(FT_BUTTON,true,100,100);ft_controller_click_filter(&c,&e,1);
|
||||
e=event(FT_MOTION,false,101,100);assert(ft_controller_click_filter(&c,&e,1) && !c.held);
|
||||
c.threshold=8;
|
||||
e=event(FT_BUTTON,true,100,100);ft_controller_click_filter(&c,&e,1);
|
||||
e=event(FT_BUTTON,true,100,100);e.button=BTN_RIGHT;ft_controller_click_filter(&c,&e,1);
|
||||
assert(!c.held);
|
||||
}
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
"$root/scripts/frame.sh" -C screens 'mkdir -p build; gcc -std=c11 -Wall -Wextra -Werror tests/controller-click-test.c -lm -o build/controller-click-test && build/controller-click-test'
|
||||
Reference in new issue
Block a user