Skip to content

Feature/night mode exceptions - #2809

Open
euletheia wants to merge 11 commits into
AvengeMedia:masterfrom
euletheia:feature/night-mode-exceptions
Open

Feature/night mode exceptions#2809
euletheia wants to merge 11 commits into
AvengeMedia:masterfrom
euletheia:feature/night-mode-exceptions

Conversation

@euletheia

Copy link
Copy Markdown
Contributor

Description

Add night mode exceptions (fullscreen and custom app list).

Drawing heavily on hthienloc/DankMaterialShell@de3e8cf, give the possibility to specify a list of applications excluded from Night Mode, alongside the ability to exclude fullscreen applications.

Logic is implemented on newly focused windows, and integrates with compositors overview mode so that Night Mode is resumed on overview.1

Notes

  • Abstracted the appId match function from MprisController:qml:20 to Paths.qml:154 with other (loosely-)related functions.

Type of change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that changes existing behavior)
  • Refactor / internal cleanup
  • Documentation
  • Other

Related issues

Screenshots / video2

contrib_night-mode-exceptions.webm

Checklist

  • My code follows the conventions in CONTRIBUTING.md
  • I have tested my changes locally
  • New user-facing strings are wrapped in I18n.tr() with translator context, reusing existing terms where possible
  • Go changes: ran make fmt, added/updated tests, make test passes, and go mod tidy is clean
  • QML changes: ran make lint-qml with no new warnings
  • I have opened a corresponding pull request in dlx-docs to document any new behaviors: https://github.com/AvengeMedia/DankLinux-Docs

Footnotes

  1. This means that enabling fullscreen exclusion while fullscreen will do nothing because the implementation reacts to Toplevel change (application focus).

  2. Screen gamma change is obviously not visible in the video, but "it works on my machine".

@euletheia
euletheia force-pushed the feature/night-mode-exceptions branch 2 times, most recently from 7a6dffa to 43a0917 Compare July 10, 2026 16:52
Comment thread quickshell/Common/settings/SessionSpec.js Outdated
@euletheia
euletheia force-pushed the feature/night-mode-exceptions branch 2 times, most recently from 566ade1 to e5c7dcc Compare July 23, 2026 15:18
@euletheia
euletheia requested a review from dionjoshualobo July 23, 2026 15:33
@dionjoshualobo

Copy link
Copy Markdown
Contributor

I noticed that when the Fullscreen Applications is turned on, and if any one app in the workspace is fullscreen, it turns of night mode for all the apps in the workspace. Is it intentional?

@euletheia
euletheia force-pushed the feature/night-mode-exceptions branch from e5c7dcc to 6470274 Compare July 23, 2026 17:07
@euletheia

Copy link
Copy Markdown
Contributor Author

That's a regression caused by the unnecessary additional commit I carelessly pushed earlier.
I just dropped it and NightMode should resume if you switch from a fullscreen app to any non-fullscreen window, as was originally planned.

Thank you !

@dionjoshualobo

Copy link
Copy Markdown
Contributor

The "excluding apps from night mode" makes sense, but the one with "full screen disabling night mode" doesn't make sense to me. Anyway, up to the maintainers to approve

@euletheia

euletheia commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

To me, the point of allowing users to disable night mode for fullscreen apps is to ensure color accuracy for applications typically run in fullscreen mode, like media players and video games.

Comment thread quickshell/Services/DisplayService.qml Outdated
@euletheia
euletheia requested a review from bbedward July 23, 2026 18:28
@euletheia

euletheia commented Jul 23, 2026

Copy link
Copy Markdown
Contributor Author

Two shortcomings I've noticed1 and I'm not sure how to solve:

  • Making a window fullscreen with nightModeExcludeFullscreen enabled seems to cause some minor "flickering".
  • Switching from a fullscreen window (with nightModeExcludeFullscreen enabled) to the Niri overview mode introduces a slight but noticeable delay before the blurred wallpaper shows.

EDIT:

  • seems like 2bafc21 solved most of the problem.
  • I'd still like to "fix" the abrupt temperature change: following is a basic QML patch to animate state switching.
    • I'm not sure about how to properly do error handling here,
    • It spawns a lot of IPC requests (very noisy in debug mode).

If time permits, I'd be glad to try my hand at a Go implementation, but I'm unsure how an animation player that handles bezier curves would fit in the wayland pkg. Would it make sense for it to live in a separate file, and be hooked into the manager.go:schedulerLoop() ? If you have any time to spare to provide some hints or directions, it would be greatly appreciated.

night-mode-transition-basic.patch

Footnotes

  1. at least on Niri, and at least on my laptop.

Drawing heavily on hthienloc/DankMaterialShell@de3e8cf
(excluded media players), give the possibility to specify a list of
applications excluded from NightMode, alongside the ability to exclude
fullscreen applications.

(Logic is implemented on newly focused windows, and integrates with
compositors overview mode so that NightMode is resumed on overview.)

+ Update GammaControlTab main section to use a SettingsCard component.
…ceptions

- Use the appId matching function abstracted to Paths.qml.

- Use common appId normalization functions to append/remove from
settings list.

- Align with GammaControlTab app list input fields to fix bug where
white spaces only input would prevent field clearing on accept.

