feat(foreground): track topmost process + browser page title
Lint & Test / test (push) Failing after 8s
Lint & Test / test (push) Failing after 8s
Adds cross-platform foreground-window tracking and exposes it over REST (/api/foreground) and the existing WebSocket feed. - foreground_service.py: Windows probe via ctypes (HANDLE-correct argtypes to avoid 64-bit handle truncation); macOS via AppKit; Linux via Xlib (Wayland returns unavailable). TTL cache + per-platform fallback. - browser_url_service.py: when foreground is a recognised browser, extract the page title from the window title (browser-name suffix stripped) and surface `is_browser` + `browser_page_title`. Optional UIA-based URL extraction behind MEDIA_SERVER_BROWSER_UIA env flag (off by default — Chromium browsers keep their accessibility tree dormant otherwise). - websocket_manager: poll foreground every 1s inside the existing status loop, broadcast `foreground` on connect and `foreground_update` on change. Diff only on user-visible fields to avoid geometry spam. - WebUI: new editorial card rendered under the monitor list on the Display tab — process name, window title, fullscreen/minimized/monitor chips, browser block when applicable, exe path, PID, started-ago, geometry, platform. 16px inter-section gap matches Settings cadence. - i18n: 25 new keys added to both en.json and ru.json. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9321,3 +9321,321 @@ body.is-fullscreen-player .now-playing .vu-meter {
|
||||
body.is-fullscreen-player .fs-bloom #fs-bloom-art { animation: none !important; }
|
||||
:root[data-theme="light"] body.is-fullscreen-player .fs-bloom { opacity: 0.22; }
|
||||
}
|
||||
|
||||
/* ════════════════════════════════════════════════════════════════
|
||||
FOREGROUND container — editorial process plate
|
||||
════════════════════════════════════════════════════════════════ */
|
||||
.foreground-container {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
margin-top: 28px;
|
||||
}
|
||||
|
||||
.foreground-stage {
|
||||
min-height: 360px;
|
||||
}
|
||||
|
||||
/* Match the inter-section gap used between .settings-section blocks
|
||||
in the Settings tab — keeps cadence consistent across tabs. */
|
||||
.display-container > * + * {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.foreground-card {
|
||||
position: relative;
|
||||
display: block;
|
||||
padding: clamp(24px, 3vw, 40px) clamp(24px, 3vw, 40px) 28px;
|
||||
border: 1px solid var(--rule);
|
||||
border-top: 2px solid var(--copper);
|
||||
background:
|
||||
radial-gradient(120% 80% at 0% 0%, rgba(var(--copper-rgb), 0.05), transparent 60%),
|
||||
var(--bg-paper);
|
||||
box-shadow:
|
||||
0 1px 0 var(--bg-paper),
|
||||
0 28px 60px -28px rgba(0, 0, 0, 0.45),
|
||||
0 8px 20px -10px rgba(0, 0, 0, 0.25);
|
||||
}
|
||||
.foreground-card[data-fullscreen="1"] {
|
||||
border-top-color: var(--copper-hi);
|
||||
box-shadow:
|
||||
0 1px 0 var(--bg-paper),
|
||||
0 28px 60px -28px rgba(0, 0, 0, 0.55),
|
||||
0 0 0 1px rgba(var(--copper-rgb), 0.18),
|
||||
0 0 60px -12px var(--copper-glow);
|
||||
}
|
||||
|
||||
.foreground-card .fg-kicker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.32em;
|
||||
text-transform: uppercase;
|
||||
color: var(--copper);
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
.foreground-card .fg-kicker::before,
|
||||
.foreground-card .fg-kicker::after {
|
||||
content: "";
|
||||
height: 1px;
|
||||
background: var(--copper);
|
||||
opacity: 0.6;
|
||||
flex: 0 0 24px;
|
||||
}
|
||||
.foreground-card .fg-kicker::after { flex: 1 0 auto; }
|
||||
|
||||
.foreground-card .fg-process {
|
||||
font-family: var(--serif);
|
||||
font-weight: 400;
|
||||
font-size: clamp(34px, 4.4vw, 56px);
|
||||
line-height: 1.02;
|
||||
letter-spacing: -0.02em;
|
||||
font-variation-settings: 'opsz' 144;
|
||||
color: var(--ink);
|
||||
margin: 0 0 10px;
|
||||
word-break: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
transition: color 180ms var(--ease, ease);
|
||||
}
|
||||
.foreground-card .fg-process:hover {
|
||||
color: var(--copper-hi);
|
||||
}
|
||||
|
||||
.foreground-card .fg-window-title {
|
||||
font-family: var(--serif);
|
||||
font-style: italic;
|
||||
font-size: 20px;
|
||||
font-weight: 300;
|
||||
color: var(--ink-soft);
|
||||
font-variation-settings: 'opsz' 60;
|
||||
margin-bottom: 22px;
|
||||
line-height: 1.35;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-word;
|
||||
}
|
||||
.foreground-card .fg-window-title:empty { display: none; }
|
||||
|
||||
.foreground-card .fg-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.foreground-card .fg-chips:empty { display: none; }
|
||||
|
||||
.fg-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 5px 11px;
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-soft);
|
||||
background: transparent;
|
||||
border: 1px solid var(--rule-strong);
|
||||
border-radius: 999px;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.fg-chip.fg-chip-accent {
|
||||
color: var(--copper);
|
||||
border-color: var(--copper);
|
||||
background: rgba(var(--copper-rgb), 0.07);
|
||||
}
|
||||
.fg-chip.fg-chip-mute {
|
||||
color: var(--ink-mute);
|
||||
border-color: var(--rule);
|
||||
}
|
||||
|
||||
.foreground-card .fg-details {
|
||||
display: block;
|
||||
margin: 0;
|
||||
border-top: 1px solid var(--rule);
|
||||
}
|
||||
.foreground-card .fg-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(160px, 220px) 1fr;
|
||||
gap: 24px;
|
||||
padding: 14px 0;
|
||||
border-bottom: 1px solid var(--rule);
|
||||
align-items: baseline;
|
||||
min-width: 0;
|
||||
}
|
||||
.foreground-card .fg-row dt {
|
||||
font-family: var(--mono);
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--copper);
|
||||
margin: 0;
|
||||
}
|
||||
.foreground-card .fg-row dd {
|
||||
font-family: var(--serif);
|
||||
font-style: italic;
|
||||
font-size: 18px;
|
||||
color: var(--ink);
|
||||
font-variation-settings: 'opsz' 30;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.foreground-card .fg-mono {
|
||||
font-family: var(--mono);
|
||||
font-style: normal;
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.02em;
|
||||
color: var(--ink-soft);
|
||||
font-variant-numeric: tabular-nums;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.foreground-empty {
|
||||
padding: 60px 24px;
|
||||
text-align: center;
|
||||
color: var(--ink-mute);
|
||||
}
|
||||
.foreground-empty svg {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-bottom: 14px;
|
||||
opacity: 0.55;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
.foreground-empty p {
|
||||
font-family: var(--serif);
|
||||
font-style: italic;
|
||||
font-size: 18px;
|
||||
color: var(--ink-soft);
|
||||
margin: 0;
|
||||
}
|
||||
.foreground-empty .foreground-empty-error {
|
||||
margin-top: 10px;
|
||||
font-family: var(--mono);
|
||||
font-style: normal;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.06em;
|
||||
color: var(--ink-mute);
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* ─── Header status badge ──────────────────────────────────── */
|
||||
.foreground-status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
height: 32px;
|
||||
padding: 0 12px 0 10px;
|
||||
margin-right: 4px;
|
||||
background: transparent;
|
||||
border: 1px solid var(--rule-strong);
|
||||
border-radius: 999px;
|
||||
color: var(--ink-soft);
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.04em;
|
||||
cursor: pointer;
|
||||
max-width: 240px;
|
||||
transition: color 180ms ease, border-color 180ms ease, background 180ms ease;
|
||||
}
|
||||
.foreground-status-badge:hover {
|
||||
color: var(--ink);
|
||||
border-color: var(--copper);
|
||||
background: rgba(var(--copper-rgb), 0.06);
|
||||
}
|
||||
.foreground-status-badge.hidden { display: none !important; }
|
||||
|
||||
.foreground-status-badge .fg-badge-mark {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--ink-mute);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.foreground-status-badge.is-media .fg-badge-mark,
|
||||
.foreground-status-badge.is-fullscreen .fg-badge-mark {
|
||||
background: var(--copper);
|
||||
box-shadow: 0 0 8px var(--copper-glow);
|
||||
}
|
||||
.foreground-status-badge.is-fullscreen {
|
||||
border-color: var(--copper);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.foreground-status-badge .fg-badge-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 140px;
|
||||
}
|
||||
.foreground-status-badge .fg-badge-tag {
|
||||
color: var(--copper);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.14em;
|
||||
font-size: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.foreground-status-badge .fg-badge-tag.hidden { display: none; }
|
||||
|
||||
/* ─── Light theme overrides ──────────────────────────────── */
|
||||
:root[data-theme="light"] .foreground-card {
|
||||
background:
|
||||
radial-gradient(120% 80% at 0% 0%, rgba(var(--copper-rgb), 0.05), transparent 60%),
|
||||
var(--bg-paper);
|
||||
box-shadow:
|
||||
0 1px 0 var(--bg-paper),
|
||||
0 22px 50px -24px rgba(26, 23, 21, 0.20),
|
||||
0 6px 16px -8px rgba(26, 23, 21, 0.12);
|
||||
}
|
||||
:root[data-theme="light"] .foreground-card[data-fullscreen="1"] {
|
||||
box-shadow:
|
||||
0 1px 0 var(--bg-paper),
|
||||
0 22px 50px -24px rgba(26, 23, 21, 0.28),
|
||||
0 0 0 1px rgba(var(--copper-rgb), 0.20),
|
||||
0 0 50px -12px var(--copper-glow);
|
||||
}
|
||||
:root[data-theme="light"] .foreground-status-badge {
|
||||
border-color: rgba(26, 23, 21, 0.18);
|
||||
}
|
||||
:root[data-theme="light"] .foreground-status-badge:hover {
|
||||
background: rgba(var(--copper-rgb), 0.08);
|
||||
}
|
||||
|
||||
/* ─── Mobile breakpoint ──────────────────────────────────── */
|
||||
@media (max-width: 720px) {
|
||||
.foreground-card {
|
||||
padding: 22px 18px 20px;
|
||||
}
|
||||
.foreground-card .fg-process {
|
||||
font-size: 30px;
|
||||
}
|
||||
.foreground-card .fg-window-title {
|
||||
font-size: 16px;
|
||||
}
|
||||
.foreground-card .fg-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 4px;
|
||||
padding: 12px 0;
|
||||
}
|
||||
.foreground-card .fg-row dd {
|
||||
font-size: 16px;
|
||||
}
|
||||
.foreground-status-badge {
|
||||
max-width: 160px;
|
||||
}
|
||||
.foreground-status-badge .fg-badge-name {
|
||||
max-width: 80px;
|
||||
}
|
||||
.foreground-status-badge .fg-badge-tag {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user