Skip to content

Windows theme integration

ALTaleX edited this page Oct 15, 2026 · 2 revisions

Windows theme integration

The theme extension connects Windows theme application and the classic color panel to OpenGlass through themeui.dll and themecpl.dll. It is intended for developers building customized components and requires OpenGlass for the glass effects. Build it from source or download the separate unsigned DLL/PDB CI artifact. The extension is not included in formal Release assets. The OpenGlass installer does not include, register, update or remove it.

This is a developer prototype with manually managed registration and process lifetime. The color hooks were investigated on Windows binaries 10.0.26100.9549. The core compatibility table does not establish extension compatibility on other builds. If DUI70 cannot be loaded, a required DUI export is missing or the required imports cannot be resolved, activation returns the original COM factory without installing that activation's hooks. Hook transaction errors still fail fast; this capability check does not validate private page layouts.

Build and customize

Build OpenGlassThemeExtension/OpenGlassThemeExtension.vcxproj in Release|x64, or build the full solution, which includes the extension. The default output is Build\x64\Release\common\OpenGlassThemeExtension.dll, ready to copy to its installation directory. CI also compiles it with the solution and uploads an unmodified developer build with its matching PDB as a separate artifact. Building OpenGlass covers prerequisites and build commands.

  • Core.cpp implements all four COM exports, TrustedInstaller impersonation and registration. It dispatches activation by proxy CLSID, calls the registered downstream server's class factory export, then initializes the corresponding system module's hooks before returning the factory result to COM.
  • ColorHooks.cpp, ColorHooks.Shell.cpp and Colorization.cpp implement the color integration. The Windows 7 balance and Vista opacity calculations are shared with the GUI in Common/Colorization.hpp; Automatic uses the shared Common/ShellColorRefresh.hpp request.
  • DuiHooks.cpp and the embedded ColorPage.xml extend the classic color page using native DUI controls and exported DUI70 functions. Transparency rollback joins the native navigation handler using a private layout verified in x64 themecpl 10.0.22621.4745 and 10.0.26100.9549; no runtime symbols are required.

CLSID constants and registration routes are local to Core.cpp; each hook module exposes one Prepare function, and Core queues the hooks only after all preparation succeeds. Change the project's Target Name (TargetName) when customizing the filename. Registration reads the DLL's own full path. There is no separate component-name configuration and no signing setup is needed.

The installation examples below use the default name; register the renamed DLL for a customized build.

Install

  1. Copy the DLL to a fixed, administrator-protected directory outside the main OpenGlass installation directory, for example C:\Program Files\OpenGlassThemeExtension.

  2. From an elevated 64-bit PowerShell, register the DLL, adjusting the path if needed:

    & "$env:SystemRoot\System32\regsvr32.exe" "C:\Program Files\OpenGlassThemeExtension\OpenGlassThemeExtension.dll"
  3. Restart Settings and any personalization hosts so they use the new registration.

  4. We recommend adding --scope=hkcu to the OpenGlass GUI shortcut's command line so ordinary edits target the same user layer as the extension.

The single DLL's DllRegisterServer registers all three proxy classes to its own path. See the repository's LICENSE.txt for licensing terms. The extension remains unsigned even in ReleaseSigned.

Update, move or remove

  1. Before replacing, moving or deleting the DLL, unregister the currently installed copy from an elevated 64-bit PowerShell:

    & "$env:SystemRoot\System32\regsvr32.exe" /u "C:\Program Files\OpenGlassThemeExtension\OpenGlassThemeExtension.dll"
  2. Exit every process that loaded the extension. Closing a window alone may leave its host running. Sign out or restart Windows if necessary. Unregistration does not unload existing objects or pinned hooks.

  3. For an update or move, replace the DLL or move it to the new protected directory, then register it at its final path. For removal, delete the files after their hosts have exited.

If unregistration fails, retain the DLL and resolve the failure before replacing or deleting it. Updating or uninstalling OpenGlass itself does not perform these steps for the extension.

Migration from the three-DLL build