- Align with GammaControlTab and use a Loader for AppBrowserPopup so
that it can be recreated in case the popup is not closed with the
dedicated button (i.e., via the compositor).
…ecreated if closed

(Align with GammaControlTab changes)
@euletheia
euletheia force-pushed the feature/night-mode-exceptions branch from 5f85669 to def58ec Compare July 27, 2026 00:32
}
function resumeNightMode() {
enableNightMode();
nightModePaused = false;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the paused state should be in-memory only, not persisted to the session json. Similar with the auto disabling/enabling, doesnt need to be persisted to disk

@euletheia euletheia Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the nightModePaused persistence, but I'm not sure I fully understand you: do you also want to remove the session persistence for nightModeEnabled, or for nightModeAutoEnabled (or both) ?

}

Connections {
target: CompositorService

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably connect to ToplevelManager.activeToplevel instead of the CompositorService list

@euletheia euletheia Aug 15, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless I'm missing something, it requires three connections to match the previous behavior (but still reduces function calls).

Comment thread quickshell/Services/DisplayService.qml Outdated
Comment thread quickshell/Services/DisplayService.qml Outdated
property bool nightModePaused: false
property var nightModeExcludedAppsMatchesCache: []

property var hyprlandOverviewLoader: null

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be restructured so the Services don't hold a reference to a UI loader/module, can simplify this wiring a lot I think

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ended up with 5a1dddf (tested in a VM). Would this work for you ?

@euletheia
euletheia force-pushed the feature/night-mode-exceptions branch from e516e9c to 3749b45 Compare August 15, 2026 12:07
@euletheia
euletheia force-pushed the feature/night-mode-exceptions branch from 3749b45 to 4a7868a Compare August 15, 2026 13:41
@euletheia
euletheia force-pushed the feature/night-mode-exceptions branch from 4a7868a to 95bfa41 Compare August 15, 2026 16:57
@euletheia
euletheia requested a review from bbedward August 15, 2026 22:33
@bbedward

Copy link
Copy Markdown
Collaborator

/claude review

}

function pauseNightMode() {
disableNightMode();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The paused state still gets persisted to disk. disableNightMode() does SessionData.setNightModeEnabled(false) (line 1051), and SessionData.setNightModeEnabled calls saveSettings() (SessionData.qml:791).

So: night mode on → focus a fullscreen/excluded app → session.json now has nightModeEnabled: false. Restart (or crash) the shell while paused and Component.onCompleted (line 1423) restores nightModeEnabled = false with nightModePaused = false — night mode is silently off for good and nothing ever resumes it. It also writes to disk on every focus switch in and out of an excluded app.

Pause/resume should only flip the in-memory nightModeEnabled + the wayland.gamma.setEnabled request and leave SessionData.nightModeEnabled at the user's choice — e.g. split the DMS call out of enableNightMode()/disableNightMode() into a helper that pause/resume use, or re-write the user's value back after.

}

function recreatePopup() {
log.debug("Recreating popup");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log is undefined here. The readonly property var log: Log.scoped("NightModeExceptions") you added is declared on the SettingsCard at line 688, which is not in scope for popupLoader (a sibling of the flickable at the component root) — and it's unused where it is declared. recreatePopup() will throw a ReferenceError when it runs.

Move the declaration to the root Item (matching AutoStartTab.qml:14) and drop it from the SettingsCard.

}

function recreatePopup() {
log.debug("Recreating popup");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log is undefined in this file — there is no log property anywhere in MediaPlayerTab.qml, so recreatePopup() throws a ReferenceError when it runs. Add readonly property var log: Log.scoped("MediaPlayerTab") to the root Item (same as AutoStartTab.qml:14), or drop the log line.

import Quickshell.Io
import qs.Common
import qs.Services
import qs.Modules.WorkspaceOverlays

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leftover from the earlier hyprlandOverviewLoader approach — nothing in DisplayService.qml references qs.Modules.WorkspaceOverlays anymore now that overview state comes from CompositorService.inOverview. It also re-introduces the Services → Modules (UI) dependency that was the point of the restructure.

Suggested change
import qs.Modules.WorkspaceOverlays

@claude

claude Bot commented Aug 20, 2026

Copy link
Copy Markdown

Claude review

Feature logic is sound, but pausing writes through to session.json and two log references are out of scope.

  • Pause persists nightModeEnabled: false to session.json via disableNightMode() → night mode is lost across a restart while paused, plus a disk write per focus switch — quickshell/Services/DisplayService.qml:1078
  • log is undefined in popupLoader.recreatePopup(); the Log.scoped() property is declared on the SettingsCard at line 688 instead of the root Itemquickshell/Modules/Settings/GammaControlTab.qml:51
  • log is undefined in popupLoader.recreatePopup(); no log property exists in this file — quickshell/Modules/Settings/MediaPlayerTab.qml:38
  • Leftover unused import qs.Modules.WorkspaceOverlays (Services → UI dependency) — quickshell/Services/DisplayService.qml:10

Checked: night mode pause/resume state machine and its interaction with SessionData persistence, the new ToplevelManager/CompositorService connections, CompositorService.inOverview wiring across Hyprland/Niri/Mango, the Paths.isAppIdMatch extraction against the original MprisController logic, the SettingsData list helpers, and the popup Loader refactor in the three settings tabs; I18n wrapping and untouched en.json/template.json are fine. Model: claude-opus-5.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants