Improve live input indicator visibility
Some checks failed
ci-bundle.yml / Improve live input indicator visibility (push) Failing after 0s
ci-copr.yml / Improve live input indicator visibility (push) Failing after 0s
ci-homebrew.yml / Improve live input indicator visibility (push) Failing after 0s

This commit is contained in:
Joey Yakimowich-Payne 2026-02-11 15:33:10 -07:00
commit 3f19554cfa
2 changed files with 39 additions and 7 deletions

View file

@ -23,9 +23,9 @@ namespace stream {
constexpr auto CONTROL_PORT = 10; constexpr auto CONTROL_PORT = 10;
constexpr auto AUDIO_STREAM_PORT = 11; constexpr auto AUDIO_STREAM_PORT = 11;
constexpr uint64_t KB_ACTIVE_WINDOW_MS = 500; constexpr uint64_t KB_ACTIVE_WINDOW_MS = 1500;
constexpr uint64_t MOUSE_ACTIVE_WINDOW_MS = 500; constexpr uint64_t MOUSE_ACTIVE_WINDOW_MS = 1500;
constexpr uint64_t GAMEPAD_ACTIVE_WINDOW_MS = 500; constexpr uint64_t GAMEPAD_ACTIVE_WINDOW_MS = 1500;
struct session_t; struct session_t;

View file

@ -151,7 +151,7 @@
<div class="card my-4"> <div class="card my-4">
<div class="card-body"> <div class="card-body">
<h2 id="input_status">Live Input Status</h2> <h2 id="input_status">Live Input Status</h2>
<p>Per-session input activity and policy. Indicators update every second.</p> <p>Per-session input activity and policy. Indicators update twice per second.</p>
</div> </div>
<div v-if="activeSessions.length === 0" class="card-body pt-0"> <div v-if="activeSessions.length === 0" class="card-body pt-0">
<em>No active streaming sessions.</em> <em>No active streaming sessions.</em>
@ -170,6 +170,9 @@
<span :class="['badge', s.policy.allow_keyboard ? 'bg-success' : 'bg-secondary']" style="font-size: 0.7em"> <span :class="['badge', s.policy.allow_keyboard ? 'bg-success' : 'bg-secondary']" style="font-size: 0.7em">
{{ s.policy.allow_keyboard ? 'ON' : 'OFF' }} {{ s.policy.allow_keyboard ? 'ON' : 'OFF' }}
</span> </span>
<span :class="['activity-state', s.activity.keyboard_active ? 'text-success' : 'text-secondary']">
{{ activityStateLabel(s.activity.keyboard_active, s.activity.last_keyboard_ms_ago) }}
</span>
</span> </span>
<span class="d-flex align-items-center gap-1" title="Mouse"> <span class="d-flex align-items-center gap-1" title="Mouse">
<span :class="['activity-dot', s.activity.mouse_active ? 'dot-green' : 'dot-gray']"></span> <span :class="['activity-dot', s.activity.mouse_active ? 'dot-green' : 'dot-gray']"></span>
@ -177,6 +180,9 @@
<span :class="['badge', s.policy.allow_mouse ? 'bg-success' : 'bg-secondary']" style="font-size: 0.7em"> <span :class="['badge', s.policy.allow_mouse ? 'bg-success' : 'bg-secondary']" style="font-size: 0.7em">
{{ s.policy.allow_mouse ? 'ON' : 'OFF' }} {{ s.policy.allow_mouse ? 'ON' : 'OFF' }}
</span> </span>
<span :class="['activity-state', s.activity.mouse_active ? 'text-success' : 'text-secondary']">
{{ activityStateLabel(s.activity.mouse_active, s.activity.last_mouse_ms_ago) }}
</span>
</span> </span>
<span class="d-flex align-items-center gap-1" title="Gamepad"> <span class="d-flex align-items-center gap-1" title="Gamepad">
<span :class="['activity-dot', s.activity.gamepad_active ? 'dot-green' : 'dot-gray']"></span> <span :class="['activity-dot', s.activity.gamepad_active ? 'dot-green' : 'dot-gray']"></span>
@ -184,6 +190,9 @@
<span :class="['badge', s.policy.allow_gamepad ? 'bg-success' : 'bg-secondary']" style="font-size: 0.7em"> <span :class="['badge', s.policy.allow_gamepad ? 'bg-success' : 'bg-secondary']" style="font-size: 0.7em">
{{ s.policy.allow_gamepad ? 'ON' : 'OFF' }} {{ s.policy.allow_gamepad ? 'ON' : 'OFF' }}
</span> </span>
<span :class="['activity-state', s.activity.gamepad_active ? 'text-success' : 'text-secondary']">
{{ activityStateLabel(s.activity.gamepad_active, s.activity.last_gamepad_ms_ago) }}
</span>
</span> </span>
</div> </div>
</div> </div>
@ -436,7 +445,7 @@
}, 5000); }, 5000);
this.sessionInterval = setInterval(() => { this.sessionInterval = setInterval(() => {
this.refreshActiveSessions(); this.refreshActiveSessions();
}, 1000); }, 500);
this.refreshLogs(); this.refreshLogs();
this.refreshClients(); this.refreshClients();
this.refreshActiveSessions(); this.refreshActiveSessions();
@ -446,6 +455,21 @@
clearInterval(this.sessionInterval); clearInterval(this.sessionInterval);
}, },
methods: { methods: {
activityStateLabel(active, msAgo) {
if (active) {
return 'LIVE';
}
if (msAgo < 0) {
return 'never';
}
if (msAgo < 1000) {
return `${msAgo}ms`;
}
return `${(msAgo / 1000).toFixed(1)}s`;
},
refreshActiveSessions() { refreshActiveSessions() {
fetch("./api/sessions/active") fetch("./api/sessions/active")
.then((r) => r.json()) .then((r) => r.json())
@ -677,18 +701,26 @@
<style> <style>
.activity-dot { .activity-dot {
display: inline-block; display: inline-block;
width: 10px; width: 12px;
height: 10px; height: 12px;
border-radius: 50%; border-radius: 50%;
transition: background-color 0.2s; transition: background-color 0.2s;
border: 1px solid #9ca3af;
} }
.dot-green { .dot-green {
background-color: #22c55e; background-color: #22c55e;
box-shadow: 0 0 4px #22c55e; box-shadow: 0 0 4px #22c55e;
border-color: #16a34a;
} }
.dot-gray { .dot-gray {
background-color: #6b7280; background-color: #6b7280;
} }
.activity-state {
min-width: 3.25rem;
font-size: 0.72em;
text-transform: uppercase;
letter-spacing: 0.02em;
}
</style> </style>
</body> </body>