Wallpaper changes → browser theme. Huepr listens for wallpaper events via Native Messaging and injects CSS custom properties into whitelisted sites in real time.
Built for Zen Browser and Firefox on Hyprland + Caelestia. Any other wallpaper tool can trigger it by calling huepr-notify.
The same GitHub page after two wallpaper changes, with no reload in between:
Wallpaper Aurora.jpg |
Wallpaper Latte.png |
|---|---|
![]() |
![]() |
| Sites & Mappings | Themes | Popup |
|---|---|---|
![]() |
![]() |
![]() |
flowchart LR
hook["Wallpaper hook<br/>(caelestia-wallhook)"] -->|WALLPAPER_PATH| notify["huepr-notify"]
notify -->|writes path| pipe[("~/.cache/huepr/huepr.pipe")]
pipe -->|reads path| host["theme_host.py<br/>(native messaging host)"]
host -->|"{type: wallpaper_change,<br/>wallpaper: 'Aurora'}"| bg["background.js"]
bg -->|"apply_theme<br/>(whitelisted tabs only)"| cs["content.js"]
cs -->|"CSS vars on :root"| page["Web page"]
store[("browser.storage.local<br/>themes, sites, mappings")] --- bg
- Your wallpaper hook calls
huepr-notifywith the new wallpaper path. - The browser starts
theme_host.pywhen the extension loads and keeps it running. The host reads the path from the named pipe, takes the filename stem (/path/to/Aurora.jpg→Aurora) and sends it to the extension over Native Messaging. Theme colors are not resolved in the host. background.jsmatches the name against your themes and sends the resolved variables to every whitelisted tab.content.jssets the variables on:root. Sites that use those variables update instantly.
- Wallpaper-driven theming — theme switches automatically when your wallpaper changes
- Per-site CSS variable mapping — map site variables (e.g.
--background) to theme variables (e.g.--theme-bg) - Per-site theme override — pin a specific theme to a site regardless of wallpaper
- Custom CSS injection — inject arbitrary CSS rules per site (override hardcoded colors, SVG fills, etc.)
- Auto-detect site variables — scan the active tab's stylesheets and pick variables to map
- Color extraction from image — extract a palette from any image to seed a new theme
- Hook toggle — disable wallpaper trigger and pick a theme manually
- Smooth transitions — CSS transition injected on theme change, removed after 600ms
- Popup — quick access to hook toggle, manual theme select, and "Add current site"
- Updates — the extension updates through addons.mozilla.org;
huepr-updaterefreshes the host scripts, with an opt-in weekly timer
The extension is on Firefox Add-ons. It needs the native host from this repository to receive wallpaper events:
git clone https://github.com/Contraill/huepr
cd huepr
bash install.shThe installer sets up the native host and also puts the newest signed XPI from GitHub releases (the same file Mozilla signed for addons.mozilla.org) into each Firefox / Zen profile it finds. Restart your browser and approve Huepr when it asks about the new add-on (or enable it in about:addons).
If no profile is found, install it by hand: about:addons → ⚙ → Install Add-on From File → ~/.local/share/huepr/huepr.xpi.
After installation the git directory can be deleted — everything Huepr needs is copied to
~/.local/.
| Flag | Effect |
|---|---|
--auto-update |
Installs and enables a weekly systemd user timer that runs huepr-update. Off by default, because it runs install.sh from the git remote unattended. |
--dev |
For working on the extension: copies the unpacked source to ~/.local/share/huepr/extension instead of installing the signed XPI. Load it through about:debugging → Load Temporary Add-on. Browser settings are not changed. |
- Firefox or Zen Browser (see Compatibility)
- Python 3.8+
- curl (downloads the signed XPI)
- git (used by
huepr-updateto fetch updates) - Caelestia with
caelestia-wallhookfor the automatic wallpaper trigger — or add thehuepr-notifyline to your own hook
From 2.2.0 on, the extension gets updates from addons.mozilla.org, like any other add-on. Versions up to 2.1.0 checked updates.json instead; it points them to 2.2.0, after which they follow addons.mozilla.org too. To update the native host and helper scripts:
huepr-updateClose the browser, then:
huepr-uninstall # or: bash uninstall.sh from the repoIt reverses every change listed below and keeps backups of the files it edits. Themes and site mappings live in the browser's extension storage; remove the add-on in about:addons to delete them too.
| Location | What | Removed by huepr-uninstall |
|---|---|---|
~/.local/bin/huepr-theme-host |
Wrapper that starts the native host | ✓ |
~/.local/bin/huepr-notify |
Writes the wallpaper path to the pipe | ✓ |
~/.local/bin/huepr-update, huepr-uninstall |
Update / uninstall commands | ✓ |
~/.local/share/huepr/ |
theme_host.py, cached signed XPI (unpacked source with --dev) |
✓ |
~/.mozilla/native-messaging-hosts/com.huepr.theme_host.json |
Native messaging manifest (also under ~/.zen/ if it exists) |
✓ |
~/.cache/huepr/ |
Named pipe huepr.pipe and host.log |
✓ |
~/.config/huepr/config |
Git remote used by huepr-update |
✓ |
<profile>/extensions/huepr@localhost.xpi |
The signed extension, in each Firefox / Zen profile | ✓ |
caelestia-wallhook |
A comment and a huepr-notify line; backup saved as caelestia-wallhook.huepr-bak |
✓ (backup: .huepr-uninstall-bak) |
~/.config/systemd/user/huepr-update.{service,timer} |
Only with --auto-update |
✓ |
Browser preferences are not changed. Older versions of install.sh (up to the v2.0.3 release) added user_pref("xpinstall.signatures.required", false); to each profile's user.js and loaded the extension unpacked. Running the current install.sh or huepr-uninstall removes that line and the old proxy file (keeping user.js.huepr-bak). The value may already have been copied into prefs.js, so also reset xpinstall.signatures.required in about:config.
| Permission | Why |
|---|---|
nativeMessaging |
Receives wallpaper events from theme_host.py. The host only sends the wallpaper name; the extension never sends page data to it. |
storage |
Keeps themes, whitelisted sites, mappings and custom CSS in browser.storage.local. |
tabs |
Reads tab URLs to find whitelisted tabs, sends them theme updates and finds the active tab for Add current site and Detect. |
| Site access (optional) | Requested per site when you add it — *://github.com/* and *://www.github.com/* for github.com. Removing a site gives the access back. |
The manifest lists <all_urls> only under optional_permissions, as the upper bound of what can be requested. A new install starts with access to no website. content.js is registered at runtime for the granted sites only, and is injected into tabs that are already open when access is granted.
What the content script does on a granted site:
- On load it asks
background.jsfor a theme and sets the CSS variables on:root. If you configured custom CSS, it also adds a<style>element. - Detect reads CSS variable names from the page's stylesheets, and only when you press it.
Huepr makes no network requests and sends no page content anywhere. The manifest declares this to Firefox with data_collection_permissions: none.
Upgrading from 2.0.x: <all_urls> was a required permission before 2.1.0. The browser keeps it granted after the update, so your sites keep working. To switch to per-site access, turn off "Access your data for all websites" under about:addons → Huepr → Permissions. Then click Grant next to each site in Options → Sites & Mappings.
- Browsers: Firefox 91+ (
strict_min_versionin the manifest) and Zen Browser. The installer finds profiles in~/.mozilla/firefox,~/.config/mozilla/firefox,~/.zenand~/.config/zen. - OS: Linux only (named pipe,
~/.localand~/.mozillapaths). - Extension format: Manifest V2 WebExtension. Chromium-based browsers are not supported.
Known limits:
- Flatpak and Snap browser builds are sandboxed and cannot start a native host from
~/.local/bin; the installer does not configure them. - Release builds of Firefox only load signed add-ons. Use the signed XPI, or
--devwithabout:debuggingwhile developing. - Content scripts do not run on
about:pages,addons.mozilla.orgor other restricted pages, and site access can only be requested forhttp/httpssites. - Sites that do not use CSS variables need custom CSS rules. A site's own
!importantrules inside CSS cascade layers win over custom CSS rules, even when those are!important. - Detect cannot read cross-origin stylesheets that block CORS.
- If the native host cannot be reached, the extension retries 10 times, 5 seconds apart. The last theme stays applied, and manual theme selection keeps working. Restart the browser to reconnect after that.
Open the popup (click the Huepr icon) → Add current site, or go to Options → Sites & Mappings and type a hostname. Allow access when the browser asks — Huepr only gets access to that site.
Options → Themes → type a name → Add Theme. The theme name must match your wallpaper filename stem for automatic switching (Aurora.jpg → theme Aurora).
Add variables to the theme — e.g. --theme-bg: #1e1e2e, --theme-accent: #cba6f7.
In Sites & Mappings, select a site. Add a mapping:
- Site variable — the CSS variable the site uses (e.g.
--bgColor-defaulton GitHub) - Theme variable — the theme variable to pull the value from (e.g.
--theme-bg)
Use Detect to scan the active tab's stylesheets and find variables automatically.
In Sites & Mappings, select a site and add custom CSS rules to override hardcoded colors:
/* Override YouTube logo red */
path[fill="#FF0033"] { fill: var(--theme-accent) !important; }Call huepr-notify from whatever runs after a wallpaper change:
WALLPAPER_PATH="/path/to/Aurora.jpg" ~/.local/bin/huepr-notifyinstall.sh — installer: NM host, helper scripts, signed XPI, wallpaper hook
uninstall.sh — reverts everything install.sh did (installed as huepr-uninstall)
manifest.json — WebExtension manifest (MV2)
background.js — background page: NM connection, theme resolution, message routing
content.js — content script: CSS variable + custom CSS injection
popup/ — browser action popup
options/ — options page (themes, sites & mappings, help)
host/
theme_host.py — native messaging host: reads pipe, sends theme to extension
icons/ — extension icons
screenshots/ — README images
updates.json — update manifest for installs up to 2.1.0 (self-distributed)
amo-metadata.json — addons.mozilla.org listing text, category, license, reviewer notes
.github/workflows/ — release.yml (submit to AMO, open release), publish-approved.yml (attach signed XPI)
web-ext-config.mjs — keeps non-extension files out of the XPI (web-ext build / sign)
- Bump
versioninmanifest.jsonand commit it tomain. - Push an annotated tag; its message becomes the release notes on GitHub and addons.mozilla.org:
git tag -a v2.2.0 -m "v2.2.0" -m "- what changed" && git push origin v2.2.0
release.yml then submits the version to addons.mozilla.org (listed channel) with the listing text from amo-metadata.json, and opens the GitHub release. It reads AMO_API_KEY and AMO_API_SECRET (AMO API key) and the optional RELEASE_TOKEN from the huepr-release environment, which only v* tags can use.
Mozilla reviews the version before signing it. publish-approved.yml checks every 3 hours (or on demand from the Actions tab). Once the version is approved, it attaches the signed huepr-<version>.xpi to the GitHub release and adds the version to updates.json.
A theme is a flat key-value map of CSS custom properties:
{
"--theme-bg": "#1e1e2e",
"--theme-surface": "#313244",
"--theme-accent": "#cba6f7",
"--theme-fg": "#cdd6f4",
"--theme-border": "#45475a"
}When active, all theme variables are injected into :root on whitelisted sites. Site mappings then alias site-specific variables to theme values:
--site-background → value of --theme-bg
--site-link-color → value of --theme-accent
Aurora, Sunset, Latte and Frappe ship as built-in presets; a theme with the same name in Options overrides their variables.
Extension icon doesn't appear
Check about:addons — sideloaded add-ons stay disabled until you approve them. about:debugging → This Firefox lists every loaded extension.
Theme doesn't switch on wallpaper change
- Verify the hook is patched:
grep huepr-notify ~/.local/bin/caelestia-wallhook - Check
~/.cache/huepr/host.log— every received wallpaper event is logged there about:debugging→ Huepr → Inspect → check the background console for connection messages
CSS variables not applying Open Options → Sites & Mappings → confirm the site is whitelisted and has mappings. A Grant button next to a site means Huepr has no access to it yet. Use the Detect button to find available CSS variables on the active tab.
huepr-update fails
Check that ~/.config/huepr/config contains a valid REPO_URL pointing to a git remote.