Unregister the old core DLL and exit every host that loaded the extension. Remove the old UI/CPL DLLs, replace the core with the new single DLL and register it. The three proxy CLSIDs remain unchanged; their InprocServer32 entries now all point to the same DLL.

Migration from an installer-managed prototype

If an earlier prototype was registered from the main OpenGlass installation directory, unregister it using its old path, then exit its hosts before moving to a separate directory and registering again. Earlier installer records may still remove files from the old directory when OpenGlass is uninstalled. New installers do not migrate or remove old registrations.

Configuration scope and colors

The extension writes appearance values under the current user's HKCU\Software\Microsoft\Windows\DWM key. Its machine-wide COM registration is separate from this user configuration. Using --scope=hkcu aligns ordinary GUI edits with the extension; it does not change the GUI's default scope or the registration scope. Both HKCU and HKLM remain valid OpenGlass configuration layers. See Configuration and registry reference for precedence.

Windows transparency is a separate current-user preference. The extension's checkbox always edits EnableTransparency. The GUI's --no-system-transparency option only disables its own mapping between the preset field ColorizationOpaqueBlend and Windows transparency; it does not change extension behavior. The extension leaves both DWM ColorizationOpaqueBlend values untouched.

Each intercepted color setter writes:

Values Behavior
ColorizationColor, ColorizationAfterglow Preserve both incoming ARGB values, including their alpha bytes.
AccentColor Convert the incoming Color RGB to opaque COLORREF (0xFFBBGGRR).
ColorizationColorBalanceOverride, ColorizationAfterglowBalanceOverride, ColorizationBlurBalanceOverride Recalculate Windows 7 glass balances from color alpha and the transparency conditions below, using the shared GUI formula.
GlassOpacity Convert color alpha to a percentage for Vista glass, using the shared GUI formula.

When the setter's skipPersistence argument is zero, the extension also writes the same opaque COLORREF to HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\Accent\AccentColorMenu after completing the DWM writes. A nonzero argument leaves this preference unchanged. After these writes, the extension directly sends the DWM refresh notification using SendMessageTimeoutW with a one-second timeout. This sends the ImmersiveColorSet category and no color snapshot; DWM reads the current registry values. Notification delivery remains best effort. The extension deliberately ignores color policies for this synchronization, without modifying the policies themselves.

Each setter calls Util::IsTransparencyEnabled() from Common/SystemSettings.hpp to read EnableTransparency from HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize, defaulting to zero. The shared DwmSettings::IsTransparencyDisabled predicate selects the opaque formula when ColorizationOpaqueBlend is nonzero, EnableTransparency is zero, or power saver is active and DisableGlassOnBattery is nonzero. DisableGlassOnBattery follows HKCU, then HKLM, then the default of 1. Each setter also calls Util::IsEnergySaverEnabled() from the same header, shared with both renderers and GUI diagnostics; DisableGlassOnBattery controls whether its result affects the formula. This synchronously queries GUID_POWER_SAVING_STATUS through NtPowerInformation. An active Battery Saver result needs no further query; otherwise it queries GUID_ENERGY_SAVER_STATUS, treating an unsupported new GUID as inactive. Other query failures are logged and return false. Both Standard and High Savings count as active, without an OS-version threshold or notification subscription. Each query reads the first DWORD entry directly because these status GUIDs publish the same state for AC and DC. Power changes alone do not rewrite balances; the next intercepted setter queries the current state.

Base balances and the unused EnableWindowColorization / ColorizationGlassAttribute keys are neither written nor removed. Existing GlassOpacityInactive remains independent. The three user balance Overrides take precedence over machine balance Overrides and remain until changed or removed. Existing ColorizationColorOverride or ColorizationAfterglowOverride values can still mask the incoming base colors; the extension does not remove them.

The hooked getter reads the Windows colors and effective OpenGlass balances and opacity so the classic panel reflects GUI intensity changes. It also handles the classic panel's special decoding of alpha FF.

Preview, Save, Cancel and Automatic

