diff --git a/index.js b/index.js
index e3ac9f0..3301c18 100644
--- a/index.js
+++ b/index.js
@@ -779,6 +779,93 @@ class VaultGuardPlugin extends obsidian.Plugin {
await this.app.vault.modify(file, decoded);
}
+ // ========================================================================
+ // WINDOW FRAME DETECTION & CONTROLS
+ // ========================================================================
+
+ getFrameStyle() {
+ // Detected from body classes set by Obsidian at startup — authoritative,
+ // unlike app.vault.config which does not expose this in the plugin API.
+ // is-frameless + is-hidden-frameless → 'hidden' (default)
+ // is-frameless only → 'obsidian' (Obsidian custom bar)
+ // neither → 'native' (OS titlebar)
+ const body = document.body;
+ if (!body.classList.contains('is-frameless')) return 'native';
+ if (body.classList.contains('is-hidden-frameless')) return 'hidden';
+ return 'obsidian';
+ }
+
+ _platform() {
+ try { return process.platform; } catch { return 'unknown'; }
+ }
+
+ // Show a themed navbar for hidden and obsidian styles — native lets the OS handle it.
+ get needsNavbar() {
+ return this.getFrameStyle() !== 'native';
+ }
+
+ // On non-macOS with hidden or obsidian frame, our navbar covers Electron's own
+ // window controls (web content) — render replacements.
+ // macOS: traffic lights are native OS overlays above all web content, always visible.
+ get needsWindowControls() {
+ return this.needsNavbar && this._platform() !== 'darwin';
+ }
+
+ createWindowControls() {
+ const bar = document.createElement('div');
+ bar.className = 'vg-window-controls';
+
+ const buttons = [
+ {
+ cls: 'vg-wc-min',
+ label: 'Minimize',
+ svg: '',
+ action: 'minimize',
+ },
+ {
+ cls: 'vg-wc-max',
+ label: 'Maximize',
+ svg: '',
+ action: 'maximize',
+ },
+ {
+ cls: 'vg-wc-close',
+ label: 'Close',
+ svg: '',
+ action: 'close',
+ },
+ ];
+
+ for (const { cls, label, svg, action } of buttons) {
+ const btn = bar.createEl('button', {
+ cls: `vg-wc-btn ${cls}`,
+ attr: { 'aria-label': label },
+ });
+ btn.innerHTML = svg;
+ btn.addEventListener('click', () => this._winAction(action));
+ }
+
+ return bar;
+ }
+
+ _winAction(action) {
+ // Path 1: Electron remote module (Obsidian ≤ 0.x / some 1.x builds)
+ try {
+ const electron = window.require('electron');
+ const win = electron.remote?.getCurrentWindow?.();
+ if (win) {
+ if (action === 'minimize') { win.minimize(); return; }
+ if (action === 'maximize') { win.isMaximized() ? win.unmaximize() : win.maximize(); return; }
+ if (action === 'close') { win.close(); return; }
+ }
+ } catch {}
+
+ // Path 2: Obsidian command fallback (close only)
+ if (action === 'close') {
+ try { this.app.commands.executeCommandById('app:quit'); } catch {}
+ }
+ }
+
// ========================================================================
// LOCK SCREEN UI
// ========================================================================
@@ -801,7 +888,10 @@ class VaultGuardPlugin extends obsidian.Plugin {
}
_buildLockScreenUI(el) {
- el.createDiv({ cls: 'vaultguard-titlebar' });
+ if (this.needsNavbar) {
+ const navbar = el.createDiv({ cls: 'vaultguard-navbar' });
+ if (this.needsWindowControls) navbar.appendChild(this.createWindowControls());
+ }
const mainContentEl = el.createDiv({ cls: 'vaultguard-main-content' });
mainContentEl.appendChild(this.createBackgroundElement());
mainContentEl.appendChild(this.createOverlayElement());
@@ -822,8 +912,10 @@ class VaultGuardPlugin extends obsidian.Plugin {
}
_buildTamperedUI(el) {
- // Titlebar so the window remains draggable on desktop
- el.createDiv({ cls: 'vaultguard-titlebar' });
+ if (this.needsNavbar) {
+ const navbar = el.createDiv({ cls: 'vaultguard-navbar' });
+ if (this.needsWindowControls) navbar.appendChild(this.createWindowControls());
+ }
const screen = el.createDiv({ cls: 'vaultguard-tamper-screen' });
const panel = screen.createDiv({ cls: 'vaultguard-tamper-panel' });
diff --git a/styles.css b/styles.css
index 40222a0..e94fb80 100644
--- a/styles.css
+++ b/styles.css
@@ -42,16 +42,72 @@
}
/* ============================================================================
- TITLEBAR (for window dragging)
+ NAVBAR — shown for hidden and obsidian frame styles.
+ Matches Obsidian's titlebar background so it blends seamlessly.
+ native frame: omitted entirely (OS chrome handles it).
============================================================================ */
-.vaultguard-titlebar {
+.vaultguard-navbar {
flex-shrink: 0;
- height: 30px;
+ height: 34px;
width: 100%;
-webkit-app-region: drag;
- background: transparent;
+ background-color: var(--titlebar-background, var(--background-secondary));
+ position: relative;
z-index: 1;
+ display: flex;
+ align-items: center;
+}
+
+/* Match Obsidian's own focused-window titlebar colour */
+body.is-focused .vaultguard-navbar {
+ background-color: var(--titlebar-background-focused, var(--background-secondary-alt));
+}
+
+/* ============================================================================
+ WINDOW CONTROLS — rendered inside .vaultguard-navbar on Windows/Linux
+ where Electron's own controls are web content covered by the lockscreen.
+ macOS: traffic lights are native OS overlays — always visible, never needed here.
+ ============================================================================ */
+
+.vg-window-controls {
+ position: absolute;
+ top: 0;
+ right: 0;
+ height: 100%;
+ display: flex;
+ align-items: stretch;
+ -webkit-app-region: no-drag;
+}
+
+.vg-wc-btn {
+ width: 46px;
+ border: none;
+ background: transparent;
+ color: var(--titlebar-text-color, rgba(255, 255, 255, 0.55));
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ transition: background-color 0.1s ease, color 0.1s ease;
+ padding: 0;
+ -webkit-app-region: no-drag;
+}
+
+.vg-wc-btn svg {
+ width: 10px;
+ height: 10px;
+ pointer-events: none;
+}
+
+.vg-wc-btn:hover {
+ background: rgba(255, 255, 255, 0.1);
+ color: var(--titlebar-text-color, white);
+}
+
+.vg-wc-close:hover {
+ background: #c42b1c;
+ color: #fff;
}
/* ============================================================================