fix(osd/notch/shell): animation fixes, SafeLoader component, Vulkan backend detection - #159
fix(osd/notch/shell): animation fixes, SafeLoader component, Vulkan backend detection#159git-napkin wants to merge 97 commits into
Conversation
Add explicit sourceSize to avatar images in notch, lock screen, and resource monitor to prevent downscaling and use full available pixels.
…p polish - OSD: fix hover bug (was instantly hiding on enter), add slide+fade animation, wider pill (240x56), % suffix on value, 3s timer, font sizes via Styling.fontSize() - NotchAnimationBehavior: add subtle Y-translate drop-in, tighten scale floor from 0.8 to 0.85 - LockScreen: add animated date label below clock digits - StyledToolTip: fix desciription typo, reduce hover delay 1000→700ms, dim description text to 70% opacity, use Styling.fontSize() - SysTrayItem: fix desciription→description to match tooltip fix
- BarContent: remove redundant !== undefined checks - PanelTitlebar: use customContent array instead of children at init
- Add QML unit test suite (ConfigValidator, theme defaults, error patterns) - Create ErrorHandler singleton for centralized error tracking - Add SafeLoader component with error handling and fallback UI - Update AGENTS.md with testing docs and error handling anti-pattern - Integrate ErrorHandler into shell startup
… internal - setFallback() now actually parents the item into fallbackContainer so it renders when hasError is true (was previously orphaned) - Remove public 'loader' property alias — exposes internal Loader as API, allowing callers to bypass root.sourceComponent and write directly to internalLoader.sourceComponent - Make internalLoader fully internal (id only, no alias) - isLoading/hasError/isReady now bind to internalLoader directly - retry() null-cycles internalLoader.sourceComponent, not loader alias - Add newline at EOF lalala
Previously 'visible: GlobalStates.osdVisible' would hide the PanelWindow the moment osdVisible flipped false, killing the fade-out and slide-down animations before they could play. Fix: bind window visibility to 'GlobalStates.osdVisible || osdRect.opacity > 0' so the window stays alive until the opacity Behavior finishes animating to 0, then naturally goes invisible. Both the slide and opacity exit animations now play fully before the window is culled.
The previous check used 'command -v vulkaninfo || command -v glxinfo', meaning any system with glxinfo (OpenGL/GLX) would get QT_QUICK_BACKEND=vulkan even without a Vulkan ICD installed. Qt silently falls back to software rendering in that case, which is worse than just using opengl. Use 'vulkaninfo --summary' instead, which actually enumerates Vulkan ICDs and exits non-zero when none are found.
vulkan-headers is a development package (C headers for writing Vulkan applications). End users only need the runtime loader (vulkan-icd-loader) plus a GPU-specific ICD (vulkan-radeon, vulkan-intel, etc.) which they should already have from their mesa/nvidia installation. The Fedora list correctly uses only vulkan-loader; the Arch list now matches the same intent with vulkan-icd-loader only.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f489476788
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| StyledRect { | ||
| id: placeholder | ||
| anchors.fill: parent | ||
| visible: root.showPlaceholder && (root.isLoading || root.hasError) && !root.fallbackItem |
There was a problem hiding this comment.
Provide a variant for SafeLoader's StyledRect placeholder
StyledRect requires its variant property, but this placeholder instance never sets one. As soon as SafeLoader is instantiated, QML will raise a required-property initialization error for this child and the fallback/loading UI cannot be created reliably. Assign a valid variant (for example "common" or "popup") on this StyledRect instance.
Useful? React with 👍 / 👎.
| internalLoader.sourceComponent = null; | ||
| internalLoader.sourceComponent = current; |
There was a problem hiding this comment.
Keep sourceComponent binding intact in retry()
internalLoader.sourceComponent starts as a binding to root.sourceComponent, but retry() overwrites it imperatively (null then current). In QML, that assignment removes the original binding, so after the first retry later updates to root.sourceComponent no longer propagate to the loader. This can leave SafeLoader showing stale content when callers swap components after a retry.
Useful? React with 👍 / 👎.
Use a queue (_pendingTimeoutIds) instead of a single overwritable property to ensure all timed-out notifications are properly cleaned up when multiple timeouts occur in rapid succession.
- Quote font-family values so multi-word names parse correctly in CSS - Reduce dark-mode text scaling factors (1.05-1.40 instead of 1.15-3.19) to keep all text variants visible on dark backgrounds - Increase light-mode scaling factors (1.5-5.0 instead of 1.15-3.19) to create proper visual hierarchy when fg is near-black
- fix(Ai): remove dead re-entrancy guard and destroy rejected duplicate models (memory leak) - fix(GameModeService): route state through StateService instead of writing states.json directly (race fix) - fix(pauseAutoSave): sync across theme/shell/compositor edit sessions + discard-on-close safety net - fix(null-safety): guard focusedMonitor, note title in delete mode, clipboard model access - fix(config): align 7 adapter defaults with defaults/*.js and add missing compositor.layout - perf(Config): debounce auto-saves through a 150ms timer instead of per-property disk writes - perf(WavyLine): wire CarouselProgress active->running to stop idle 60fps canvas redraws - perf(lists): reuseItems + larger cacheBuffer on Clipboard/Tmux/Notes ListViews - perf(IdleMonitor): gate media-inhibitor poll to isIdle - chore: StateService init via Component.onCompleted; quiet PowerProfile/Wallpaper debug logs
- add modules/widgets/dashboard/list_utils.js with ROW_HEIGHT, expandedRowHeight, and scrollToReveal shared across Clipboard/Notes/Tmux tabs - replace triplicated adjustScrollForExpandedItem scroll math with the helper - add lastworkingcommit.txt (rollback point 3bb8f3e)
Security: - Replace eval in wf-record.sh with array-based command execution - Replace XOR cipher in keystore.py with PBKDF2-HMAC-SHA256 + Fernet - Remove insecure XOR fallback (require cryptography as hard dependency) - Add python3Packages.cryptography and pytest to Nix packaging UX smoothness: - Add line_buffering to fprintd_auth.py for real-time output - Add flush=True to link_preview.py - Remove unnecessary Qt.callLater in PowerProfileSelector.qml - Bump config auto-save debounce from 150ms to 250ms - Add startup init logging to shell.qml - Make weather.sh cache TTL configurable via Config.weather.cacheTtl Config consistency: - Fix workspaceSpacing mismatch (4 -> 8 to match defaults) - Add .pragma library to defaults/ai.js - Add config migration system with version tracking - Add enum and range validators to ConfigValidator.js - Add JSDoc comments to config defaults Theme consistency: - Replace hardcoded hex colors in Colors.qml with adapter.* properties - Fix empty power icon in Icons.qml - Replace font.pixelSize with Styling.fontSize() across 18 bar files - Replace Easing.OutCubic/OutQuart with Styling.animEasing across bar modules - Replace manual Qt.rgba() with Styling.tint() in 3 QML files Code quality: - Add set -euo pipefail to 10 bash scripts - Replace 14 bare except: with except Exception: - Add __main__ guard to colorpicker.py - Add argparse to system_monitor.py and link_preview.py - Add cache TTL and error handling to weather.sh - Add error handling to ocr.sh Nix packaging: - Fix flake URI in install.sh - Add wayland.enable dependency - Add meta attributes to package and NixOS module - Create shell.nix Documentation: - Update root AGENTS.md with missing modules - Create AGENTS.md for dock, notifications, widgets modules Testing & CI: - Add unit tests for keystore, system_monitor, colorpicker - Add GitHub Actions CI pipeline (nix build, python tests, shellcheck) CLI: - Optimize find_ambxst_pid with single pgrep call - Add ps aux fallback for systems without pgrep
…t child declaration error
…id QML QtObject child declaration error
The axctl daemon has a timing issue: config set commands sent before the daemon is ready are lost, and the daemon may re-read its TOML file after config set, reverting the change. Switch to hyprctl keyword, which is synchronous and avoids the daemon IPC entirely. Also removed stale result symlink and added to .gitignore.
The fork has the critical DefaultOutputPath fix (+ in dir name) and Config.Set now re-applies TOML so generated files stay in sync.
The visualContent Item had layer.enabled: true with a Shadow effect, which rendered the entire shell panel as a single opaque texture (a: 1 per hyprctl layers). The compositor couldn't blur behind the notch menus because it saw zero transparency on the overlay surface. Each component (bar, dock, notch) already has its own shadow/border handling, so the unified layer is redundant. Removing it lets Qt render directly to the PanelWindow surface, preserving transparency and allowing Hyprland's layer-rule blur to work.
…ell-routed keybinds
Core (Audio.qml):
- Exponential volume curve: 40 dB range, each 1/16 step = 2.5 dB
(equal perceived loudness per tick, flattens top-end spike)
- sliderToGain() / gainToSlider() conversion functions
- 16-step grid (tick=1/16) + fine quarter-step (1/64) with Shift
- Ear-bang protection now wired to all UI paths (was dead code)
UI sliders:
- VolumeSlider.qml, MicSlider.qml, ControlsButton.qml, WidgetsTab.qml:
all use Audio.setVolume()/setMicVolume() (slider→gain curve + protection)
- StyledSlider: new fineStepSize property (Shift+scroll = fine step)
- ControlSliderRow: new scrollStep property
- CircularControl: stepSize replaces hardcoded 0.1
- AudioVolumeEntry: per-app nodes use gainToSlider()/setNodeVolume()
OSD & Keybinds:
- OSD.qml: display perceptual slider position instead of raw gain
- GlobalShortcuts.qml: volume-up/down/mute IPC commands
- KeybindActions.js + Config.qml: keybinds now route through shell
(ambxst+ run volume-up) instead of wpctl, so curve + OSD + protection apply
…xes, UI consistency audit - Camera: CameraService + camera_monitor.py (in-use detection via /proc fd walk), CameraIndicator with pulsing halo + user popup in both bar orientations - Mic: XF86AudioMicMute keybind -> Audio.toggleMicMute with OSD feedback, KeybindActions/Capture entries, cli.sh mic-mute subcommand - Fingerprint: FingerprintPopup rewritten as PanelWindow (created by FprintdInterceptor), missing proc.running=true in verify/enroll/check/list (processes never started), kill() -> running=false (Process has no kill) - PAM: drop nullok from password.conf - UI consistency: 143 literal font sizes -> Styling.fontSize(), 30 radii -> Styling.radius(), tinted scrim/overBackground fixes (0 literals remain) - Perf/services: TaskbarApps regex cache, CompositorConfig/TomlWriter debounce, NetworkService single-process coalescing, StateService debounce, misc fixes
…nce, dead keybinds - Declare Component/Timer/Connections children of QtObject roots as properties (Screenshot, FprintdInterceptor, ClipboardService, BluetoothDevice) — QtObject has no default property, crashing the shell on launch since bd86c30. - Rename CameraService signal camerasChanged -> cameraListChanged (collided with the implicit signal of property list<var> cameras). - Add missing Quickshell.Widgets import in WorkspaceButton (IconImage). - Rename IconToggleButton icon -> glyph (overrode FINAL Button.icon), update BarContent/DockContent consumers. - Replace nonexistent Keys.onHomePressed/onEndPressed in StyledSlider with a Keys.onPressed handler. - Dashboard: disable NotchAnimationBehavior scale/slide entrance (compounds with the StackView push scale into a bounce); plain fade. - Instantiate CompositorKeybinds in shell.qml (removed by 92df9c2) and re-apply binds on the daemon's configreloaded event — the shell's core keybinds were never reaching the compositor.
… unit a real PATH - The generated axctl.toml wrote exec-once = "ambxst+", starting a second shell at Hyprland login on top of the ambxst-plus systemd user service; a bare "axctl daemon" would also use the legacy config path. The shell spawns the daemon itself (AxctlService, with -c), so no exec-once is needed. - The user unit inherited systemd's minimal PATH, so hyprctl/axctl/ bash were not found: layout detection and keybind application hung at startup. Set PATH to the system profile.
- Workspaces.qml: occupied binding read workspaceOccupied[index], which evaluates to undefined for empty workspaces — QML keeps the previous value on undefined binding results, so workspace numbers turned white when a window arrived and never greyed out after it moved/closed. Compare with === true so the binding always yields a real boolean. - BluetoothService.qml/BluetoothDevice.qml: Qt's JS engine has no Promise.prototype.finally; replace .finally() with .then/.catch pairs that keep the same cleanup + error propagation.
bluetoothctl can exit non-zero (br-connection-create-socket) while the device actually connects. On connect failure, verify the real link state via `bluetoothctl info`; only surface an error if truly disconnected.
- Add switchToActivatedWorkspace compositor config option (default true) - Implement followActivatedWorkspace() in AxctlService with debounce guard - Auto-unmute sink when adjusting volume (Audio service) - Hide EasyEffects sink nodes from device lists - Add UI toggle in CompositorPanel for the new option - Update AGENTS.md with CI check details and config/pauseAutoSave notes - Replace README.md with fork attribution
xdg-activation requests are denied when misc:focus_on_activate is off (and some apps never request activation at all), so a link or file opening in an app already running on another workspace, or an app demanding attention with a popup, used to do nothing: the window sat unfocused on its workspace and the shell never switched. Hyprland reports both cases via its 'urgent' socket event (the live session has focus_on_activate=false, which is exactly the denied path). axctl now surfaces window urgency (Event.WindowUrgent + is_urgent in window state; flake input bumped to 362f366), and followActivatedWorkspace gains a second path: when an urgent window sits on a non-active workspace, switch to its workspace and focus the window explicitly. The existing follow path (compositor-focused window on a non-active workspace) is unchanged, both are gated by the same switchToActivatedWorkspace toggle, and the per-window dispatch throttle is shared so the two paths can't double-fire.
// psst, this description was made by ai, theres too many tiny changes for me to bother with looking through the entire diff thingy for things i would probably forget about in a description, since this was written over the span of a couple weeks or so
Describe your changes
PanelWindow.visibletoosdVisible || osdRect.opacity > 0. Added slide-in/out translate animation, tweaked sizing and margins, fixed hover behavior so hovering pauses the hide timer instead of immediately dismissing, and fixed the percentage display to clamp against-0%.modules/components/SafeLoader.qmlcomponent — a drop-inLoaderwrapper with error handling, fallback UI, and a retry function using the standard null-cycle pattern.vulkaninfo --summary(more reliable thanglxinfo), plusQML_USE_TYPED_ARRAYSandQT_THREADED_RENDERERenv vars for rendering performance.vulkan-loader/vulkan-icd-loaderto dependency lists for Fedora and Arch respectively.desciription→descriptionin bothStyledToolTip.qmlandSysTrayItem.qml.font.pixelSizevalues in OSD and tooltip to useStyling.fontSize()for consistency.sourceSizehints to avatar images inMetricsTab,LockScreen, andUserInfoto avoid loading full-res images into GPU memory unnecessarily.PanelTitlebarcustom content visibility binding, made some container backgrounds transparent where a colored background was leaking through, cleaned up a few redundantimplicitWidth !== undefinedchecks inBarContent.qml.Add screenshots
Skipping screenshots, most changes are either invisible infrastructure, minor metric tweaks, or animation timing adjustments that don't photograph well.
Does this change any existing behavior?
Yes, a few things behave differently:
StyledToolTip'sdesciriptionproperty is renamed todescription— any callers using the old misspelled name will need to update.