The classic color page displays the selected swatch's name in a left-aligned Current color: row below the swatches, including the native Automatic name. A mixed color with no selected swatch displays Customized. Swatch names retain Windows localization. The extension's prefix, checkbox text, fallback, accessibility descriptions and default-action text support English, Simplified Chinese and Traditional Chinese, selected through Windows' normal UI-language fallback. Native swatch templates and their accessibility strings still come from Windows. The intensity slider retains its native 10..85 range.

The Enable transparency checkbox directly writes EnableTransparency as a DWORD 0 or 1 under HKCU\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize, without ever deleting it. Only a successfully read nonzero DWORD counts as enabled; missing, invalid or unreadable values display as disabled. Its change listener then uses the color setter to recalculate OpenGlass balances and send the WM_SETTINGCHANGE / ImmersiveColorSet notification directly to DWM. Equal preferences skip the write and refresh. It changes the current user's Windows transparency preference, independently of GUI editing scope. ColorizationOpaqueBlend and the power-saver conditions described above still affect effective glass. A failed registry write restores the checkbox to the current Windows preference.

During rollback, the preference is restored before the checkbox, so the resulting checkbox notification skips the write and refresh. The extension leaves color refresh to the native color cancellation path. Its checkpoint stores only the initial boolean, unlike the GUI's raw-value checkpoint: pending changes restore DWORD 0 or 1, without restoring the original type, bytes or absence. For example, enabling an initially absent preference and then cancelling leaves DWORD 0.

Save keeps the transparency change. Cancel, Escape, Back and closing the page restore its initial state if the checkbox was changed. Returning the checkbox to its initial state clears the pending change. A page where the checkbox was never changed leaves external transparency changes alone. Each page receives its own copied handler vtable. Its HandleOK hook accepts the current checkbox value as the new checkpoint, and its HandleNavigateAway hook restores transparency before native color cancellation, so balance calculations use the restored preference. Save accepts transparency immediately, even if native navigation subsequently fails. The copied table identifies its provider directly, allowing multiple pages on the same thread to keep separate checkpoints; the Windows transparency preference remains shared. A failed transparency restore still lets native color cancellation run, then returns an error to the caller; this is not guaranteed navigation cancellation or crash recovery. Detaching the page listener restores the original vtable before either the page or provider is destroyed.

Every intercepted setter call writes DWM values immediately, including slider previews, Automatic, Save and Cancel. The transparency checkpoint does not introduce a DWM registry journal or separate Save transaction. The caller's skipPersistence flag controls only the additional AccentColorMenu write: zero writes it, nonzero leaves it unchanged. In the inspected themecpl, manual previews pass 1, Automatic previews pass 0, and Save/Cancel can also pass 0; the persistent preference write is therefore not exclusive to Save. This differs from OpenGlassGUI's own synchronization, which waits until Save. The forced Shell request used by both Automatic paths can also change the Windows accent preference independently.

The classic panel captures its original color and resubmits it on Cancel. This restores the color, recalculated balances, opacity and DWM AccentColor through the same setter; it also synchronizes AccentColorMenu when the call passes zero. It does not restore an arbitrary previous configuration: custom balances, independent afterglow, raw value types and absent values are not captured. A failed write can leave earlier values from the same call written.

Automatic uses the Shell's final RGB, through the same request as OpenGlassGUI. The extension wraps the private desktop-wallpaper interface requested by themecpl. Its color method retains the native alpha, synchronously requests Shell automatic coloring, then returns the resulting DWM ColorizationColor RGB to the native page. Both Automatic preview and Cancel returning to Automatic use this path. This accounts for Windows processing that can make the applied accent darker than the raw wallpaper color, without hardcoding an algorithm. Other wallpaper operations, including desktop-background Save/Cancel persistence, forward to the native object. The GUI also uses native alpha on explicit Automatic selection; the original byte determines balances, and its percentage determines GlassOpacity.

In shell32 10.0.17763.737 and 10.0.26100.9549, normal image-color calculation varies alpha within 125..175 and reaches 175 once the average RGB channel difference is 50. Native fallback and cached values can lie outside that range; the extension keeps using the returned byte. Converting 175 to a percentage and back produces 175 -> 69% -> 176, so 176 in a later registry/tool value does not demonstrate a constant COM result. Later Shell-driven wallpaper changes still lose native alpha during RGB conversion and leave the existing OpenGlass strength settings in place. See the formula, alpha limits and community-mod boundary.

