Hide the screens during VR games unless the dashboard is open

A new setting, During VR games: with Always, the screens hide while a VR
game runs and show while the SteamVR dashboard is open (the default), or
stay visible over the game. The hotkey still shows them; a game starting or
stopping resets it. Socket command: ingames hide|visible.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DeeJanuzandClaude Opus 5.5 committed 2026-09-26 17:06:42 -06:00
1 parent 6122313fae
commit b9dc805e75
6 files changed
+85 -13

No files matched your search

+1 -1
View File
@@ -55,7 +55,7 @@ After the restart:
| While carrying a screen, sweep its laser across your other controller's ring, then let go | Pins it to that wrist, at its size and distance, as you hold it when you let go; it shows while you see its front. Grab its bar to adjust it (it stays pinned); sweep across the ring again to take it off | | While carrying a screen, sweep its laser across your other controller's ring, then let go | Pins it to that wrist, at its size and distance, as you hold it when you let go; it shows while you see its front. Grab its bar to adjust it (it stays pinned); sweep across the ring again to take it off |
| Meta+Shift+R in the desktop | Puts the screens back in their layout (also the **Reset Screen Layout** menu entry, and a button you can map) | | Meta+Shift+R in the desktop | Puts the screens back in their layout (also the **Reset Screen Layout** menu entry, and a button you can map) |
| Meta+Shift+H in the desktop | Hides or shows all screens (also **Hide/Show Screens** and a mappable button). **Frametop Display Settings → Visibility & wrist** can instead show them only with the dashboard open, or while you look at your wrist | | Meta+Shift+H in the desktop | Hides or shows all screens (also **Hide/Show Screens** and a mappable button). **Frametop Display Settings → Visibility & wrist** can instead show them only with the dashboard open, or while you look at your wrist |
| Play a VR game | The screens stay up over it, and your controllers stay in the game. Work the screens with the 3D mouse, or open the SteamVR dashboard. (**Visibility & wrist → Controllers on the screens** changes this.) | | Play a VR game | The screens hide and your controllers stay in the game. Open the SteamVR dashboard (or press Meta+Shift+H) to see and use them. **Visibility & wrist → During VR games** can keep them visible over the game instead; the controllers still stay in the game, and the 3D mouse or the dashboard works the screens. |
Map the mouse's extra buttons to actions such as **Toggle SteamVR dashboard** or **Recenter pointer** in **Frametop Input Settings → Buttons**. Speed, dot size, and the rest are on its **Pointer** page and apply immediately. Map the mouse's extra buttons to actions such as **Toggle SteamVR dashboard** or **Recenter pointer** in **Frametop Input Settings → Buttons**. Speed, dot size, and the rest are on its **Pointer** page and apply immediately.
+2
View File
@@ -365,6 +365,8 @@ class Backend(QObject):
self._ask_screens(f"wrist {float(value):.1f}") self._ask_screens(f"wrist {float(value):.1f}")
elif key == "controllers": elif key == "controllers":
self._ask_screens(f"controllers {value}") self._ask_screens(f"controllers {value}")
elif key == "in_games":
self._ask_screens(f"ingames {value}")
else: else:
self._ask_screens(f"gesture {v['gesture_hand']} {float(v['gesture_angle']):.1f}") self._ask_screens(f"gesture {v['gesture_hand']} {float(v['gesture_angle']):.1f}")
+36 -1
View File
@@ -567,7 +567,7 @@ Kirigami.ApplicationWindow {
Repeater { Repeater {
model: [ model: [
{ value: "always", text: "Always", help: "They stay up, over VR games too. Meta+Shift+H (or a mapped button) hides them." }, { value: "always", text: "Always", help: "Meta+Shift+H (or a mapped button) hides them. During VR games, see below." },
{ value: "dashboard", text: "Only with the SteamVR dashboard open", help: "They come and go with the dashboard. Meta+Shift+H shows them anyway." }, { value: "dashboard", text: "Only with the SteamVR dashboard open", help: "They come and go with the dashboard. Meta+Shift+H shows them anyway." },
{ value: "gesture", text: "While I look at my wrist", help: "They show while you look toward the controller below. Meta+Shift+H shows them anyway." }, { value: "gesture", text: "While I look at my wrist", help: "They show while you look toward the controller below. Meta+Shift+H shows them anyway." },
{ value: "toggle", text: "Only when I show them", help: "Hidden until Meta+Shift+H (or a mapped button) shows them." } { value: "toggle", text: "Only when I show them", help: "Hidden until Meta+Shift+H (or a mapped button) shows them." }
@@ -615,6 +615,41 @@ Kirigami.ApplicationWindow {
Controls.Label { text: Math.round(gesture.value) + "° of it" } Controls.Label { text: Math.round(gesture.value) + "° of it" }
} }
Kirigami.Separator { Kirigami.FormData.isSection: true; Kirigami.FormData.label: "During VR games" }
Repeater {
model: [
{ value: "hide", text: "Hide them unless the SteamVR dashboard is open", help: "The game has the view to itself; open the dashboard (or press Meta+Shift+H) to see the screens." },
{ value: "visible", text: "Keep them visible over the game", help: "They float over the game as they are outside it." }
]
delegate: ColumnLayout {
required property var modelData
spacing: 0
Controls.RadioButton {
text: modelData.text
enabled: vpage.v.mode === "always"
checked: (vpage.v.in_games || "hide") === modelData.value
onToggled: if (checked) backend.setVisibility("in_games", modelData.value)
}
Controls.Label {
text: modelData.help
opacity: 0.7
font: Kirigami.Theme.smallFont
leftPadding: Kirigami.Units.gridUnit * 1.6
wrapMode: Text.Wrap
Layout.maximumWidth: Kirigami.Units.gridUnit * 26
}
}
}
Controls.Label {
visible: vpage.v.mode !== "always"
text: "Applies when the screens show \"Always\"; the other choices above already keep them out of the way."
opacity: 0.7
font: Kirigami.Theme.smallFont
wrapMode: Text.Wrap
Layout.maximumWidth: Kirigami.Units.gridUnit * 26
}
Kirigami.Separator { Kirigami.FormData.isSection: true; Kirigami.FormData.label: "Controllers on the screens" } Kirigami.Separator { Kirigami.FormData.isSection: true; Kirigami.FormData.label: "Controllers on the screens" }
Repeater { Repeater {
+2 -2
View File
@@ -31,9 +31,9 @@ Restarting the desktop (`desktops.sh restart`, or Restart desktop in Frametop Di
- Screens: each KWin window is a screen. ft-screens sizes it (`xdg_toplevel` configure) and KWin resizes that screen to match, live. Frames arrive as DMA-BUFs and go to SteamVR with `ImportDmabuf` (OpenVR's `IVRIPCResourceManagerClient`), no copy, no size limit. - Screens: each KWin window is a screen. ft-screens sizes it (`xdg_toplevel` configure) and KWin resizes that screen to match, live. Frames arrive as DMA-BUFs and go to SteamVR with `ImportDmabuf` (OpenVR's `IVRIPCResourceManagerClient`), no copy, no size limit.
- Panels: `frametop.screen.N`, with `.bar` (move), `.curve` (the round button next to it), `.roll` (the next one: drag it sideways like a knob to roll the screen, snapping level within 2.5°, or scroll on it for 5° steps), and `.resize` (the tab on the bottom right corner; screens go down to 15 cm wide). The controls are sized from both the screen's width and its distance from you, sit on its surface when it's curved, and are translucent like SteamVR's own until a laser is on them. They're invisible until a laser or the 3D mouse's cursor comes very close to one of them (about 1.5 times a button's size), and fade out a moment after it leaves. Drag the bar with any laser (a controller, or the 3D mouse, whose right-drag tilt works too); scroll while dragging to push it away or pull it closer (along the line from your head). The curve button bends the screen into a cylinder around you (radius: your distance to it), or flat again. Pin to a wrist: while carrying a screen, sweep the laser (the line from whatever carries it to its bar) across your other controller. A ring around that controller shows the target and a dot shows where the laser passes; entering the ring arms the pin (ring and bar turn blue), entering it again disarms it. Let go while armed and the screen rides on that controller as it is then, at its size and distance (a 3.6 m screen 5 m away works; so does pinning all screens), so you can arm it first and then turn it the way you want. Grab a pinned screen's bar to adjust it: it comes back to the same wrist when you let go, unless you sweep across the ring to disarm. A pinned screen shows only while you see its front within the wrist angle, fading over the last 10°. - Panels: `frametop.screen.N`, with `.bar` (move), `.curve` (the round button next to it), `.roll` (the next one: drag it sideways like a knob to roll the screen, snapping level within 2.5°, or scroll on it for 5° steps), and `.resize` (the tab on the bottom right corner; screens go down to 15 cm wide). The controls are sized from both the screen's width and its distance from you, sit on its surface when it's curved, and are translucent like SteamVR's own until a laser is on them. They're invisible until a laser or the 3D mouse's cursor comes very close to one of them (about 1.5 times a button's size), and fade out a moment after it leaves. Drag the bar with any laser (a controller, or the 3D mouse, whose right-drag tilt works too); scroll while dragging to push it away or pull it closer (along the line from your head). The curve button bends the screen into a cylinder around you (radius: your distance to it), or flat again. Pin to a wrist: while carrying a screen, sweep the laser (the line from whatever carries it to its bar) across your other controller. A ring around that controller shows the target and a dot shows where the laser passes; entering the ring arms the pin (ring and bar turn blue), entering it again disarms it. Let go while armed and the screen rides on that controller as it is then, at its size and distance (a 3.6 m screen 5 m away works; so does pinning all screens), so you can arm it first and then turn it the way you want. Grab a pinned screen's bar to adjust it: it comes back to the same wrist when you let go, unless you sweep across the ring to disarm. A pinned screen shows only while you see its front within the wrist angle, fading over the last 10°.
- Visibility (Frametop Display Settings → Visibility & wrist tab): always (Meta+Shift+H, the Hide/Show Screens menu entry, or a mapped mouse button hides them), only with the SteamVR dashboard open, while you look at a chosen controller (the wrist gesture), or only when shown with the hotkey. In the last three, the hotkey shows them anyway. Controllers on the screens (same tab): visible screens can keep SteamVR's laser mouse on so controllers work them with the dashboard closed, which also takes the controllers from a VR game. By default that's off while a VR game (a scene app) runs: the screens stay over the game, the controllers stay in it, and the 3D mouse or the dashboard works the screens. The other choices are always and only with the dashboard open (also right for flatscreen games, which aren't scene apps). Control socket: `controllers always|outside_games|dashboard`. (A controller button to show them isn't there yet: in a game the game owns the buttons.) - Visibility (Frametop Display Settings → Visibility & wrist tab): always (Meta+Shift+H, the Hide/Show Screens menu entry, or a mapped mouse button hides them), only with the SteamVR dashboard open, while you look at a chosen controller (the wrist gesture), or only when shown with the hotkey. In the last three, the hotkey shows them anyway. Controllers on the screens (same tab): visible screens can keep SteamVR's laser mouse on so controllers work them with the dashboard closed, which also takes the controllers from a VR game. By default that's off while a VR game (a scene app) runs: the screens stay over the game, the controllers stay in it, and the 3D mouse or the dashboard works the screens. The other choices are always and only with the dashboard open (also right for flatscreen games, which aren't scene apps). Control socket: `controllers always|outside_games|dashboard`. During VR games (same tab): with Always, the screens hide unless the dashboard is open (default; `ingames hide`), or stay visible (`ingames visible`); the hotkey still shows them, and a game starting or stopping resets it. (A controller button to show them isn't there yet: in a game the game owns the buttons.)
- Input: pointer from the panels to KWin through our seat; keys from the input relay (every keyboard it doesn't grab, and keys a pointer device passes through) to the screen that was clicked last, but not while the SteamVR dashboard is open. - Input: pointer from the panels to KWin through our seat; keys from the input relay (every keyboard it doesn't grab, and keys a pointer device passes through) to the screen that was clicked last, but not while the SteamVR dashboard is open.
- Control socket `@ft_screens` (datagrams, replies to the sender): `place N x y z yaw pitch roll`, `width N metres`, `curve N radius|on|off`, `pin N|all left|right [12 numbers]`, `unpin N|all`, `size N w h` (live resolution), `get N`, `screens`, `head`, `visibility always|dashboard|gesture|toggle`, `wrist degrees`, `gesture left|right degrees`, `hide | show | toggle`, `controllers always|outside_games|dashboard`, `state`, `key code value`. - Control socket `@ft_screens` (datagrams, replies to the sender): `place N x y z yaw pitch roll`, `width N metres`, `curve N radius|on|off`, `pin N|all left|right [12 numbers]`, `unpin N|all`, `size N w h` (live resolution), `get N`, `screens`, `head`, `visibility always|dashboard|gesture|toggle`, `wrist degrees`, `gesture left|right degrees`, `hide | show | toggle`, `controllers always|outside_games|dashboard`, `ingames hide|visible`, `state`, `key code value`.
## Input relay (Bluetooth mice and keyboards) ## Input relay (Bluetooth mice and keyboards)
+4 -1
View File
@@ -63,8 +63,10 @@ LOCK_PATH = "/tmp/ft-layout.lock"
DEFAULT_PANEL = (1.18, 0.664) # gamescope: a floating 16:9 dashboard panel, measured on the Frame DEFAULT_PANEL = (1.18, 0.664) # gamescope: a floating 16:9 dashboard panel, measured on the Frame
PIXELS_PER_METRE = 800 # ft-screens: a new screen's default size in VR (1920 px: 2.4 m) PIXELS_PER_METRE = 800 # ft-screens: a new screen's default size in VR (1920 px: 2.4 m)
# controllers: when controllers' lasers work the screens (always | outside_games | dashboard). # controllers: when controllers' lasers work the screens (always | outside_games | dashboard).
# in_games: during a VR game, "always" hides the screens unless the dashboard is open (hide),
# or leaves them up (visible).
VISIBILITY = {"mode": "always", "wrist_angle": 60, "gesture_hand": "left", "gesture_angle": 20, VISIBILITY = {"mode": "always", "wrist_angle": 60, "gesture_hand": "left", "gesture_angle": 20,
"controllers": "outside_games"} "controllers": "outside_games", "in_games": "hide"}
DEFAULTS = {"auto": True, "mode": "preset", DEFAULTS = {"auto": True, "mode": "preset",
"preset": {"kind": "arc", "rows": 1, "distance": 2.0, "gap": 0.05, "height": 0.0}, "preset": {"kind": "arc", "rows": 1, "distance": 2.0, "gap": 0.05, "height": 0.0},
"screens": [], "panel_size": list(DEFAULT_PANEL)} "screens": [], "panel_size": list(DEFAULT_PANEL)}
@@ -376,6 +378,7 @@ def send_visibility(sock, layout):
sock.ask(f"wrist {float(v['wrist_angle']):.1f}") sock.ask(f"wrist {float(v['wrist_angle']):.1f}")
sock.ask(f"gesture {v['gesture_hand']} {float(v['gesture_angle']):.1f}") sock.ask(f"gesture {v['gesture_hand']} {float(v['gesture_angle']):.1f}")
sock.ask(f"controllers {v['controllers']}") sock.ask(f"controllers {v['controllers']}")
sock.ask(f"ingames {v['in_games']}")
def parse_get(reply): def parse_get(reply):
+40 -8
View File
@@ -33,6 +33,8 @@
// the controllers stay in it, and the 3D mouse (its own laser mode) or the dashboard // the controllers stay in it, and the 3D mouse (its own laser mode) or the dashboard
// works the screens. Modes: always, outside_games (default), dashboard (never on its // works the screens. Modes: always, outside_games (default), dashboard (never on its
// own; also for flatscreen games, which aren't scene apps). // own; also for flatscreen games, which aren't scene apps).
// - during a VR game the screens hide unless the dashboard is open (g_inGames, default),
// or stay visible over it; the hotkey still shows them.
// OpenVR has no overlay-relative transforms here (openvr v2.15.6), so the bar, button, // OpenVR has no overlay-relative transforms here (openvr v2.15.6), so the bar, button,
// and handle are placed whenever their screen moves. // and handle are placed whenever their screen moves.
#include "vr.h" #include "vr.h"
@@ -139,6 +141,7 @@ const char *HandName(vr::TrackedDeviceIndex_t i) {
enum class Drag { None, Move, Resize, Roll }; enum class Drag { None, Move, Resize, Roll };
enum class Mode { Always, Dashboard, Gesture, Toggle }; enum class Mode { Always, Dashboard, Gesture, Toggle };
enum class Lasers { Always, OutsideGames, Dashboard }; enum class Lasers { Always, OutsideGames, Dashboard };
enum class InGames { Visible, Hide };
constexpr double kWristZone = 0.06; // the laser passing this close to a controller is on its wrist constexpr double kWristZone = 0.06; // the laser passing this close to a controller is on its wrist
constexpr double kWristLeave = 0.09; // ...and has left it beyond this (so it doesn't flicker) constexpr double kWristLeave = 0.09; // ...and has left it beyond this (so it doesn't flicker)
@@ -196,6 +199,7 @@ double g_gestureAngle = 20; // gesture: look within this of the controller
std::string g_gestureHand = "left"; std::string g_gestureHand = "left";
Lasers g_lasers = Lasers::OutsideGames; // when controllers' lasers work the screens (see the top) Lasers g_lasers = Lasers::OutsideGames; // when controllers' lasers work the screens (see the top)
bool g_gameRunning = false; // a scene app (VR game) is running bool g_gameRunning = false; // a scene app (VR game) is running
InGames g_inGames = InGames::Hide; // during a VR game, the always mode acts like the dashboard mode
// ---------------------------------------------------------------- chrome (bar, button, handle) // ---------------------------------------------------------------- chrome (bar, button, handle)
@@ -533,8 +537,25 @@ double FacingAngle(const Mat &p, const Mat &head) {
} }
// The screens' shared visibility for the mode (before a pinned screen's own facing rule). // The screens' shared visibility for the mode (before a pinned screen's own facing rule).
// The mode in effect: during a VR game (with g_inGames Hide), "always" becomes "only with
// the dashboard open", so the screens stay out of the game until you open the dashboard.
Mode EffectiveMode() {
return g_gameRunning && g_inGames == InGames::Hide && g_mode == Mode::Always ? Mode::Dashboard : g_mode;
}
// A VR game starting or stopping (checked twice a second) resets the hide/show switch, whose
// meaning depends on the mode in effect.
void UpdateGame() {
if (g_tick % 45) return;
const bool running = vr::VRApplications()->GetCurrentSceneProcessId() != 0;
if (running == g_gameRunning) return;
g_gameRunning = running;
g_manual = false;
std::printf("%s\n", running ? "a VR game started" : "the VR game ended");
}
bool ModeVisible() { bool ModeVisible() {
switch (g_mode) { switch (EffectiveMode()) {
case Mode::Always: return !g_manual; case Mode::Always: return !g_manual;
case Mode::Toggle: return g_manual; case Mode::Toggle: return g_manual;
case Mode::Dashboard: return g_manual || vr::VROverlay()->IsDashboardVisible(); case Mode::Dashboard: return g_manual || vr::VROverlay()->IsDashboardVisible();
@@ -603,9 +624,8 @@ void UpdateVisibility() {
// Controllers' lasers on the screens (see the top): the flag follows the mode and whether a // Controllers' lasers on the screens (see the top): the flag follows the mode and whether a
// VR game runs (checked twice a second). // VR game runs.
void UpdateLasers() { void UpdateLasers() {
if (g_tick % 45 == 0) g_gameRunning = vr::VRApplications()->GetCurrentSceneProcessId() != 0;
const bool want = g_lasers == Lasers::Always || (g_lasers == Lasers::OutsideGames && !g_gameRunning); const bool want = g_lasers == Lasers::Always || (g_lasers == Lasers::OutsideGames && !g_gameRunning);
for (auto &[i, s] : g_screens) { for (auto &[i, s] : g_screens) {
if (s.lasers == want) continue; if (s.lasers == want) continue;
@@ -1162,6 +1182,7 @@ void ft_vr_poll(void (*handle)(const struct ft_event *, void *), void *data) {
if (ev.eventType == vr::VREvent_TrackedDeviceDeactivated) EndDragsBy(ev.trackedDeviceIndex); if (ev.eventType == vr::VREvent_TrackedDeviceDeactivated) EndDragsBy(ev.trackedDeviceIndex);
} }
++g_tick; ++g_tick;
UpdateGame();
UpdateVisibility(); UpdateVisibility();
UpdateLasers(); UpdateLasers();
UpdateControls(); UpdateControls();
@@ -1185,8 +1206,10 @@ void ft_vr_poll(void (*handle)(const struct ft_event *, void *), void *data) {
// gesture <left|right> <degrees> the gesture mode: look within this of that controller // gesture <left|right> <degrees> the gesture mode: look within this of that controller
// hide | show | toggle the manual switch (see g_manual) // hide | show | toggle the manual switch (see g_manual)
// controllers always|outside_games|dashboard when controllers' lasers work the screens // controllers always|outside_games|dashboard when controllers' lasers work the screens
// ingames hide|visible during a VR game, "always" acts like "only with the dashboard"
// (hide), or stays as it is (visible)
// state -> "ok <mode> <manual 0|1> <wrist deg> <gesture hand> <gesture deg> // state -> "ok <mode> <manual 0|1> <wrist deg> <gesture hand> <gesture deg>
// <controllers> <game running 0|1>" // <controllers> <game running 0|1> <ingames>"
// (size <screen> <w> <h> and key <code> <value> are handled in compositor.c.) Screens are // (size <screen> <w> <h> and key <code> <value> are handled in compositor.c.) Screens are
// numbered from 1 here, like everywhere the user sees them. // numbered from 1 here, like everywhere the user sees them.
void ft_vr_command(const char *cmd, char *reply, int size) { void ft_vr_command(const char *cmd, char *reply, int size) {
@@ -1291,11 +1314,19 @@ void ft_vr_command(const char *cmd, char *reply, int size) {
g_gestureAngle = std::clamp(w, 5.0, 90.0); g_gestureAngle = std::clamp(w, 5.0, 90.0);
std::snprintf(reply, size, "ok"); std::snprintf(reply, size, "ok");
} else if (!std::strncmp(cmd, "hide", 4) || !std::strncmp(cmd, "show", 4) || !std::strncmp(cmd, "toggle", 6)) { } else if (!std::strncmp(cmd, "hide", 4) || !std::strncmp(cmd, "show", 4) || !std::strncmp(cmd, "toggle", 6)) {
const bool shownNow = g_mode == Mode::Always ? !g_manual : g_manual; const bool always = EffectiveMode() == Mode::Always;
const bool shownNow = always ? !g_manual : g_manual;
const bool want = cmd[0] == 's' ? true : cmd[0] == 'h' ? false : !shownNow; const bool want = cmd[0] == 's' ? true : cmd[0] == 'h' ? false : !shownNow;
g_manual = g_mode == Mode::Always ? !want : want; g_manual = always ? !want : want;
UpdateVisibility(); UpdateVisibility();
std::snprintf(reply, size, "ok %s", want ? "shown" : "hidden"); std::snprintf(reply, size, "ok %s", want ? "shown" : "hidden");
} else if (std::sscanf(cmd, "ingames %15s", word) == 1) {
if (!std::strcmp(word, "hide")) g_inGames = InGames::Hide;
else if (!std::strcmp(word, "visible")) g_inGames = InGames::Visible;
else return (void)std::snprintf(reply, size, "error modes: hide visible");
g_manual = false;
UpdateVisibility();
std::snprintf(reply, size, "ok %s", word);
} else if (std::sscanf(cmd, "controllers %15s", word) == 1) { } else if (std::sscanf(cmd, "controllers %15s", word) == 1) {
const std::string m = word; const std::string m = word;
if (m == "always") g_lasers = Lasers::Always; if (m == "always") g_lasers = Lasers::Always;
@@ -1305,8 +1336,9 @@ void ft_vr_command(const char *cmd, char *reply, int size) {
UpdateLasers(); UpdateLasers();
std::snprintf(reply, size, "ok %s", LasersName()); std::snprintf(reply, size, "ok %s", LasersName());
} else if (std::strncmp(cmd, "state", 5) == 0) { } else if (std::strncmp(cmd, "state", 5) == 0) {
std::snprintf(reply, size, "ok %s %d %.0f %s %.0f %s %d", ModeName(), g_manual ? 1 : 0, g_wristAngle, std::snprintf(reply, size, "ok %s %d %.0f %s %.0f %s %d %s", ModeName(), g_manual ? 1 : 0, g_wristAngle,
g_gestureHand.c_str(), g_gestureAngle, LasersName(), g_gameRunning ? 1 : 0); g_gestureHand.c_str(), g_gestureAngle, LasersName(), g_gameRunning ? 1 : 0,
g_inGames == InGames::Hide ? "hide" : "visible");
} else { } else {
std::snprintf(reply, size, "error unknown command"); std::snprintf(reply, size, "error unknown command");
} }