mirror of
https://github.com/DeeJanuz/frametop.git
synced 2026-10-06 11:00:14 +02:00
Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
540d8c425c | ||
|
|
ba6ccdfbd1 | ||
|
|
77f28f0922 | ||
|
|
85532a54f3 | ||
|
|
3864741f53 |
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.
|
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
|
## 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.
|
You need a Steam Frame with an internet connection, a keyboard (Bluetooth, or the on-screen one), and about 3 GB of free space.
|
||||||
@@ -139,7 +141,7 @@ In a terminal on the headset, run:
|
|||||||
cd ~/frametop && scripts/report.sh
|
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
|
## Update
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
# 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, and only
|
||||||
|
presses from hand controllers start it. The 3D mouse (whose laser comes from the
|
||||||
|
`ft_pointer` virtual controller), 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.
|
||||||
+1
-1
@@ -175,7 +175,7 @@ Flatpak apps need `XDG_DATA_DIRS` to include Flatpak's exports, or Plasma opens
|
|||||||
|
|
||||||
The private runtime directory also moves the session's document portal to `$XDG_RUNTIME_DIR/frametop/doc`, and that broke saving and uploading in Flatpak apps. The file picker (xdg-desktop-portal 1.18.4 on SteamOS) gives a sandboxed app the host path of the file it picked, `/run/user/1000/frametop/doc/ID/NAME`. Inside the sandbox the portal is at `/run/flatpak/doc`, and `/run/user/1000` is a private per-app folder (`.flatpak/APP/xdg-run` in the runtime directory). So Brave created the missing folder there, "finished" the download into it, and the file vanished when the session cleaned up. The session script now links that path to `/run/flatpak/doc` in each installed app's folder before Plasma starts. Upstream xdg-desktop-portal fixed this after 1.22.1 (commit `69ba5e1`) by handing Flatpak apps `/run/flatpak/doc` paths, after which the links go unused.
|
The private runtime directory also moves the session's document portal to `$XDG_RUNTIME_DIR/frametop/doc`, and that broke saving and uploading in Flatpak apps. The file picker (xdg-desktop-portal 1.18.4 on SteamOS) gives a sandboxed app the host path of the file it picked, `/run/user/1000/frametop/doc/ID/NAME`. Inside the sandbox the portal is at `/run/flatpak/doc`, and `/run/user/1000` is a private per-app folder (`.flatpak/APP/xdg-run` in the runtime directory). So Brave created the missing folder there, "finished" the download into it, and the file vanished when the session cleaned up. The session script now links that path to `/run/flatpak/doc` in each installed app's folder before Plasma starts. Upstream xdg-desktop-portal fixed this after 1.22.1 (commit `69ba5e1`) by handing Flatpak apps `/run/flatpak/doc` paths, after which the links go unused.
|
||||||
|
|
||||||
A podman container's monitor process (conmon) stays in the cgroup of whatever started the container, and `distrobox enter` starts it on demand. When a Frametop service happened to start the `dev` container, stopping that service stopped the container and everything in it, including the desktop's compositor. `scripts/container-up.sh` starts the container in a systemd scope of its own before anything enters it.
|
A podman container's monitor process (conmon) stays in the cgroup of whatever started the container, and `distrobox enter` starts it on demand. When a Frametop service happened to start the `dev` container, stopping that service stopped the container and everything in it, including the desktop's compositor. `scripts/container-up.sh` starts the container in a systemd scope of its own before anything enters it. It then waits for distrobox-init to log `container_setup_done`, as `distrobox enter` does only for containers it starts itself. A new container's first start takes a minute or more (it installs distrobox's dependencies and sets up passwordless sudo), and an install that entered right away met a sudo password prompt with no terminal to answer it ([#9](https://github.com/DeeJanuz/frametop/issues/9)).
|
||||||
|
|
||||||
Program names stay within 15 characters, because Linux truncates process names there and the scripts find programs with `pgrep -x` and `pkill -x`. That's why the prefix is `ft-`.
|
Program names stay within 15 characters, because Linux truncates process names there and the scripts find programs with `pgrep -x` and `pkill -x`. That's why the prefix is `ft-`.
|
||||||
|
|
||||||
|
|||||||
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 <wlr/util/log.h>
|
||||||
|
|
||||||
#include "vr.h"
|
#include "vr.h"
|
||||||
|
#include "controller-click.h"
|
||||||
|
|
||||||
#define MAX_SCREENS 24 // screens and spare outputs
|
#define MAX_SCREENS 24 // screens and spare outputs
|
||||||
|
|
||||||
@@ -104,6 +105,7 @@ struct server {
|
|||||||
struct wl_list buffers; // tracked_buffer
|
struct wl_list buffers; // tracked_buffer
|
||||||
struct wl_event_source *tick;
|
struct wl_event_source *tick;
|
||||||
struct screen *pointer_focus;
|
struct screen *pointer_focus;
|
||||||
|
struct ft_controller_click controller_click;
|
||||||
pid_t child;
|
pid_t child;
|
||||||
// Where typing goes: the screens after a click on one, Steam after a click on another
|
// 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).
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
if (e->screen < 0 || e->screen >= MAX_SCREENS || !s->screens[e->screen]) 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 screen *sc = s->screens[e->screen];
|
||||||
struct wlr_surface *surface = sc->toplevel->base->surface;
|
struct wlr_surface *surface = sc->toplevel->base->surface;
|
||||||
const uint32_t t = now_ms();
|
const uint32_t t = now_ms();
|
||||||
@@ -514,7 +521,18 @@ static int control_readable(int fd, uint32_t mask, void *data) {
|
|||||||
unsigned code;
|
unsigned code;
|
||||||
int value, index, w, h;
|
int value, index, w, h;
|
||||||
double scale;
|
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
|
// 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.)
|
// 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;
|
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) {
|
int main(int argc, char **argv) {
|
||||||
struct server s = {0};
|
struct server s = {0};
|
||||||
|
s.controller_click.threshold = 8;
|
||||||
for (int i = 0; i < MAX_SCREENS; ++i) s.scale[i] = 1;
|
for (int i = 0; i < MAX_SCREENS; ++i) s.scale[i] = 1;
|
||||||
s.kb_screen = -1;
|
s.kb_screen = -1;
|
||||||
const char *socket_name = "ft-screens-0", *control_name = "ft_screens";
|
const char *socket_name = "ft-screens-0", *control_name = "ft_screens";
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
#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 or delayed button-down. Only a hand controller's press starts
|
||||||
|
// it: the 3D mouse drives a laser too, and its short drags must stay drags.
|
||||||
|
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 && e->controller && !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,42 @@
|
|||||||
|
#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,.controller=true,.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);
|
||||||
|
// The 3D mouse's laser: a short drag stays a drag, and the release stays where it was.
|
||||||
|
e=event(FT_BUTTON,true,100,100);e.controller=false;assert(ft_controller_click_filter(&c,&e,1) && !c.held);
|
||||||
|
e=event(FT_MOTION,false,104,100);assert(ft_controller_click_filter(&c,&e,1));
|
||||||
|
e=event(FT_BUTTON,false,106,100);e.controller=false;
|
||||||
|
assert(ft_controller_click_filter(&c,&e,1) && e.x==106 && c.clicks==2 && c.suppressed==1);
|
||||||
|
}
|
||||||
@@ -1741,6 +1741,7 @@ void ft_vr_poll(void (*handle)(const struct ft_event *, void *), void *data) {
|
|||||||
e.type = FT_BUTTON;
|
e.type = FT_BUTTON;
|
||||||
e.button = LinuxButton(ev.data.mouse.button);
|
e.button = LinuxButton(ev.data.mouse.button);
|
||||||
e.pressed = ev.eventType == vr::VREvent_MouseButtonDown;
|
e.pressed = ev.eventType == vr::VREvent_MouseButtonDown;
|
||||||
|
e.controller = IsHandController(ev.trackedDeviceIndex);
|
||||||
at();
|
at();
|
||||||
if (!e.pressed && s.titleCarry) e.x = s.carryX, e.y = s.carryY, s.titleCarry = false;
|
if (!e.pressed && s.titleCarry) e.x = s.carryX, e.y = s.carryY, s.titleCarry = false;
|
||||||
if (e.pressed) {
|
if (e.pressed) {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ struct ft_event {
|
|||||||
double x, y; // FT_MOTION: buffer pixels from the top left
|
double x, y; // FT_MOTION: buffer pixels from the top left
|
||||||
uint32_t button; // FT_BUTTON: linux BTN_*
|
uint32_t button; // FT_BUTTON: linux BTN_*
|
||||||
bool pressed;
|
bool pressed;
|
||||||
|
bool controller; // FT_BUTTON: from a hand controller's laser (not the 3D mouse's)
|
||||||
double dx, dy; // FT_SCROLL: notches (positive dy: scroll down)
|
double dx, dy; // FT_SCROLL: notches (positive dy: scroll down)
|
||||||
uint32_t key; // FT_KEY: linux KEY_* from our keyboard (pressed: down or up)
|
uint32_t key; // FT_KEY: linux KEY_* from our keyboard (pressed: down or up)
|
||||||
};
|
};
|
||||||
|
|||||||
+18
-3
@@ -6,7 +6,22 @@
|
|||||||
# `distrobox enter`.
|
# `distrobox enter`.
|
||||||
box=${FRAME_BOX:-dev}
|
box=${FRAME_BOX:-dev}
|
||||||
export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}
|
export XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}
|
||||||
[ "$(podman container inspect -f '{{.State.Running}}' "$box" 2>/dev/null)" = true ] && exit 0
|
running() { [ "$(podman container inspect -f '{{.State.Running}}' "$box" 2>/dev/null)" = true ]; }
|
||||||
|
running && exit 0
|
||||||
podman container exists "$box" 2>/dev/null || exit 0 # not created yet: setup/dev-container.sh does that
|
podman container exists "$box" 2>/dev/null || exit 0 # not created yet: setup/dev-container.sh does that
|
||||||
exec systemd-run --user --scope --quiet --collect --description="$box container (started for Frametop)" \
|
since=$(date -u +%FT%T)
|
||||||
podman start "$box" >/dev/null
|
systemd-run --user --scope --quiet --collect --description="$box container (started for Frametop)" \
|
||||||
|
podman start "$box" >/dev/null || exit
|
||||||
|
|
||||||
|
# Every start runs distrobox-init in the container, and `distrobox enter` only waits for it
|
||||||
|
# when it starts the container itself. The first start installs what distrobox needs and sets
|
||||||
|
# up passwordless sudo, which takes a minute or more; until then sudo in the container asks
|
||||||
|
# for a password (issue #9). Later starts take a few seconds.
|
||||||
|
for i in $(seq 600); do
|
||||||
|
podman logs --since "$since" "$box" 2>&1 | grep -q '^container_setup_done' && exit 0
|
||||||
|
running || break
|
||||||
|
[ "$i" = 10 ] && echo "setting up the $box container (the first start takes a few minutes)" >&2
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo "the $box container didn't finish starting; see: podman logs $box" >&2
|
||||||
|
exit 1
|
||||||
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'
|
||||||
@@ -45,9 +45,9 @@ fi
|
|||||||
"$distrobox" enter dev -- bash -c '
|
"$distrobox" enter dev -- bash -c '
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
echo "installing ${#@} packages (already-installed ones are skipped)"
|
echo "installing ${#@} packages (already-installed ones are skipped)"
|
||||||
sudo dnf install -y -q "$@" 2>&1 | { grep -vE "is already installed|^Nothing to do|^$" || true; }
|
sudo -n dnf install -y -q "$@" 2>&1 | { grep -vE "is already installed|^Nothing to do|^$" || true; }
|
||||||
# OpenVR programs built here (the pointer helper and probe) look for the runtime at /opt/steamvr.
|
# OpenVR programs built here (the pointer helper and probe) look for the runtime at /opt/steamvr.
|
||||||
[ -e /opt/steamvr ] || sudo ln -s /run/host/opt/steamvr /opt/steamvr
|
[ -e /opt/steamvr ] || sudo -n ln -s /run/host/opt/steamvr /opt/steamvr
|
||||||
echo "dev container ready: $(. /etc/os-release; echo $PRETTY_NAME), glibc $(ldd --version | head -1 | grep -oE "[0-9.]+$")"
|
echo "dev container ready: $(. /etc/os-release; echo $PRETTY_NAME), glibc $(ldd --version | head -1 | grep -oE "[0-9.]+$")"
|
||||||
' dev "$@"
|
' dev "$@"
|
||||||
EOF
|
EOF
|
||||||
Reference in new issue
Block a user