fix(client): move fullscreen control into session menu (#156)

The v1.7.2 floating "⛶ Fullscreen" corner button at 0.45 opacity was
still 80px of permanent clutter in the top-right of the remote session
display before fullscreen was entered. This PR moves the manual
fullscreen action into the existing Ctrl+Alt+Shift session-menu panel
(next to the Home button), removing the floating overlay entirely.

The per-entry `fullscreen_on_connect` flag and the in-fullscreen top
bar (entry name + Exit + Disconnect) are unchanged. Esc-key forwarding
via navigator.keyboard.lock still applies.

Also adds an "In-session keyboard shortcuts" section to
docs/web-sessions.md documenting the Ctrl+Alt+Shift panel toggle,
Ctrl+V clipboard paste-sync, Esc behaviour, and the disable_copy /
disable_paste interaction.

Closes #156.
This commit is contained in:
Lindsay Harvey
2026-06-09 16:25:25 +10:00
committed by GitHub
parent 0a6b27cc4b
commit 8f67a668ea
3 changed files with 25 additions and 28 deletions
+1
View File
@@ -75,6 +75,7 @@ guacd (C, from guacamole-server)
- **Connections** with folder-based organisation and OIDC group access control
- **Active Sessions** section with live thumbnail previews
- **Session ended overlay** with Reconnect/Close buttons
- **Clipboard panel controls** (Home + Fullscreen)
- **8 built-in themes** with CSS gradient backgrounds, or configure your own
- **Reports page** with session analytics, history, and CSV export
+16
View File
@@ -431,6 +431,22 @@ Clipboard copy and paste can be independently disabled per connections entry. Th
These work for all session types (SSH, RDP, VNC, Web), not just web sessions. See [Security: Clipboard control](security.md#clipboard-control) for details.
## In-session keyboard shortcuts
The session page supports a small set of browser-side shortcuts:
| Shortcut | Action | Notes |
|----------|--------|-------|
| `Ctrl+Alt+Shift` | Toggle the clipboard side panel | Works globally on the session page (capture phase), including when the remote display is focused. |
| `Ctrl+V` (Windows/Linux) or `Cmd+V` (macOS) | Sync browser clipboard text to the remote session, then send paste | If clipboard API access is available, rustguac reads local clipboard text and sends it to the remote before forwarding the paste key event. |
| `Esc` (browser fullscreen) | Exit fullscreen | Browser-native fullscreen key. rustguac may request keyboard lock while in fullscreen; if lock is unavailable, an on-screen notice reminds users to press Esc. |
Additional behavior:
- No dedicated keyboard combo is currently assigned for entering fullscreen. Users can use entry-level fullscreen-on-connect, or open the `Ctrl+Alt+Shift` clipboard panel and click its **Fullscreen** button (next to **Home**).
- All other key presses are passed through to the remote host by Guacamole keyboard handling.
- Clipboard policy flags still apply: `disable_copy` and `disable_paste` can block corresponding clipboard flows regardless of local shortcuts.
## URL placeholders
The entry URL supports credential placeholders that are URL-encoded and substituted before Chromium navigates:
+8 -28
View File
@@ -118,24 +118,6 @@
border: 1px solid var(--border) !important;
}
#btn-reconnect:hover { background: var(--border); }
#fs-toggle {
display: none;
position: fixed;
top: 10px;
right: 10px;
z-index: 1500;
background: rgba(0,0,0,0.55);
color: var(--text-muted);
border: 1px solid var(--border);
border-radius: 4px;
padding: 4px 10px;
font-family: monospace;
font-size: 14px;
cursor: pointer;
opacity: 0.45;
transition: opacity 0.2s;
}
#fs-toggle:hover { opacity: 1; color: var(--accent); }
#fs-bar {
display: none;
position: fixed;
@@ -247,7 +229,6 @@
</div>
</div>
<div id="status">Loading...</div>
<button id="fs-toggle" title="Enter fullscreen">&#9974; Fullscreen</button>
<div id="fs-bar">
<div id="fs-bar-title"></div>
<div id="fs-bar-buttons">
@@ -528,6 +509,7 @@
'<div style="display:flex;align-items:center;gap:0.6em;padding:0.8em 1em;border-bottom:1px solid var(--border);background:var(--surface);">' +
'<h3 style="margin:0;flex:1;color:var(--primary);font-size:1.1em;">Session</h3>' +
'<button id="cp-home" style="background:var(--input);border:1px solid var(--border);color:var(--accent);font-family:monospace;font-size:0.9em;padding:0.35em 0.8em;border-radius:3px;cursor:pointer;" title="Return to the Connections page">&#127968; Home</button>' +
'<button id="cp-fullscreen" style="background:var(--input);border:1px solid var(--border);color:var(--accent);font-family:monospace;font-size:0.9em;padding:0.35em 0.8em;border-radius:3px;cursor:pointer;" title="Enter fullscreen">&#9974; Fullscreen</button>' +
'<button id="cp-close" style="background:none;border:none;color:var(--text-dim);font-size:1.6em;cursor:pointer;font-family:monospace;padding:0 0.3em;">&times;</button>' +
'</div>' +
'<div style="flex:1;display:flex;flex-direction:column;padding:1em;gap:0.8em;overflow-y:auto;min-height:0;">' +
@@ -557,6 +539,10 @@
// Connections' active-session card.
window.location.href = '/connections.html';
});
document.getElementById('cp-fullscreen').addEventListener('click', function() {
rgEnterFullscreen();
if (panelOpen) toggleClipboardPanel();
});
document.getElementById('cp-send').addEventListener('click', function() {
if (clipboardTextarea.value) sendClipboardToRemote(clipboardTextarea.value);
});
@@ -1043,9 +1029,9 @@
// the prior gesture. So when the entry is marked
// fullscreen-on-connect we don't try to fire on page load.
// Instead we arm a one-shot listener for the next mousedown
// or keydown after CONNECTED. The manual gear-toggle button
// in the corner uses its own click gesture and works at any
// time once shown.
// or keydown after CONNECTED. The Ctrl+Alt+Shift menu's
// fullscreen button uses its own click gesture and works at
// any time once shown.
function rgFullscreenEl() {
return document.fullscreenElement
|| document.webkitFullscreenElement
@@ -1138,9 +1124,7 @@
}
function rgOnFullscreenChange() {
var inFs = !!rgFullscreenEl();
var tg = document.getElementById('fs-toggle');
var bar = document.getElementById('fs-bar');
if (tg) tg.style.display = inFs ? 'none' : 'block';
if (bar) {
if (inFs) {
bar.classList.add('visible');
@@ -1159,7 +1143,6 @@
}
['fullscreenchange','webkitfullscreenchange','mozfullscreenchange','MSFullscreenChange']
.forEach(function(ev){ document.addEventListener(ev, rgOnFullscreenChange); });
document.getElementById('fs-toggle').addEventListener('click', rgEnterFullscreen);
document.getElementById('fs-exit').addEventListener('click', rgExitFullscreen);
document.getElementById('fs-disconnect').addEventListener('click', function() {
rgExitFullscreen();
@@ -1179,7 +1162,6 @@
// First capture after 3s (let display render)
setTimeout(captureAndUploadThumbnail, 3000);
}
document.getElementById('fs-toggle').style.display = 'block';
document.getElementById('fs-bar-title').textContent =
entryDisplayName || ('Session ' + sessionId.substring(0,8));
if (wantFullscreen) rgArmFullscreenGesture();
@@ -1197,7 +1179,6 @@
statusEl.textContent = 'Disconnected'; statusEl.className = '';
if (_thumbInterval) { clearInterval(_thumbInterval); _thumbInterval = null; }
document.getElementById('disconnected-overlay').className = 'visible';
document.getElementById('fs-toggle').style.display = 'none';
if (rgFullscreenEl()) rgExitFullscreen();
break;
}
@@ -1212,7 +1193,6 @@
document.getElementById('disconnected-title').textContent = 'Connection Error';
document.getElementById('disconnected-message').textContent = status.message || 'An error occurred.';
document.getElementById('disconnected-overlay').className = 'visible';
document.getElementById('fs-toggle').style.display = 'none';
if (rgFullscreenEl()) rgExitFullscreen();
};