Automatic requires a running Shell and the shared request has a five-second timeout; request and color-read errors return to the native caller. The Shell request can update Windows accent state before the extension setter runs. The extension does not watch wallpaper changes or intercept color writes in other processes. It suppresses themecpl/themeui's subsequent SetUserColorPreference calls to prevent a second DWM write from replacing the calculated parameters. Accent synchronization uses the direct writes described above; the extension does not itself regenerate the palette or history.

Registration and coverage

Registration creates machine-wide, 64-bit TreatAs redirects for ThemeManager2, ThemeManagerShared and the themecpl theme element provider. The extension forwards to the original classes' HKLM InprocServer32 entries, including SecureUxTheme's proxy when registered. Those original paths remain unchanged. It temporarily impersonates TrustedInstaller to write the protected system registration keys without changing their ACLs.

The extension handles all three routes in one registry transaction without initializing color hooks. Each registration or unregistration operation commits all three routes together, including key creation and deletion; a failure or process termination before commit leaves the previous registration intact. A foreign TreatAs is rejected. Unregistration deletes the extension's proxy CLSID subtrees and removes only redirects that still point to those proxies, preserving unrelated data under the original system CLSIDs and their InprocServer32 entries.

The extension exports DllGetClassObject, DllCanUnloadNow, DllRegisterServer and DllUnregisterServer. Windows continues to call the system DLLs' special exports; coverage depends on their subsequent COM activation:

Entry point Caller and subsequent path Extension coverage
themecpl!OpenThemeActionW Opening .theme, .themepack or .deskthemepack invokes rundll32 ...\themecpl.dll,OpenThemeAction %1; the export then activates ThemeManager2 through COM and calls OpenTheme or InstallThemePack Its ThemeManager2 activation reaches TreatAs; themeui hooks initialize after the downstream factory export returns, before its result is returned to COM
themeui!DllInstall with /UserInstall Shell Active Setup loads the ShellComponent DLL and calls DllInstall(TRUE, StubPath) directly; themeui performs user theme initialization through internal factories Does not itself activate the proxy or initialize its hooks
themeui!DllInstall with /InstallVS:'... The export handles visual-style installation through an internal ThemeManager factory; its automatic external caller remains unidentified Does not itself activate the proxy or initialize its hooks

The observed Active Setup entry is HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components\{2C7339CF-2B09-4501-B3F3-F3508C9228ED}, with ShellComponent=themeui.dll and StubPath=/UserInstall. Shell compares the machine component with current-user initialization records before deciding to run it; it does not necessarily run on every logon, and this path does not launch regsvr32.

The same /InstallVS:'<path>','<color>','<size>' format is also used internally by ApplySetVisualStylePolicy. Under the HKCU\Software\Microsoft\Windows\CurrentVersion\Policies\System\SetVisualStyle policy and setup conditions, SetupThemeForUser reaches this helper directly or through InstallThemeAndDoNotStompBackground. The helper calls InstallVS directly, so Active Setup's /UserInstall processing can reach the shared implementation without using the /InstallVS export branch. This does not identify an external caller of that branch.

The function paths above were inspected in x64 10.0.26100.9549 binaries. File associations and Active Setup values were read on a separate host, not from the registry accompanying those samples. Hooks already initialized in the same process may affect later DllInstall color calls, but that is not guaranteed coverage for setup or logon initialization.

Forwarding OpenThemeActionW is unnecessary for the traced file-opening path. Combining the routes in one DLL does not change the direct-construction limitation of DllInstall. See Activation and special exports for the call chains and activation boundary. Register and unregister the extension with the commands above; it does not export DllInstall, so regsvr32 /i is not supported for it.

For build prerequisites and test coverage, see Building OpenGlass. The source-local design contract records COM activation, DUI behavior, color calculations and hook lifetimes for developers.

Clone this wiki locally