Skip to content

fix(osd/notch/shell): animation fixes, SafeLoader component, Vulkan backend detection - #159

Open
git-napkin wants to merge 97 commits into
Axenide:devfrom
git-napkin:dev
Open

fix(osd/notch/shell): animation fixes, SafeLoader component, Vulkan backend detection#159
git-napkin wants to merge 97 commits into
Axenide:devfrom
git-napkin:dev

Conversation

@git-napkin

Copy link
Copy Markdown

// 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

  • OSD: Fixed a bug where the fade-out animation would get cut dead by binding PanelWindow.visible to osdVisible || 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%.
  • Notch animations: Improved entrance/exit with a subtle vertical translate (drop-in from slightly above) alongside the existing scale+opacity behavior. Scale start point tightened from 0.8 → 0.85 for a less dramatic pop.
  • SafeLoader: New modules/components/SafeLoader.qml component — a drop-in Loader wrapper with error handling, fallback UI, and a retry function using the standard null-cycle pattern.
  • cli.sh: Added automatic Vulkan/OpenGL backend detection using vulkaninfo --summary (more reliable than glxinfo), plus QML_USE_TYPED_ARRAYS and QT_THREADED_RENDERER env vars for rendering performance.
  • install.sh: Added vulkan-loader / vulkan-icd-loader to dependency lists for Fedora and Arch respectively.
  • Typo fix: desciriptiondescription in both StyledToolTip.qml and SysTrayItem.qml.
  • Font sizing: Migrated hardcoded font.pixelSize values in OSD and tooltip to use Styling.fontSize() for consistency.
  • Avatar/image rendering: Added sourceSize hints to avatar images in MetricsTab, LockScreen, and UserInfo to avoid loading full-res images into GPU memory unnecessarily.
  • Misc: Fixed PanelTitlebar custom content visibility binding, made some container backgrounds transparent where a colored background was leaking through, cleaned up a few redundant implicitWidth !== undefined checks in BarContent.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:

  • The OSD hover interaction is inverted from before — hovering now pauses auto-hide rather than triggering immediate dismissal.
  • OSD auto-hide timer extended from 2500ms → 3000ms.
  • Tooltip delay shortened from 1000ms → 700ms.
  • StyledToolTip's desciription property is renamed to description — any callers using the old misspelled name will need to update.

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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread modules/components/SafeLoader.qml Outdated
Comment on lines +44 to +47
StyledRect {
id: placeholder
anchors.fill: parent
visible: root.showPlaceholder && (root.isLoading || root.hasError) && !root.fallbackItem

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

Comment thread modules/components/SafeLoader.qml Outdated
Comment on lines +76 to +77
internalLoader.sourceComponent = null;
internalLoader.sourceComponent = current;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

git-napkin and others added 10 commits June 30, 2026 20:15
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
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.
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.

2 participants