fix windows window issues

This commit is contained in:
Boof2015
2026-06-07 18:34:23 -04:00
parent bf447dd8df
commit c56b86ba63
7 changed files with 189 additions and 1 deletions
+3 -1
View File
@@ -15,6 +15,7 @@
"src/waveform.cpp",
"src/vumeter.cpp",
"src/lufsmeter.cpp",
"src/window_chrome.cpp",
"src/dsp_utils.cpp"
],
"include_dirs": [
@@ -55,7 +56,8 @@
"ole32.lib",
"avrt.lib",
"runtimeobject.lib",
"uuid.lib"
"uuid.lib",
"dwmapi.lib"
],
"msvs_settings": {
"VCCLCompilerTool": {
+2
View File
@@ -12,6 +12,7 @@
#include "waveform.h"
#include "vumeter.h"
#include "lufsmeter.h"
#include "window_chrome.h"
// Global instances
static Visualizer::Oscilloscope oscilloscope;
@@ -741,6 +742,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
RegisterMacOSCapture(env, exports);
RegisterWindowsCapture(env, exports);
RegisterLinuxCapture(env, exports);
RegisterWindowChrome(env, exports);
return exports;
}
+96
View File
@@ -0,0 +1,96 @@
#include "window_chrome.h"
#ifdef _WIN32
#include <windows.h>
#include <dwmapi.h>
// These DWM attributes/values exist on Windows 11 (build 22000+) but may be
// missing from older SDK headers, so provide fallback definitions.
#ifndef DWMWA_NCRENDERING_POLICY
#define DWMWA_NCRENDERING_POLICY 2
#endif
#ifndef DWMNCRP_DISABLED
#define DWMNCRP_DISABLED 1
#endif
#ifndef DWMWA_WINDOW_CORNER_PREFERENCE
#define DWMWA_WINDOW_CORNER_PREFERENCE 33
#endif
#ifndef DWMWA_BORDER_COLOR
#define DWMWA_BORDER_COLOR 34
#endif
#ifndef DWMWCP_DONOTROUND
#define DWMWCP_DONOTROUND 1
#endif
#ifndef DWMWA_COLOR_NONE
#define DWMWA_COLOR_NONE 0xFFFFFFFE
#endif
namespace {
Napi::Value ApplyFlatFrame(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsBuffer()) {
Napi::TypeError::New(env, "Expected native window handle Buffer")
.ThrowAsJavaScriptException();
return env.Undefined();
}
Napi::Buffer<uint8_t> handle = info[0].As<Napi::Buffer<uint8_t>>();
if (handle.Length() < sizeof(HWND)) {
Napi::TypeError::New(env, "Window handle Buffer is too small")
.ThrowAsJavaScriptException();
return env.Undefined();
}
HWND hwnd = *reinterpret_cast<HWND*>(handle.Data());
if (hwnd == nullptr || !IsWindow(hwnd)) {
return Napi::Boolean::New(env, false);
}
// The window keeps WS_THICKFRAME so native Aero Snap and edge resize work, but
// on Windows 11 that style makes DWM paint a visible border and a drop shadow
// ("depth"). Disabling DWM non-client rendering removes that shadow/border,
// and squaring the corners + clearing the accent border color restores the old
// flat, borderless look. All four calls are no-ops/benign on Windows 10 and
// fail harmlessly on unsupported builds.
DWORD ncRendering = DWMNCRP_DISABLED;
DwmSetWindowAttribute(hwnd, DWMWA_NCRENDERING_POLICY, &ncRendering, sizeof(ncRendering));
DWORD cornerPreference = DWMWCP_DONOTROUND;
DwmSetWindowAttribute(hwnd, DWMWA_WINDOW_CORNER_PREFERENCE, &cornerPreference, sizeof(cornerPreference));
COLORREF borderColor = DWMWA_COLOR_NONE;
DwmSetWindowAttribute(hwnd, DWMWA_BORDER_COLOR, &borderColor, sizeof(borderColor));
MARGINS margins = {0, 0, 0, 0};
DwmExtendFrameIntoClientArea(hwnd, &margins);
return Napi::Boolean::New(env, true);
}
} // namespace
void RegisterWindowChrome(Napi::Env env, Napi::Object exports) {
Napi::Object chrome = Napi::Object::New(env);
chrome.Set("applyFlatFrame", Napi::Function::New(env, ApplyFlatFrame));
exports.Set("windowChrome", chrome);
}
#else // !_WIN32
namespace {
Napi::Value ApplyFlatFrameNoop(const Napi::CallbackInfo& info) {
return Napi::Boolean::New(info.Env(), false);
}
} // namespace
void RegisterWindowChrome(Napi::Env env, Napi::Object exports) {
Napi::Object chrome = Napi::Object::New(env);
chrome.Set("applyFlatFrame", Napi::Function::New(env, ApplyFlatFrameNoop));
exports.Set("windowChrome", chrome);
}
#endif // _WIN32
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include <napi.h>
// Registers the `windowChrome` export. On Windows it exposes `applyFlatFrame`,
// which strips the visible native frame/shadow from a frameless WS_THICKFRAME
// window while preserving native Aero Snap and edge resize. On other platforms
// it registers a no-op so the JS surface stays uniform.
void RegisterWindowChrome(Napi::Env env, Napi::Object exports);
+35
View File
@@ -33,6 +33,7 @@ import {
import { calculateResizedWindowBounds } from '../shared/windowResize'
import { FileBackedProfileLibrary } from './profileLibrary'
import { loadNativeWindowsMediaApi } from './nativeWindowsMedia'
import { loadNativeWindowChromeApi } from './nativeWindowChrome'
import { NowPlayingManager } from './services/nowPlayingManager'
import { AstraIntegrationService } from './services/astraIntegration'
import { MacSpotifyProvider } from './services/macSpotifyProvider'
@@ -41,6 +42,7 @@ import { checkForUpdates, resolveSafeReleaseUrl } from './services/updates'
import { FileBackedThemeLibrary } from './themeLibrary'
import { FileBackedWindowStateStore } from './windowStateStore'
import type { NativeWindowsMediaAPI } from '../types/nativeWindowsMedia'
import type { NativeWindowChromeAPI } from '../types/nativeWindowChrome'
let mainWindow: BrowserWindow | null = null
let moveInterval: ReturnType<typeof setInterval> | null = null
@@ -74,6 +76,7 @@ let nowPlayingManager: NowPlayingManager | null = null
let windowStateStore: FileBackedWindowStateStore | null = null
let secretVault: SecretVault | null = null
let nativeWindowsMediaApi: NativeWindowsMediaAPI | null | undefined
let nativeWindowChromeApi: NativeWindowChromeAPI | null | undefined
const WINDOW_DEFAULTS = {
width: 900,
@@ -350,6 +353,35 @@ function getNativeWindowsMediaApi(): NativeWindowsMediaAPI | null {
return nativeWindowsMediaApi
}
function getNativeWindowChromeApi(): NativeWindowChromeAPI | null {
if (nativeWindowChromeApi !== undefined) {
return nativeWindowChromeApi
}
nativeWindowChromeApi = loadNativeWindowChromeApi()
return nativeWindowChromeApi
}
// Strips the visible native frame/shadow from a frameless WS_THICKFRAME window so
// it looks flat again, while keeping native Aero Snap and edge resize. Windows-only;
// a no-op on macOS/Linux.
function applyFlatFramelessChrome(window: BrowserWindow): void {
if (process.platform !== 'win32') {
return
}
const api = getNativeWindowChromeApi()
if (!api) {
return
}
try {
api.applyFlatFrame(window.getNativeWindowHandle())
} catch {
// Frame styling is purely cosmetic; never block window creation on it.
}
}
function getNowPlayingManager(): NowPlayingManager {
if (!nowPlayingManager) {
nowPlayingManager = new NowPlayingManager({
@@ -1009,6 +1041,7 @@ function createMainWindow(): void {
backgroundThrottling: false,
},
})
applyFlatFramelessChrome(mainWindow)
syncMainWindowLogicalBounds(mainWindow)
mainWindow.on('close', (event) => {
@@ -1218,6 +1251,7 @@ function createScopePopoutWindow(kind: ScopeKind, rawBounds?: WindowBounds): Bro
}
const popoutWindow = new BrowserWindow(options)
applyFlatFramelessChrome(popoutWindow)
setSettingsHeightForWindow(popoutWindow, 0)
scopePopoutWindows.set(kind, popoutWindow)
@@ -1385,6 +1419,7 @@ function createNowPlayingConfigWindow(): BrowserWindow {
}
nowPlayingConfigWindow = new BrowserWindow(options)
applyFlatFramelessChrome(nowPlayingConfigWindow)
const configWindow = nowPlayingConfigWindow
+37
View File
@@ -0,0 +1,37 @@
import { createRequire } from 'node:module'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import type { VisualizerDSP } from '../renderer/audio/native/visualizer-dsp'
import type { NativeCaptureAPI } from '../types/nativeCapture'
import type { NativeWindowChromeAPI } from '../types/nativeWindowChrome'
type NativeAddonModule = VisualizerDSP & NativeCaptureAPI & {
windowChrome?: NativeWindowChromeAPI
}
const require = createRequire(import.meta.url)
const currentDir = dirname(fileURLToPath(import.meta.url))
export function loadNativeWindowChromeApi(): NativeWindowChromeAPI | null {
if (process.platform !== 'win32') {
return null
}
const candidatePaths = [
join(currentDir, '../../native/build/Release/visualizer_dsp.node'),
process.resourcesPath ? join(process.resourcesPath, 'native/visualizer_dsp.node') : null,
].filter((value): value is string => value !== null)
for (const modulePath of candidatePaths) {
try {
const nativeAddon = require(modulePath) as NativeAddonModule
if (nativeAddon.windowChrome) {
return nativeAddon.windowChrome
}
} catch {
// Try the next candidate path.
}
}
return null
}
+7
View File
@@ -0,0 +1,7 @@
export interface NativeWindowChromeAPI {
// Strips the visible native frame/shadow from a frameless WS_THICKFRAME window
// while preserving native snapping and edge resize. Returns false if the handle
// was invalid or the subclass could not be installed. Windows-only; a no-op
// elsewhere.
applyFlatFrame: (nativeWindowHandle: Buffer) => boolean
}