Sync config from arch

- hypr/apps.lua
- hypr/autostart.lua
- hypr/envs.lua
- hypr/hyprland.lua
- hypr/hyprsunset.conf
- hypr/input.lua
- hypr/looknfeel.lua
- hypr/omasettings.lua
- hypr/xdph.conf
- omarchy/branding/about.txt
- omarchy/branding/screensaver.txt
- omarchy/extensions/omarchy-menu.jsonc
- omarchy/hooks/battery-low.d/play-warning-sound.sample
- omarchy/hooks/font-set.d/show-font-notification.sample
- omarchy/hooks/post-boot.d/weather.sample
- omarchy/hooks/post-update.d/install-voxtype.hook
- omarchy/hooks/post-update.d/setup-agent.hook
- omarchy/hooks/post-update.d/setup-fingerprint.hook
- omarchy/hooks/post-update.d/show-update-notification.sample
- omarchy/hooks/pre-refresh-pacman.d/add-custom-repo.sample
- omarchy/hooks/theme-set.d/show-theme-notification.sample
- omarchy/shell.json
- omarchy/shell.toml
- omarchy/theme.name
- omarchy/themes/azure-glow/README.md
- omarchy/themes/azure-glow/alacritty.toml
- omarchy/themes/azure-glow/btop.theme
- omarchy/themes/azure-glow/hyprland.conf
- omarchy/themes/azure-glow/hyprlock.conf
- omarchy/themes/azure-glow/icons.theme
- … 269 more
This commit is contained in:
asepharyana
2026-09-23 15:19:12 +07:00
commit 1cdb82a76f
300 changed files with 78143 additions and 0 deletions
+5
View File
@@ -0,0 +1,5 @@
{
"format": "omarchy-config",
"version": 1,
"synced_by": "gladimdim.config-sync"
}
+3
View File
@@ -0,0 +1,3 @@
-- App-specific window rules (dimuat setelah default Omarchy, jadi menang).
-- Obsidian: tiling penuh seperti browser, opacity penuh (tanpa efek kusam default).
o.window("md.obsidian.Obsidian", { tag = "-default-opacity", float = false, opacity = "1 1 override" })
+2
View File
@@ -0,0 +1,2 @@
-- Extra autostart processes.
-- o.launch_on_start("my-service")
+13
View File
@@ -0,0 +1,13 @@
-- config-sync cherry-pick
o.bind("SUPER + C", "Close window", hl.dsp.window.close())
o.bind("SUPER + E", "File manager", { omarchy = "nautilus" })
o.bind("SUPER + Q", "Terminal", { omarchy = "terminal" })
o.bind("SUPER + R", "Omarchy menu", "omarchy-menu toggle")
o.bind("SUPER + SHIFT + C", "Universal copy", universal_clipboard_shortcut("CTRL", "C", "CTRL", "Insert"))
o.bind("SUPER + SHIFT + V", "Universal paste", universal_clipboard_shortcut("CTRL", "V", "SHIFT", "Insert"))
o.bind("SUPER + SHIFT + X", "Universal cut", send_shortcut_once("CTRL", "X"))
hl.unbind("SUPER + SPACE")
hl.unbind("SUPER + V")
hl.unbind("SUPER + W")
hl.unbind("SUPER + X")
+3
View File
@@ -0,0 +1,3 @@
-- Omarchy VAAPI fix: force Intel iHD for video decode (hypr nvidia.lua sets LIBVA=nvidia which breaks Chromium YouTube on hybrid iGPU display)
hl.env("LIBVA_DRIVER_NAME", "iHD")
hl.env("__EGL_VENDOR_LIBRARY_FILENAMES", "/usr/share/glvnd/egl_vendor.d/50_mesa.json")
+35
View File
@@ -0,0 +1,35 @@
-- Learn how to configure Hyprland: https://wiki.hypr.land/Configuring/Start/
-- Omarchy's bootstrap keeps path setup out of this user config.
dofile((os.getenv("OMARCHY_PATH") or "/usr/share/omarchy") .. "/default/hypr/bootstrap.lua")
-- Disable all Omarchy default bindings. Add your own in hypr/bindings.lua.
-- omarchy_default_bindings = false
--
-- Or disable only bindings for Omarchy's preinstalled apps/web apps while
-- keeping core window-manager bindings:
-- omarchy_preinstalled_bindings = false
-- Load Omarchy defaults.
require("default.hypr.omarchy")
-- Put your personal overrides in these files. They're loaded after Omarchy's
-- defaults so package updates can improve the defaults without rewriting your
-- ~/.config/hypr files.
require("hypr.monitors")
require("hypr.input")
require("hypr.bindings")
require("hypr.looknfeel")
require("hypr.autostart")
-- Toggle config flags dynamically.
require("default.hypr.toggles")
-- VAAPI fix: overwrite nvidia env from default.hypr.nvidia
require("hypr.envs")
-- App-specific window rules (tiling/opacity per aplikasi).
require("hypr.apps")
-- Load settings written by OmaSettings (omasettings:managed).
require("hypr.omasettings")
+14
View File
@@ -0,0 +1,14 @@
# Makes hyprsunset do nothing to the screen by default
# Without this, the default applies some tint to the monitor
profile {
time = 07:00
identity = true
}
# To enable auto switch to nightlight, add to your .config/hypr/autostart.lua:
# o.launch_on_start("hyprsunset")
# and use the following:
# profile {
# time = 20:00
# temperature = 4000
# }
+57
View File
@@ -0,0 +1,57 @@
-- Keep only your personal input overrides here. Uncommented settings below
-- replace Omarchy's defaults.
-- Keyboard layout and options.
-- See https://wiki.hypr.land/Configuring/Basics/Variables/#input
-- hl.config({
-- input = {
-- -- Use multiple keyboard layouts and switch between them with Left Alt + Right Alt.
-- kb_layout = "us,dk,eu",
-- kb_options = "compose:caps,shift:both_capslock_cancel,grp:alts_toggle",
--
-- -- Use a specific keyboard variant if needed (e.g. intl for international keyboards).
-- kb_variant = "intl",
--
-- -- Change speed of keyboard repeat.
-- repeat_rate = 40,
-- repeat_delay = 250,
--
-- -- Start with numlock on by default.
-- numlock_by_default = true,
--
-- -- Increase sensitivity for mouse/trackpad (default: 0).
-- sensitivity = 0.35,
--
-- -- Turn off mouse acceleration (default: adaptive).
-- accel_profile = "flat",
--
-- touchpad = {
-- -- Use natural (inverse) scrolling.
-- natural_scroll = true,
--
-- -- Use two-finger clicks for right-click instead of lower-right corner.
-- clickfinger_behavior = true,
--
-- -- Control the speed of your scrolling.
-- scroll_factor = 0.4,
--
-- -- Enable the touchpad while typing.
-- disable_while_typing = false,
--
-- -- Left-click-and-drag with three fingers.
-- drag_3fg = 1,
-- },
-- },
-- })
-- App-specific touchpad scroll speeds.
-- o.window("(Alacritty|kitty|foot)", { scroll_touchpad = 1.5 })
-- o.window("com.mitchellh.ghostty", { scroll_touchpad = 0.2 })
-- Enable touchpad gestures for changing workspaces.
-- See https://wiki.hypr.land/Configuring/Advanced-and-Cool/Gestures/
-- hl.gesture({ fingers = 3, direction = "horizontal", action = "workspace" })
-- Enable touchpad gestures for moving focus (helpful on scrolling layout).
-- hl.gesture({ fingers = 3, direction = "left", action = function() hl.dispatch(hl.dsp.focus({ direction = "l" })) end })
-- hl.gesture({ fingers = 3, direction = "right", action = function() hl.dispatch(hl.dsp.focus({ direction = "r" })) end })
+50
View File
@@ -0,0 +1,50 @@
-- Change the default Omarchy look'n'feel.
-- https://wiki.hypr.land/Configuring/Basics/Variables/#general
-- hl.config({
-- general = {
-- -- No gaps between windows or borders.
-- gaps_in = 0,
-- gaps_out = 0,
-- border_size = 0,
--
-- -- Change to niri-like side-scrolling layout.
-- layout = "scrolling",
-- },
-- })
-- https://wiki.hypr.land/Configuring/Basics/Variables/#decoration
-- hl.config({
-- decoration = {
-- -- Use round window corners.
-- rounding = 8,
--
-- -- Dim unfocused windows (0.0 = no dim, 1.0 = fully dimmed).
-- dim_inactive = true,
-- dim_strength = 0.15,
-- },
-- })
-- https://wiki.hypr.land/Configuring/Basics/Variables/#animations
-- hl.config({
-- animations = {
-- -- Disable all animations.
-- enabled = false,
-- },
-- })
-- https://wiki.hypr.land/Configuring/Basics/Variables/#layout
-- hl.config({
-- layout = {
-- -- Avoid overly wide single-window layouts on wide screens.
-- single_window_aspect_ratio = { 1, 1 },
-- },
-- })
-- https://wiki.hypr.land/Configuring/Layouts/Scrolling-Layout/
-- hl.config({
-- scrolling = {
-- -- See only one column per screen instead of two.
-- column_width = 0.97,
-- },
-- })
+4
View File
@@ -0,0 +1,4 @@
-- Generated by OmaSettings (omasettings:managed) — do not edit by hand.
-- Every value here was set from the OmaSettings window; delete a line
-- to hand that setting back to your own config.
+4
View File
@@ -0,0 +1,4 @@
screencopy {
allow_token_by_default = true
custom_picker_binary = hyprland-preview-share-picker
}
+26
View File
@@ -0,0 +1,26 @@
██████████████████████████████████████████████████████
██████████████████████████████████████████████████████
████ ████ ████
████ ████ ████
████ █████████████████████ ████████ ████
████ █████████████████████ ████████ ████
████ ████ ████ ████
████ ████ ████ ████
████ ████ ████ ████
████ ████ ████ ████
████ ████ ████ ████
████ ████ ████ ████
████████████ ████ ████
████████████ ████ ████
████ ████ ████ ████
████ ████ ████ ████
████ ████ ████ ████
████ ████ ████ ████
████ ████ ████ ████
████ ████ ████ ████
████ ██████████████████████████████████████ ████
████ ██████████████████████████████████████ ████
████ ████ ████
████ ████ ████
█████████████████████████████ ████████████████████
█████████████████████████████ ████████████████████
+10
View File
@@ -0,0 +1,10 @@
▄▄▄
▄█████▄ ▄███████████▄ ▄███████ ▄███████ ▄███████ ▄█ █▄ ▄█ █▄
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███ ███
███ ███ ███ ███ ███ ▄███▄▄▄███ ▄███▄▄▄██▀ ███ ▄███▄▄▄███▄ ███▄▄▄███
███ ███ ███ ███ ███ ▀███▀▀▀███ ▀███▀▀▀▀ ███ ▀▀███▀▀▀███ ▀▀▀▀▀▀███
███ ███ ███ ███ ███ ███ ███ ██████████ ███ █▄ ███ ███ ▄██ ███
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
▀█████▀ ▀█ ███ █▀ ███ █▀ ███ ███ ███████▀ ███ █▀ ▀█████▀
███ █▀
+30
View File
@@ -0,0 +1,30 @@
{
// Extend the Quickshell Omarchy menu with JSONC.
//
// IDs are object keys. The parent is inferred from the dotted id, so
// "personal.notes" appears under "personal", and "personal" appears on the
// root menu. Reuse an existing id to override/extend it.
//
// Fields:
// icon Nerd Font glyph shown in the icon column.
// label Visible row title.
// action Shell command to run. If omitted, the row is a submenu.
// target Existing submenu id to open. Use for links/aliases.
// provider Runtime provider function/command returning JSON rows.
// aliases alternate `omarchy menu summon <name>` routes; also searchable.
// description Optional subtitle and extra search text.
// when Shell condition; hide row when it fails.
// checked Shell condition; append ✓ when it succeeds.
//
// Examples:
// "personal": {"icon":"","label":"Personal"},
// "personal.notes": {"icon":"󰎞","label":"Notes","action":"omarchy-launch-editor ~/notes"},
// "personal.files": {"icon":"","label":"Files","action":"uwsm-app -- nautilus ~/Documents"},
//
// Only use provider when a provider_name function or command named "name"
// returns JSON rows. Static submenus only need dotted ids.
//
// Example: replace the default About action by reusing the same id. Existing
// fields are kept unless overridden.
// "about": {"icon":"","label":"About","action":"omarchy-launch-or-focus-tui \"zsh -c 'fastfetch; read -k 1'\""},
}
@@ -0,0 +1,10 @@
#!/bin/bash
# This hook is called with the current battery percentage when the low battery
# notification is sent. To put it into use, remove .sample from this file name.
SOUND_FILE="/usr/share/sounds/freedesktop/stereo/dialog-warning.oga"
if omarchy-cmd-present mpv && [[ -f $SOUND_FILE ]]; then
mpv --no-video "$SOUND_FILE" >/dev/null 2>&1
fi
@@ -0,0 +1,7 @@
#!/bin/bash
# This hook is called with the snake-cased name of the font that has just been set.
# To put it into use, remove .sample from this file name.
# Example: Show the name of the font that was just set.
# omarchy-notification-send -u low "New font" "Your new font is $1"
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
# Show the weather status notification after Omarchy has started.
# To put it into use, remove .sample from this file name.
weather=$(omarchy-weather-status 2>/dev/null) || true
if [[ -n $weather && $weather != "Weather unavailable" ]]; then
omarchy-notification-send -u low "$weather"
fi
@@ -0,0 +1,9 @@
#!/bin/bash
set -e
if omarchy-done ensure voxtype-install-invitation; then
omarchy-notification-send -u critical -g  "Install Dictation with Voxtype" \
"Click to install voice dictation for Omarchy." \
--exec omarchy-launch-floating-terminal-with-presentation omarchy-voxtype-install
fi
@@ -0,0 +1,11 @@
#!/bin/bash
set -e
# Omarchy ships no default agent, so invite once rather than picking one. An
# agent already chosen means the invitation has nothing to offer.
if [[ -z $(omarchy-default-agent) ]] && omarchy-done ensure agent-setup-invitation; then
omarchy-notification-send -u critical -g 󰚩 "Set your default agent" \
"Let your favorite agent help with Omarchy." \
--exec omarchy menu summon setup.default.agent
fi
@@ -0,0 +1,12 @@
#!/bin/bash
set -e
# Only invite when there's a reader to use and it isn't set up yet (the lock
# PAM file is the last thing the setup writes on success).
if omarchy-hw-fingerprint && [[ ! -f /etc/pam.d/omarchy-lock-fingerprint ]] &&
omarchy-done ensure fingerprint-setup-invitation; then
omarchy-notification-send -u critical -g 󰈷 "Setup Fingerprint Reader" \
"Enable sudo and unlocking with your fingerprint." \
--exec omarchy-launch-floating-terminal-with-presentation omarchy-setup-security-fingerprint
fi
@@ -0,0 +1,7 @@
#!/bin/bash
# This hook is called after an Omarchy system update has been performed.
# To put it into use, remove .sample from this file name.
# Example: Show notification after the system has been updated.
# omarchy-notification-send -u low "Update Performed" "Your system is now up to date"
@@ -0,0 +1,24 @@
#!/bin/bash
# This hook is called by `omarchy refresh pacman` AFTER the channel template
# is copied to /etc/pacman.conf and BEFORE `pacman -Syyuu` runs. Use it to
# layer customizations onto the freshly-written pacman.conf so they're
# respected by the upgrade — common cases are adding a custom repository
# (e.g. CachyOS, Chaotic-AUR, an internal company repo) or extra IgnorePkg
# lines.
#
# The hook runs as the invoking user with a warm sudo cache.
#
# To put it into use, remove .sample from this file name.
# Example: add an Include line above [core] for a custom repo.
# Maintain the repo entries in /etc/pacman.d/custom-repos.conf yourself.
CONF=/etc/pacman.conf
SNIPPET=/etc/pacman.d/custom-repos.conf
MARKER="Include = $SNIPPET"
[[ -r $SNIPPET ]] || exit 0
grep -qxF "$MARKER" "$CONF" && exit 0
sudo sed -i "0,/^\[core\]/s||$MARKER\n\n[core]|" "$CONF"
@@ -0,0 +1,7 @@
#!/bin/bash
# This hook is called with the snake-cased name of the theme that has just been set.
# To put it into use, remove .sample from this file name.
# Example: Show the name of the theme that was just set.
# omarchy-notification-send -u low "New theme" "Your new theme is $1"
+90
View File
@@ -0,0 +1,90 @@
{
"version": 1,
"idle": {
"screensaver": 150,
"lock": 300
},
"bar": {
"position": "top",
"transparent": false,
"centerAnchor": "omarchy.clock",
"layout": {
"left": [
{
"id": "omarchy.menu"
},
{
"id": "omarchy.workspaces"
}
],
"center": [
{
"id": "omarchy.indicators"
},
{
"id": "omarchy.clock",
"format": "dddd HH:mm",
"formatAlt": "d MMMM 'W'ww yyyy",
"verticalFormat": "HH\n—\nmm"
},
{
"id": "omarchy.keyboard-layout"
},
{
"id": "omarchy.weather"
},
{
"id": "omarchy.media"
},
{
"id": "omarchy.microphone"
},
{
"id": "omarchy.system-update"
}
],
"right": [
{
"id": "omarchy.tray"
},
{
"id": "io.github.x3me.nexthop"
},
{
"id": "io.github.elevate08.qs-bitwarden-cli"
},
{
"id": "gladimdim.config-sync"
},
{
"id": "omarchy.tailscale"
},
{
"id": "stappmus.activity-monitor"
},
{
"id": "omaplug"
},
{
"id": "omarchy.agents"
},
{
"id": "omarchy.bluetooth"
},
{
"id": "omarchy.network"
},
{
"id": "omarchy.audio"
},
{
"id": "omarchy.monitor"
},
{
"id": "omarchy.power"
}
]
}
},
"plugins": []
}
+2
View File
@@ -0,0 +1,2 @@
[font]
base-size = 9
+1
View File
@@ -0,0 +1 @@
azure-glow
+81
View File
@@ -0,0 +1,81 @@
# Azure Glow Theme
A deep, cyber-ocean aesthetic — shadowy bases lit by bright azure and electric teal highlights. Designed for people who want their desktop to feel like it’s running on pure bioluminescence.
![Azure Glow Theme Preview](./preview.png)
---
## Design Notes
- **True depth**: Midnight backgrounds with subtle gradients
- **Pops of light**: Vivid blues that feel alive in the dark
- **Readable at a glance**: High-contrast, glow-ready palette
- **One vision, everywhere**: Synced colors across terminal, bar, lock screen, and notifications
---
## Color DNA
### Core Shades
| Purpose | Hex | Name |
|-------------|-----------|------------------------|
| Background | `#0a0f14` | Abyss Black-Blue |
| Foreground | `#a9dfff` | Soft Ice Blue |
| Secondary | `#101820` | Deep Graphite |
### Accents
| Role | Hex | Description |
|----------|-----------|--------------------------|
| Primary | `#00bfff` | Neon Azure |
| Secondary| `#00e0ff` | Electric Cyan |
| Success | `#2affd5` | Aqua Mint |
| Warning | `#ffd43b` | Soft Amber |
| Error | `#ff4c4c` | Signal Red |
| Info | `#33d4ff` | Sky Glow |
---
## Terminal Palette
| Color | Normal | Bright |
|----------|-----------|-----------|
| Black | `#101820` | `#1c2833` |
| Red | `#ff4c4c` | `#ff6b6b` |
| Green | `#2affd5` | `#4dffd9` |
| Yellow | `#ffd43b` | `#ffdf70` |
| Blue | `#00bfff` | `#4dcfff` |
| Magenta | `#9a6bff` | `#b594ff` |
| Cyan | `#00e0ff` | `#4deeff` |
| White | `#a9dfff` | `#ffffff` |
---
## What’s Themed
- **Alacritty** — deep ocean backdrop with crisp, glowing colors
- **Hyprland** — azure → cyan gradient active borders
- **Waybar** — cool-tone indicators with subtle luminance
- **Mako** — glowing borders on notifications
- **Btop** — gradients that mimic underwater light rays
- **Neovim** — syntax colors matched to the Azure Glow palette
- **Hyprlock** — soft blue glow lock screen motif
---
## Install
To install this theme, use the `omarchy-theme-install` command:
```bash
omarchy-theme-install https://github.com/Hydradevx/omarchy-azure-glow-theme
```
---
## Wallpaper Ideas
- Minimalist dark blue gradients with glow streaks
- Underwater photography with bright blue plankton
- Futuristic cityscapes at night with cyan lighting
- Abstract lightwave renders
---
**Tip:** Works beautifully with transparent windows and background blur for a real “underwater cyberpunk” vibe.
+37
View File
@@ -0,0 +1,37 @@
[colors.primary]
background = "#0a0f1a" # Deep ocean black-blue
foreground = "#a8dfff" # Icy azure glow
[colors.cursor]
cursor = "#00ccff" # Electric cyan beam
text = "#0a0f1a" # Dark contrast
[colors.normal]
black = "#0d1b26" # Abyssal navy
red = "#0099cc" # Bright cerulean
green = "#00e0b8" # Tropical aqua
yellow = "#33ccff" # Sky flash
blue = "#00aaff" # Bold azure
magenta = "#3399ff" # Deep sky fusion
cyan = "#66e0ff" # Frosted cyan
white = "#cceeff" # Snow drift
[colors.bright]
black = "#123247" # Steel blue shadow
red = "#33ccff" # Neon sky
green = "#33ffdd" # Glacial mint
yellow = "#66ddff" # Arc light
blue = "#33bbff" # Vibrant azure
magenta = "#66b2ff" # Cloud-washed blue
cyan = "#99eeff" # Crystal haze
white = "#ffffff" # Pure white glow
[font]
normal.family = "JetBrainsMono Nerd Font"
size = 11.0
[window]
padding.x = 10
padding.y = 10
opacity = 0.95
+60
View File
@@ -0,0 +1,60 @@
# Azure Glow Theme for Btop
# Electric blues and radiant cyan with deep abyss backgrounds
theme[main_bg]="#0a0a14"
theme[main_fg]="#b4dcff"
theme[title]="#00aaff"
theme[hi_fg]="#d0f0ff"
theme[selected_bg]="#00334d"
theme[selected_fg]="#00ccff"
theme[inactive_fg]="#00334d"
theme[proc_misc]="#b4dcff"
theme[cpu_box]="#00aaff"
theme[mem_box]="#00e5ff"
theme[net_box]="#00ccff"
theme[proc_box]="#0099ff"
theme[div_line]="#00334d"
theme[graph_text]="#66ccff"
# CPU gradients - from deep azure (low) to sky cyan to electric blue (high)
theme[cpu_start]="#0055ff"
theme[cpu_mid]="#00ccff"
theme[cpu_end]="#00f0ff"
# Memory gradients - from icy blue to bright cyan
theme[mem_start]="#66d9ff"
theme[mem_mid]="#00bfff"
theme[mem_end]="#0099cc"
# Network gradients - from bright cyan to aqua
theme[download_start]="#00e5ff"
theme[download_mid]="#00ccff"
theme[download_end]="#0099ff"
theme[upload_start]="#00ccff"
theme[upload_mid]="#00b3ff"
theme[upload_end]="#66ccff"
# Process gradients - vibrant cyan to aqua blue
theme[process_start]="#00e5ff"
theme[process_mid]="#00ccff"
theme[process_end]="#0099ff"
# Temperature gradients - from cool blue to brighter cyan
theme[temp_start]="#66d9ff"
theme[temp_mid]="#00ccff"
theme[temp_end]="#0099ff"
# Battery gradient - cool cyan tones
theme[battery_start]="#00e5ff"
theme[battery_mid]="#00ccff"
theme[battery_end]="#0099ff"
theme[clock]="#d0f0ff"
theme[input_text]="#ffffff"
theme[error_text]="#00ffff"
+4
View File
@@ -0,0 +1,4 @@
general {
col.active_border = rgb(00ccff) rgb(0055ff) 45deg
col.inactive_border = rgb(00334d)
}
+109
View File
@@ -0,0 +1,109 @@
$color = rgba(10,10,20,0.9) # Deep abyss blue
$inner_color = rgba(15,25,45,0.8) # Midnight navy
$outer_color = rgba(0,170,255,1.0) # Neon azure
$font_color = rgba(180,220,255,1.0) # Frosted cyan
$check_color = rgba(0,255,255,1.0) # Electric aqua
$fail_color = rgba(0,120,255,1.0) # Alert blue
$input_color = rgba(0,50,80,0.5) # Deep sea shadow
general {
grace = 0
hide_cursor = true
}
background {
monitor =
path = ~/.config/omarchy/themes/azure-glow/backgrounds/background.png
blur_size = 5
blur_passes = 2
noise = 0.02
brightness = 0.85
}
label {
monitor =
text = $TIME
color = $font_color
font_size = 64
font_family = JetBrainsMono Nerd Font Bold
position = 0, 100
halign = center
valign = center
}
label {
monitor =
text = ~~~ ~~~ ~~~
color = $font_color
font_size = 24
font_family = JetBrainsMono Nerd Font Mono
position = 0, 360
halign = center
valign = center
}
label {
monitor =
text = ~~ ~~ ~~ ~~ ~~ ~~
color = $font_color
font_size = 24
font_family = JetBrainsMono Nerd Font Mono
position = 0, 355
halign = center
valign = center
}
label {
monitor =
text = ~~ ~~ ~~ ~~ ~~ ~~
color = $font_color
font_size = 24
font_family = JetBrainsMono Nerd Font Mono
position = 0, 350
halign = center
valign = center
}
label {
monitor =
text = ~~ ~~ ~~ ~~
color = $font_color
font_size = 24
font_family = JetBrainsMono Nerd Font Mono
position = 0, 345
halign = center
valign = center
}
label {
monitor =
text = ~~ ~~ ~~ ~~ ~~ ~~
color = $font_color
font_size = 24
font_family = JetBrainsMono Nerd Font Mono
position = 0, 340
halign = center
valign = center
}
label {
monitor =
text = ~~ ~~ ~~ ~~ ~~ ~~
color = $font_color
font_size = 24
font_family = JetBrainsMono Nerd Font Mono
position = 0, 335
halign = center
valign = center
}
label {
monitor =
text = ~~~ ~~~ ~~~
color = $font_color
font_size = 24
font_family = JetBrainsMono Nerd Font Mono
position = 0, 330
halign = center
valign = center
}
+1
View File
@@ -0,0 +1 @@
Yaru-blue
+30
View File
@@ -0,0 +1,30 @@
text-color=#B3E5FC
border-color=#40C4FF
background-color=#0A0E14ee
progress-color=#00B0FF
font=Liberation Sans 11
width=420
height=110
padding=10
border-size=2
border-radius=5
anchor=top-right
default-timeout=5000
[urgency=low]
border-color=#80D8FF
[urgency=normal]
border-color=#40C4FF
[urgency=high]
border-color=#00B0FF
background-color=#10293Fee
text-color=#E1F5FE
[app-name=Spotify]
invisible=1
[mode=do-not-disturb]
invisible=1
+80
View File
@@ -0,0 +1,80 @@
-- Azure Glow NVIM Config
return {
{
"LazyVim/LazyVim",
opts = {
colorscheme = function()
-- Azure Glow Colorscheme
local colors = {
bg = "#0A0E14", -- Deep midnight
fg = "#E6F0FF", -- Soft icy white
primary = "#64B5F6", -- Vivid sky blue
secondary = "#00CFFF", -- Electric cyan
success = "#4DD0E1", -- Bright aqua
danger = "#2979FF", -- Intense azure
warning = "#81D4FA", -- Soft light blue
info = "#00E5FF", -- Neon cyan
light = "#B3E5FC", -- Pale baby blue
dark = "#041019", -- Deeper midnight
muted = "#355A66", -- Stormy teal
}
vim.cmd("highlight clear")
vim.cmd("set termguicolors")
vim.api.nvim_set_hl(0, "Normal", { fg = colors.fg, bg = colors.bg })
vim.api.nvim_set_hl(0, "Comment", { fg = colors.muted, italic = true })
vim.api.nvim_set_hl(0, "Constant", { fg = colors.secondary })
vim.api.nvim_set_hl(0, "String", { fg = colors.success })
vim.api.nvim_set_hl(0, "Character", { fg = colors.success })
vim.api.nvim_set_hl(0, "Number", { fg = colors.warning })
vim.api.nvim_set_hl(0, "Boolean", { fg = colors.primary, bold = true })
vim.api.nvim_set_hl(0, "Float", { fg = colors.warning })
vim.api.nvim_set_hl(0, "Identifier", { fg = colors.info })
vim.api.nvim_set_hl(0, "Function", { fg = colors.primary, bold = true })
vim.api.nvim_set_hl(0, "Statement", { fg = colors.danger, bold = true })
vim.api.nvim_set_hl(0, "Conditional", { fg = colors.danger })
vim.api.nvim_set_hl(0, "Repeat", { fg = colors.secondary })
vim.api.nvim_set_hl(0, "Label", { fg = colors.secondary })
vim.api.nvim_set_hl(0, "Operator", { fg = colors.fg })
vim.api.nvim_set_hl(0, "Keyword", { fg = colors.primary, bold = true })
vim.api.nvim_set_hl(0, "Exception", { fg = colors.danger })
vim.api.nvim_set_hl(0, "PreProc", { fg = colors.secondary })
vim.api.nvim_set_hl(0, "Include", { fg = colors.primary })
vim.api.nvim_set_hl(0, "Define", { fg = colors.primary })
vim.api.nvim_set_hl(0, "Macro", { fg = colors.warning })
vim.api.nvim_set_hl(0, "PreCondit", { fg = colors.secondary })
vim.api.nvim_set_hl(0, "Type", { fg = colors.info })
vim.api.nvim_set_hl(0, "StorageClass", { fg = colors.info })
vim.api.nvim_set_hl(0, "Structure", { fg = colors.secondary })
vim.api.nvim_set_hl(0, "Typedef", { fg = colors.secondary })
vim.api.nvim_set_hl(0, "Special", { fg = colors.warning })
vim.api.nvim_set_hl(0, "SpecialChar", { fg = colors.warning })
vim.api.nvim_set_hl(0, "Tag", { fg = colors.info })
vim.api.nvim_set_hl(0, "Delimiter", { fg = colors.fg })
vim.api.nvim_set_hl(0, "SpecialComment",{ fg = colors.muted })
vim.api.nvim_set_hl(0, "Debug", { fg = colors.danger })
-- UI Elements
vim.api.nvim_set_hl(0, "CursorLine", { bg = "#0F141C" })
vim.api.nvim_set_hl(0, "CursorLineNr", { fg = colors.primary, bold = true })
vim.api.nvim_set_hl(0, "LineNr", { fg = colors.muted })
vim.api.nvim_set_hl(0, "Visual", { bg = "#123C56" })
vim.api.nvim_set_hl(0, "Search", { fg = colors.bg, bg = colors.primary })
vim.api.nvim_set_hl(0, "IncSearch", { fg = colors.bg, bg = colors.secondary })
vim.api.nvim_set_hl(0, "Pmenu", { fg = colors.fg, bg = "#0E1620" })
vim.api.nvim_set_hl(0, "PmenuSel", { fg = colors.bg, bg = colors.primary })
vim.api.nvim_set_hl(0, "StatusLine", { fg = colors.fg, bg = "#0E1620" })
vim.api.nvim_set_hl(0, "StatusLineNC", { fg = colors.muted, bg = "#0E1620" })
vim.api.nvim_set_hl(0, "VertSplit", { fg = colors.dark })
vim.api.nvim_set_hl(0, "Title", { fg = colors.primary, bold = true })
vim.api.nvim_set_hl(0, "ErrorMsg", { fg = colors.bg, bg = colors.danger, bold = true })
vim.api.nvim_set_hl(0, "WarningMsg", { fg = colors.bg, bg = colors.warning })
vim.api.nvim_set_hl(0, "MoreMsg", { fg = colors.success })
vim.api.nvim_set_hl(0, "ModeMsg", { fg = colors.primary, bold = true })
end,
},
},
}
+45
View File
@@ -0,0 +1,45 @@
@define-color background-color #0A0E14ee; /* Deep midnight blue with transparency */
@define-color border-color #40C4FF; /* Bright sky blue */
@define-color label #B3E5FC; /* Soft glowing cyan */
@define-color image #B3E5FC; /* Soft glowing cyan */
@define-color progress #00B0FF; /* Rich azure */
window {
background: @background-color;
border: 2px solid @border-color;
border-radius: 10px;
}
#container {
padding: 15px;
}
#image {
color: @image;
font-size: 48px;
}
#label {
color: @label;
font-family: "JetBrainsMono Nerd Font";
font-size: 14px;
font-weight: bold;
margin: 10px 0;
}
progressbar {
background: rgba(27, 42, 58, 0.5);
border-radius: 5px;
min-height: 10px;
}
progressbar > trough {
background: rgba(27, 42, 58, 0.5);
border-radius: 5px;
}
progressbar > trough > progress {
background: linear-gradient(90deg, @progress, @border-color);
border-radius: 5px;
box-shadow: 0 0 10px rgba(64, 196, 255, 0.3);
}
+72
View File
@@ -0,0 +1,72 @@
@define-color selected-text #40C4FF; /* Bright sky blue */
@define-color text #B3E5FC; /* Soft glowing cyan */
@define-color base #0A0E14; /* Deep midnight blue */
@define-color surface #081018; /* Abyssal navy */
@define-color overlay #1B2A3A; /* Dim steel blue */
@define-color muted #4DD0E1; /* Lively aqua */
@define-color subtle #00B0FF; /* Rich azure */
@define-color border #40C4FF; /* Bright sky blue */
* {
font-family: "JetBrainsMono Nerd Font";
}
#window {
background: rgba(10, 14, 20, 0.95);
border: 2px solid @border;
border-radius: 10px;
}
#box {
padding: 20px;
}
#search {
background: @surface;
border: 2px solid @border;
border-radius: 5px;
padding: 10px;
color: @text;
font-size: 14px;
}
#search:focus {
border-color: @selected-text;
box-shadow: 0 0 10px rgba(64, 196, 255, 0.5);
}
#list {
margin-top: 10px;
}
#entry {
padding: 10px;
margin: 5px 0;
background: @surface;
border-radius: 5px;
color: @text;
}
#entry:hover {
background: @overlay;
border-left: 3px solid @subtle;
}
#entry:selected {
background: linear-gradient(90deg, rgba(64, 196, 255, 0.2), rgba(0, 176, 255, 0.2));
border-left: 3px solid @selected-text;
color: @selected-text;
}
#entry #icon {
margin-right: 10px;
}
#entry #name {
font-weight: bold;
}
#entry #description {
color: @muted;
font-size: 12px;
}
+93
View File
@@ -0,0 +1,93 @@
@define-color foreground #B3E5FC; /* Soft glowing cyan */
@define-color background #0A0E14; /* Deep midnight blue */
@define-color primary #40C4FF; /* Bright sky blue */
@define-color secondary #00B0FF; /* Rich azure */
@define-color success #00E5FF; /* Electric cyan */
@define-color danger #FF5252; /* Alert red */
@define-color warning #FFD740; /* Vibrant yellow */
@define-color info #82B1FF; /* Cool periwinkle */
@define-color light #E6F0FF; /* Frost white */
@define-color dark #081018; /* Abyssal navy */
@define-color muted #1B2A3A; /* Dim steel blue */
* {
font-family: "JetBrainsMono Nerd Font", monospace;
font-size: 13px;
min-height: 0;
}
window#waybar {
background: rgba(10, 14, 20, 0.9);
color: @foreground;
border-bottom: 2px solid @primary;
}
#workspaces button {
padding: 0 5px;
background: transparent;
color: @muted;
border-bottom: 3px solid transparent;
}
#workspaces button.active {
background: rgba(64, 196, 255, 0.1);
color: @primary;
border-bottom: 3px solid @primary;
}
#workspaces button:hover {
background: rgba(0, 176, 255, 0.2);
color: @secondary;
}
#cpu, #memory, #temperature, #network, #pulseaudio, #battery, #clock {
padding: 0 10px;
color: @foreground;
}
#cpu {
color: @secondary;
}
#memory {
color: @warning;
}
#temperature {
color: @danger;
}
#temperature.critical {
background: @danger;
color: @background;
}
#network {
color: @info;
}
#pulseaudio {
color: @primary;
}
#battery {
color: @success;
}
#battery.critical:not(.charging) {
background: @danger;
color: @background;
animation: blink 0.5s linear infinite alternate;
}
#clock {
color: @light;
font-weight: bold;
}
@keyframes blink {
to {
background-color: @background;
color: @danger;
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,105 @@
import QtQuick
import qs.Commons
import qs.Ui
import "BitwardenModel.js" as Model
// One labelled, copyable field on the detail screen.
//
// The detail view drew each of these longhand -- a PanelSectionHeader, a
// BorderSurface, a Text and one or two PanelActionButtons, forty lines at a
// time. That was tolerable while only a login had fields worth showing.
// Cards and identities together add sixteen more, and sixteen more copies of
// the same forty lines is how the surfaces drift apart: one row elides and
// the next does not, one masks and the next forgets to.
//
// Empty is not a state worth drawing. `visible` is false when there is no
// value, so a caller can declare every field a type can carry and let the
// sparse ones -- most of an identity, most of the time -- take themselves off
// the screen rather than leaving labelled blanks behind.
Column {
id: root
required property string label
required property string value
required property color foreground
required property string fontFamily
// A value that should not sit in plain sight on a shared screen: a card
// number, a security code, a social security number. Masked until revealed,
// and the reveal is per-field rather than a screen-wide switch.
property bool sensitive: false
property bool revealed: false
// What the flash message calls this once it is on the clipboard.
property string copyLabel: label
// Appended to the copy button's tooltip, e.g. "(n)". Empty when the field
// has no key bound to it.
property string shortcutHint: ""
// The same, for the reveal button. Separate because only one field per item
// is reachable by `v` -- promising it on the others would be a lie, and the
// reveal on each field is independent of every other.
property string revealHint: ""
// The copy button's glyph. Defaults to a plain copy icon; callers pass a
// semantic one where the detail view already had it, so a converted row
// keeps the icon it has always drawn.
property string copyIcon: "󰈙"
signal copyRequested()
signal revealToggled()
readonly property bool masked: root.sensitive && !root.revealed
visible: root.value !== ""
width: parent ? parent.width : 0
spacing: Style.space(4)
PanelSectionHeader { text: root.label.toUpperCase() }
BorderSurface {
width: parent.width
implicitHeight: Style.space(34)
radius: Style.cornerRadius
color: Style.hoverFillFor(root.foreground, Color.accent)
borderSpec: Border.controlSpec("normal", root.foreground, Color.accent)
Row {
anchors.fill: parent
anchors.leftMargin: Style.space(10)
anchors.rightMargin: Style.space(6)
Text {
textFormat: Text.PlainText
anchors.verticalCenter: parent.verticalCenter
text: root.masked ? Model.maskString(root.value) : root.value
color: root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.body
elide: Text.ElideRight
width: parent.width - fieldActions.width - Style.space(10)
}
Row {
id: fieldActions
anchors.verticalCenter: parent.verticalCenter
spacing: Style.space(4)
PanelActionButton {
visible: root.sensitive
iconText: root.revealed ? "󰈉" : "󰈈"
tooltipText: (root.revealed ? "Hide " : "Reveal ") + root.copyLabel.toLowerCase()
+ (root.revealHint === "" ? "" : " (" + root.revealHint + ")")
fontFamily: root.fontFamily
onClicked: root.revealToggled()
}
PanelActionButton {
iconText: root.copyIcon
tooltipText: "Copy " + root.copyLabel.toLowerCase()
+ (root.shortcutHint === "" ? "" : " (" + root.shortcutHint + ")")
fontFamily: root.fontFamily
onClicked: root.copyRequested()
}
}
}
}
}
@@ -0,0 +1,73 @@
import QtQuick
import qs.Commons
import qs.Ui
// One row in an item-form picker: folder, organization, or collection.
//
// `multi` distinguishes the two behaviours. A folder or organization is a
// single choice, so its mark is a tick; a collection is one of several an item
// may belong to, so its mark is a checkbox that reads as toggleable.
BorderSurface {
id: row
property string label: ""
property string glyph: ""
property bool picked: false
property bool multi: false
property color foreground: Color.foreground
property string fontFamily: Style.font.family
signal activated()
implicitHeight: Style.space(28)
radius: Style.cornerRadius
color: picked ? Style.selectedFillFor(foreground, Color.accent)
: (mouse.containsMouse ? Style.hoverFillFor(foreground, Color.accent) : "transparent")
borderSpec: Border.surfaceSpec("menu", "border", picked ? Color.accent : "transparent", picked ? 1 : 0)
MouseArea {
id: mouse
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: row.activated()
}
Row {
anchors.fill: parent
anchors.leftMargin: Style.space(9)
anchors.rightMargin: Style.space(9)
spacing: Style.space(8)
Text {
textFormat: Text.PlainText
anchors.verticalCenter: parent.verticalCenter
text: row.glyph
color: row.picked ? Color.accent : Qt.darker(row.foreground, 1.5)
font.family: row.fontFamily
font.pixelSize: Style.font.bodySmall
}
Text {
textFormat: Text.PlainText
anchors.verticalCenter: parent.verticalCenter
width: parent.width - Style.space(46)
text: row.label
color: row.picked ? Color.accent : row.foreground
font.family: row.fontFamily
font.pixelSize: Style.font.bodySmall
font.bold: row.picked
elide: Text.ElideRight
}
Text {
textFormat: Text.PlainText
anchors.verticalCenter: parent.verticalCenter
visible: row.multi || row.picked
text: row.multi ? (row.picked ? "󰄲" : "󰄱") : "󰄬"
color: row.picked ? Color.accent : Qt.darker(row.foreground, 1.6)
font.family: row.fontFamily
font.pixelSize: Style.font.bodySmall
}
}
}
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 David Spencer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,253 @@
import QtQuick
import qs.Commons
import qs.Ui
import "BitwardenModel.js" as Model
// The SSH agent's own settings sections, lifted out of Panel.qml so that file
// is not the only place this feature can be read.
//
// Two separate things, deliberately drawn apart. The top half is what the
// feature is doing; the bottom half is whether the user's terminals will
// reach it. Neither one gates the other. The approval screen lives with the
// other screens in Panel.qml, because that is what it is.
//
// `panel` is the Panel root: this section reads its vault and agent state and
// calls back into it for every action. Nothing here holds state of its own.
Column {
id: section
required property var panel
// The bar's foreground and font family, not the global theme's -- the same
// values the rest of the panel draws with. PanelSectionHeader and Text both
// default to the globals, so every text element here states them.
component SshSectionHeader: PanelSectionHeader {
textFormat: Text.PlainText
foreground: section.panel.fg
fontFamily: section.panel.fontFamily
}
component SshCaption: Text {
textFormat: Text.PlainText
width: parent ? parent.width : 0
color: section.panel.dim
font.family: section.panel.fontFamily
font.pixelSize: Style.font.caption
wrapMode: Text.WordWrap
}
visible: panel.sshUiAvailable
width: parent.width
spacing: Style.space(6)
Item { width: parent.width; height: Style.space(10) }
SshSectionHeader {
text: "SSH AGENT STATUS"
}
Row {
width: parent.width
spacing: Style.space(8)
Text {
textFormat: Text.PlainText
anchors.verticalCenter: parent.verticalCenter
text: panel.sshAgentSetup.state === "enabled"
? (panel.sshAgentSetup.busy ? "󰔟" : "󰄬")
: (panel.sshAgentSetup.state === "error" ? "󰀪" : "󰅘")
color: panel.sshAgentSetup.state === "error"
? panel.urgent
: (panel.sshAgentSetup.state === "enabled" && !panel.sshAgentSetup.busy ? Color.accent : panel.dim)
font.family: panel.fontFamily
font.pixelSize: Style.font.body
}
SshCaption {
width: parent.width - Style.space(30)
text: panel.sshAgentSetup.message
color: panel.sshAgentSetup.state === "error" ? panel.urgent : panel.dim
}
}
// Which helper is running. A developer with a local build and a
// user on a release see the same panel otherwise, and confusing
// the two wastes an afternoon.
SshCaption {
visible: panel.sshAgentHelper.source !== ""
text: "Using " + Model.sshAgentHelperSourceLabel(panel.sshAgentHelper.source)
+ (panel.sshAgentHelper.checksum === "match" ? " (checksum verified)" : "")
color: panel.sshAgentHelper.source === "development" ? panel.urgent : panel.dim
}
// Why the feature is unavailable, when it is. These are the
// failures a real clone produces: a stale binary, a dropped file
// mode, an LFS placeholder.
SshCaption {
visible: panel.sshAgentEnabled && panel.sshAgentHelper.message !== ""
text: panel.sshAgentHelper.message
color: panel.urgent
}
// The helper's own version, once it has said hello. Non-secret,
// and the quickest way to tell a stale bundled binary apart from
// a working one.
SshCaption {
visible: panel.sshAgentVersion !== ""
text: "Helper version " + panel.sshAgentVersion
}
// Routing is the thing most likely to be missing when the agent looks
// healthy and SSH still does not use it. Said here because this is the
// block a user reads first, and decided by the routing file rather than by
// this session's SSH_AUTH_SOCK -- see sshAgentRoutingNotice for why.
SshCaption {
visible: panel.sshAgentSetup.state === "enabled" && !panel.sshAgentSetup.busy
&& panel.sshRoutingNotice.text !== ""
text: panel.sshRoutingNotice.text
color: panel.sshRoutingNotice.urgent ? panel.urgent : panel.dim
}
Item { width: parent.width; height: Style.space(10) }
SshSectionHeader {
text: "CLIENT ROUTING"
}
SshCaption {
text: panel.sshRouting.message
color: panel.sshRouting.state === "matches" ? panel.dim : panel.fg
}
// The check the user runs in the terminal they actually use --
// which is the only place the answer is authoritative.
Text {
textFormat: Text.PlainText
width: parent.width
text: " " + panel.sshRouting.terminalCheck
color: Color.accent
font.family: panel.fontFamily
font.pixelSize: Style.font.caption
wrapMode: Text.WrapAnywhere
}
SshCaption {
text: panel.uwsmFragment.message
}
// Replacing the session's primary agent is a real decision, so the
// conflict is stated and confirmed rather than absorbed by the
// first click.
SshCaption {
visible: panel.uwsmConfirmPending
text: "This will make Bitwarden your session's SSH agent at the next login, replacing "
+ (panel.sshRouting.owner !== "" ? panel.sshRouting.owner : "the one you have now")
+ ". Continue?"
color: panel.urgent
}
SshCaption {
visible: panel.uwsmFlash !== ""
text: panel.uwsmFlash
color: panel.fg
}
// A Flow, because which of these four are showing is decided by the routing
// state: the idle pair and the confirming pair are each narrow enough, but
// nothing in a Row enforces that, and a Row answers a set that is too wide by
// laying the last button out past the panel edge rather than wrapping it.
Flow {
width: parent.width
spacing: Style.space(8)
Button {
visible: !panel.uwsmConfirmPending && panel.uwsmFragment.state !== "managed"
text: "Route SSH Clients Here"
iconText: "󰌘"
tooltipText: "Write " + Model.uwsmFragmentDisplayPath() + " so the next login points SSH clients at this agent"
fontFamily: panel.fontFamily
fontSize: Style.font.bodySmall
enabled: !panel.uwsmBusy
onClicked: panel.beginUwsmSetup()
}
Button {
visible: panel.uwsmConfirmPending
text: "Yes, Replace It"
iconText: "󰄬"
fontFamily: panel.fontFamily
fontSize: Style.font.bodySmall
enabled: !panel.uwsmBusy
onClicked: panel.beginUwsmSetup()
}
Button {
visible: panel.uwsmConfirmPending
text: "Cancel"
iconText: "󰅘"
fontFamily: panel.fontFamily
fontSize: Style.font.bodySmall
onClicked: panel.cancelUwsmSetup()
}
Button {
visible: !panel.uwsmConfirmPending && panel.uwsmFragment.removable
text: "Remove Routing File"
iconText: "󰩹"
tooltipText: "Delete " + Model.uwsmFragmentDisplayPath()
fontFamily: panel.fontFamily
fontSize: Style.font.bodySmall
enabled: !panel.uwsmBusy
onClicked: panel.removeUwsmFragment()
}
}
Item {
visible: panel.sshGrants.length > 0
width: parent.width
height: visible ? Style.space(10) : 0
}
SshSectionHeader {
visible: panel.sshGrants.length > 0
text: "ACTIVE APPROVALS"
}
// Every live grant, with the process it belongs to and what is
// left of it. A grant is a window in which signing happens with
// no prompt, so it has to be visible and revocable while it runs.
Repeater {
model: panel.sshGrants
delegate: Row {
required property var modelData
width: parent.width
spacing: Style.space(8)
SshCaption {
width: parent.width - Style.space(110)
text: modelData.keyName + " · "
+ modelData.processName
+ " · " + modelData.remainingLabel
}
Button {
anchors.verticalCenter: parent.verticalCenter
text: "Revoke"
iconText: "󰩹"
fontFamily: panel.fontFamily
fontSize: Style.font.caption
onClicked: panel.revokeSshGrant(modelData.grantId)
}
}
}
Button {
visible: panel.sshGrants.length > 1
text: "Revoke All Approvals"
iconText: "󰩹"
tooltipText: "Drop every live approval; the next signature asks again"
fontFamily: panel.fontFamily
fontSize: Style.font.bodySmall
onClicked: panel.revokeAllSshGrants()
}
}
@@ -0,0 +1,163 @@
import QtQuick
import Quickshell
import Quickshell.Wayland
import qs.Commons
import qs.Ui
// A transient, centered SSH authorization surface. The full-screen layer
// window supplies the scrim, outside-click denial, and keyboard focus; only
// the compact card is visible. It is tied to the bar widget's screen but not
// positioned relative to the bar, so the plugin otherwise stays out of sight.
PanelWindow {
id: popup
required property var panel
required property Item anchorItem
readonly property bool open: panel.sshAgentApprovalPopup && (panel.sshPrompt !== null || panel.sshUnlockRequest !== null)
property bool focusPrimed: false
readonly property var anchorWindow: anchorItem ? anchorItem.QsWindow.window : null
readonly property int cardWidth: Math.max(1, Math.min(Style.space(460), width - Style.gapsOut * 2))
readonly property int cardHeight: Math.max(1, Math.min(
content.implicitHeight + card.contentTopInset + card.contentBottomInset,
height - Style.gapsOut * 2))
function beginFocusPrime() {
if (open && backingWindowVisible) focusPrimeTimer.restart()
}
function refocus() {
if (!open) return
Qt.callLater(function() {
if (!popup.open) return
if (popup.panel.sshPrompt) approvalScreen.focusDefault()
else unlockScreen.focusDefault()
})
}
screen: anchorItem && anchorItem.QsWindow.window ? anchorItem.QsWindow.window.screen : null
visible: open
color: "transparent"
exclusionMode: ExclusionMode.Ignore
WlrLayershell.namespace: "qs-bitwarden-ssh-approval"
WlrLayershell.layer: WlrLayer.Overlay
// Prime focus briefly so keyboard-summoned requests reliably receive it,
// then settle to OnDemand so another monitor is not pointer-blocked.
WlrLayershell.keyboardFocus: open
? (focusPrimed ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.Exclusive)
: WlrKeyboardFocus.None
anchors {
top: true
bottom: true
left: true
right: true
}
onBackingWindowVisibleChanged: beginFocusPrime()
onOpenChanged: {
if (open) {
focusPrimed = false
beginFocusPrime()
refocus()
} else {
focusPrimeTimer.stop()
focusPrimed = false
}
}
Connections {
target: popup.panel
function onSshPromptChanged() { popup.refocus() }
function onSshUnlockRequestChanged() { popup.refocus() }
function onStatusChanged() { popup.refocus() }
}
Timer {
id: focusPrimeTimer
interval: 75
repeat: false
onTriggered: {
popup.focusPrimed = true
popup.refocus()
}
}
Rectangle {
anchors.fill: parent
color: Color.menu.scrim
}
MouseArea {
anchors.fill: parent
onClicked: popup.panel.denySshRequest()
}
BorderSurface {
id: card
width: popup.cardWidth
height: popup.cardHeight
anchors.centerIn: parent
radius: Style.cornerRadius
color: Color.popups.background
borderSpec: Border.surfaceSpec("popups", "border", Color.popups.border,
Math.max(1, Style.space(2)))
padding: Style.spacing.panelPadding
// Swallow clicks on unused card space; only a click outside the card is a
// denial. Interactive children declared below remain above this catcher.
MouseArea { anchors.fill: parent; onClicked: {} }
Item {
id: keyScope
anchors.fill: parent
anchors.topMargin: card.contentTopInset
anchors.rightMargin: card.contentRightInset
anchors.bottomMargin: card.contentBottomInset
anchors.leftMargin: card.contentLeftInset
focus: popup.open
Keys.priority: Keys.BeforeItem
Keys.onPressed: function(event) {
if (event.key === Qt.Key_Escape) {
if (!(event.modifiers & ~Qt.KeypadModifier)) {
popup.panel.denySshRequest()
event.accepted = true
} else if (event.modifiers & Qt.ShiftModifier) {
popup.panel.denyAllSshRequests()
event.accepted = true
}
}
}
Flickable {
id: scroller
anchors.fill: parent
contentWidth: width
contentHeight: content.implicitHeight
clip: true
flickableDirection: Flickable.VerticalFlick
boundsBehavior: Flickable.StopAtBounds
interactive: contentHeight > height
Column {
id: content
width: scroller.width
SshUnlockScreen {
id: unlockScreen
panel: popup.panel
active: popup.open && popup.panel.sshPrompt === null
}
SshApprovalScreen {
id: approvalScreen
panel: popup.panel
active: popup.open && popup.panel.sshPrompt !== null
}
}
}
}
}
}
@@ -0,0 +1,210 @@
import QtQuick
import qs.Commons
import qs.Ui
import "BitwardenModel.js" as Model
// SCREEN: SSH signing approval.
//
// The one place a signature is authorised. It states what the companion
// verified -- the requesting user -- and is explicit that everything else
// about the process is context rather than identity.
//
// `panel` is the Panel root. This screen holds no state: it draws the pending
// request and calls back for the answer.
Column {
id: screen
required property var panel
property bool active: panel.activeScreen === "sshApproval"
// A signing decision should never open with an affirmative action focused.
// Both the anchored panel and the centered popup can call this after their
// window receives keyboard focus.
function focusDefault() {
if (screen.active && screen.visible) denyButton.forceActiveFocus()
}
// The bar's foreground and font family rather than the global theme's, the
// same as every other text element in this panel. Text defaults to AutoText,
// so the format is stated even where the string is constant today.
component SshSectionHeader: PanelSectionHeader {
textFormat: Text.PlainText
foreground: screen.panel.fg
fontFamily: screen.panel.fontFamily
}
component SshCaption: Text {
textFormat: Text.PlainText
width: parent ? parent.width : 0
color: screen.panel.dim
font.family: screen.panel.fontFamily
font.pixelSize: Style.font.caption
wrapMode: Text.WordWrap
}
visible: active && panel.sshPrompt !== null
width: parent.width
spacing: Style.space(12)
PanelSeparator {
visible: !screen.panel.sshAgentApprovalPopup
width: parent.width
}
Row {
width: parent.width
spacing: Style.space(8)
Text {
textFormat: Text.PlainText
anchors.verticalCenter: parent.verticalCenter
text: "󰌆"
color: Color.accent
font.family: panel.fontFamily
font.pixelSize: Style.font.body
}
Text {
textFormat: Text.PlainText
anchors.verticalCenter: parent.verticalCenter
text: "SSH signing request"
color: panel.fg
font.family: panel.fontFamily
font.pixelSize: Style.font.body
}
Item { width: Math.max(0, parent.width - Style.space(panel.sshPendingCount > 1 ? 290 : 230)); height: 1 }
Text {
textFormat: Text.PlainText
visible: panel.sshPendingCount > 1
anchors.verticalCenter: parent.verticalCenter
text: "1 of " + panel.sshPendingCount
color: Color.accent
font.family: panel.fontFamily
font.pixelSize: Style.font.caption
font.bold: true
}
Text {
textFormat: Text.PlainText
anchors.verticalCenter: parent.verticalCenter
text: panel.sshPromptRemainingSec + "s left"
color: panel.sshPromptRemainingSec <= 5 ? panel.urgent : panel.dim
font.family: panel.fontFamily
font.pixelSize: Style.font.caption
}
}
// Forwarding is rejected in v1. If one ever reaches here it is
// called out rather than shown as ordinary context, because the
// process named would not be the one using the signature.
SshCaption {
visible: panel.sshPrompt && panel.sshPrompt.forwardedWarning !== ""
text: panel.sshPrompt ? panel.sshPrompt.forwardedWarning : ""
color: panel.urgent
}
SshCaption {
visible: panel.sshAgentLoadActive
text: Model.sshAgentLoadingNote()
}
SshSectionHeader {
text: "KEY"
}
Text {
textFormat: Text.PlainText
width: parent.width
text: panel.sshPrompt ? panel.sshPrompt.keyName : ""
color: panel.fg
font.family: panel.fontFamily
font.pixelSize: Style.font.body
wrapMode: Text.WordWrap
}
// The fingerprint is the value worth checking, so it is shown whole
// rather than elided.
SshCaption {
text: panel.sshPrompt ? panel.sshPrompt.fingerprint : ""
wrapMode: Text.WrapAnywhere
}
SshSectionHeader {
text: "REQUESTED BY"
}
Text {
textFormat: Text.PlainText
width: parent.width
text: panel.sshPrompt
? panel.sshPrompt.processName
: ""
color: panel.fg
font.family: panel.fontFamily
font.pixelSize: Style.font.body
wrapMode: Text.WordWrap
}
SshCaption {
text: panel.sshPrompt ? panel.sshPrompt.processPath : ""
wrapMode: Text.WrapAnywhere
}
SshCaption {
text: panel.sshPrompt ? panel.sshPrompt.provenanceNote : ""
}
PanelSeparator {
visible: !screen.panel.sshAgentApprovalPopup
width: parent.width
}
// Deny leads, and nothing is activated by a bare Enter: a stray
// keypress must not be able to sign.
Row {
width: parent.width
spacing: Style.space(8)
Button {
id: denyButton
text: "Deny (Esc)"
iconText: "󰅘"
fontFamily: panel.fontFamily
fontSize: Style.font.bodySmall
focusable: true
onClicked: panel.denySshRequest()
}
Button {
visible: panel.sshPendingCount > 1
text: "Deny all (" + panel.sshPendingCount + ")"
iconText: "󰅙"
fontFamily: panel.fontFamily
fontSize: Style.font.bodySmall
focusable: true
onClicked: panel.denyAllSshRequests()
}
Button {
text: "Approve once"
iconText: "󰄬"
fontFamily: panel.fontFamily
fontSize: Style.font.bodySmall
focusable: true
onClicked: panel.approveSshRequest(0)
}
}
Button {
visible: panel.sshPrompt && panel.sshPrompt.grantOffered
text: panel.sshPrompt ? panel.sshPrompt.grantLabel : ""
iconText: "󰔟"
tooltipText: "Sign further requests from this same program with this key, without asking again, until the window expires"
fontFamily: panel.fontFamily
fontSize: Style.font.bodySmall
focusable: true
onClicked: panel.approveSshRequest(panel.sshPrompt ? panel.sshPrompt.grantSeconds : 0)
}
}
@@ -0,0 +1,332 @@
import QtQuick
import qs.Commons
import qs.Ui
import "BitwardenModel.js" as Model
// The first step of an SSH request when the vault is locked. Uses the same
// layout, pulsing fingerprint animation, and unlock controls as the panel's
// unlock screen, while keeping the SSH request context visible.
Column {
id: screen
required property var panel
property bool active: false
visible: active && panel.sshUnlockRequest !== null
width: parent ? parent.width : 0
spacing: Style.space(12)
onVisibleChanged: if (!visible && eyeBtnUnlock) eyeBtnUnlock.revealed = false
onActiveChanged: if (!active && eyeBtnUnlock) eyeBtnUnlock.revealed = false
component UnlockCaption: Text {
textFormat: Text.PlainText
width: parent ? parent.width : 0
color: screen.panel.dim
font.family: screen.panel.fontFamily
font.pixelSize: Style.font.caption
wrapMode: Text.WordWrap
}
function focusDefault() {
if (!screen.active || !screen.visible) return
screen.panel.prepareUnlock()
if (screen.panel.fingerprintReady) screen.panel.startFingerprintUnlock()
Qt.callLater(function() {
if (!screen.active || screen.panel.status !== "locked") return
if (screen.panel.pinReady) pinField.forceActiveFocus()
else passwordField.forceActiveFocus()
})
}
// Centered header matching Panel.qml Screen 2
Column {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Style.space(6)
Text {
id: fingerprintIcon
textFormat: Text.PlainText
anchors.horizontalCenter: parent.horizontalCenter
text: screen.panel.fingerprintScanning ? "󰈷" : "󰌋"
color: screen.panel.fingerprintScanning ? Color.accent : screen.panel.fg
opacity: 0.85
font.family: screen.panel.fontFamily
font.pixelSize: Style.space(38)
SequentialAnimation on opacity {
running: screen.panel.fingerprintScanning
loops: Animation.Infinite
NumberAnimation { to: 0.35; duration: 700; easing.type: Easing.InOutQuad }
NumberAnimation { to: 0.95; duration: 700; easing.type: Easing.InOutQuad }
onStopped: fingerprintIcon.opacity = 0.85
}
}
Text {
textFormat: Text.PlainText
anchors.horizontalCenter: parent.horizontalCenter
text: screen.panel.status === "unlocked"
? "Loading SSH keys"
: (screen.panel.fingerprintReady ? "Unlock Vault" : "Enter Master Password")
color: screen.panel.fg
font.family: screen.panel.fontFamily
font.pixelSize: Style.font.title
font.bold: true
}
Text {
textFormat: Text.PlainText
visible: screen.panel.userEmail !== ""
anchors.horizontalCenter: parent.horizontalCenter
text: screen.panel.userEmail
color: screen.panel.dim
font.family: screen.panel.fontFamily
font.pixelSize: Style.font.bodySmall
}
}
UnlockCaption {
text: {
var request = screen.panel.sshUnlockRequest
var prefix = "Vault needs to be unlocked first: "
if (!request) return "Vault needs to be unlocked first."
if (request.keyName !== "") {
return prefix + request.keyName + " is needed by " + request.processName + "."
}
return prefix + request.processName + " is asking which SSH keys are available."
}
horizontalAlignment: Text.AlignHCenter
color: screen.panel.fg
}
UnlockCaption {
text: "Unlocking only loads the key. You will still approve the signing request separately."
horizontalAlignment: Text.AlignHCenter
}
// Fingerprint status / prompt
Text {
textFormat: Text.PlainText
visible: screen.panel.fingerprintMessage !== ""
width: parent.width
horizontalAlignment: Text.AlignHCenter
text: screen.panel.fingerprintMessage
color: screen.panel.fingerprintScanning ? Color.accent : screen.panel.dim
font.family: screen.panel.fontFamily
font.pixelSize: Style.font.bodySmall
wrapMode: Text.WordWrap
}
// Offered when fingerprint unlock is on but nothing is stored yet
Text {
textFormat: Text.PlainText
visible: screen.panel.fingerprintUnlock && screen.panel.fingerprintAvailable && !screen.panel.fingerprintStored
width: parent.width
horizontalAlignment: Text.AlignHCenter
text: "󰈷 Unlock once with your master password to enable fingerprint unlock."
color: screen.panel.dim
font.family: screen.panel.fontFamily
font.pixelSize: Style.font.caption
wrapMode: Text.WordWrap
}
// Checking / keys loading into helper indicator
Rectangle {
visible: screen.panel.status === "checking"
|| (screen.panel.status === "unlocked" && screen.panel.sshAgentLoadActive)
width: parent.width
height: loadingText.implicitHeight + Style.space(20)
radius: Style.cornerRadius
color: Util.alpha(Color.popups.text, 0.06)
Text {
id: loadingText
textFormat: Text.PlainText
anchors.centerIn: parent
width: parent.width - Style.space(24)
text: screen.panel.status === "checking"
? "Checking vault status..."
: Model.sshAgentLoadingNote()
color: screen.panel.fg
font.family: screen.panel.fontFamily
font.pixelSize: Style.font.bodySmall
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
// PIN entry, offered above the password field when one is set
Column {
visible: screen.panel.status === "locked" && screen.panel.pinReady
width: parent.width
spacing: Style.space(8)
Text {
textFormat: Text.PlainText
text: "PIN"
color: screen.panel.dim
font.family: screen.panel.fontFamily
font.pixelSize: Style.font.caption
font.bold: true
}
Row {
width: parent.width
spacing: Style.space(8)
TextField {
id: pinField
width: parent.width - pinUnlockBtn.width - Style.space(8)
placeholderText: "Enter your PIN..."
password: true
text: screen.panel.pinEntry
onTextChanged: screen.panel.pinEntry = text.replace(/[^0-9]/g, "")
onAccepted: screen.panel.submitPinUnlock()
enabled: !screen.panel.pinBusy && !screen.panel.isUnlocking
}
Button {
id: pinUnlockBtn
text: screen.panel.pinBusy ? "Checking..." : "Unlock"
iconText: screen.panel.pinBusy ? "󰑐" : "󰌿"
iconSpinning: screen.panel.pinBusy
selected: true
accent: Color.accent
fontFamily: screen.panel.fontFamily
focusable: true
enabled: !screen.panel.pinBusy && !screen.panel.isUnlocking
onClicked: screen.panel.submitPinUnlock()
}
}
Text {
textFormat: Text.PlainText
visible: screen.panel.pinError !== ""
width: parent.width
text: screen.panel.pinError
color: screen.panel.urgent
font.family: screen.panel.fontFamily
font.pixelSize: Style.font.bodySmall
wrapMode: Text.WordWrap
}
Text {
textFormat: Text.PlainText
text: "or use your master password below"
color: screen.panel.dim
font.family: screen.panel.fontFamily
font.pixelSize: Style.font.caption
}
}
// Fingerprint / Password column matching Panel.qml
Column {
visible: screen.panel.status === "locked"
width: parent.width
spacing: Style.space(10)
Button {
visible: screen.panel.fingerprintReady
width: parent.width
text: screen.panel.fingerprintScanning ? "Waiting for fingerprint..." : "Unlock with Fingerprint"
iconText: "󰈷"
selected: true
accent: Color.accent
fontFamily: screen.panel.fontFamily
focusable: true
enabled: !screen.panel.isUnlocking && !screen.panel.fingerprintScanning
onClicked: screen.panel.startFingerprintUnlock()
}
Row {
width: parent.width
spacing: Style.space(8)
TextField {
id: passwordField
width: parent.width - eyeBtnUnlock.width - Style.space(8)
placeholderText: "Master password..."
password: !eyeBtnUnlock.revealed
text: screen.panel.masterPassword
onTextChanged: screen.panel.masterPassword = text
onActiveFocusChanged: if (activeFocus) screen.panel.prepareUnlock()
onAccepted: screen.panel.unlockVault()
enabled: !screen.panel.isUnlocking
}
Button {
id: eyeBtnUnlock
property bool revealed: false
iconText: revealed ? "󰈉" : "󰈈"
tooltipText: revealed ? "Hide password" : "Show password"
fontFamily: screen.panel.fontFamily
focusable: true
onClicked: revealed = !revealed
}
}
Button {
width: parent.width
text: screen.panel.isUnlocking ? "Unlocking..." : "Unlock Vault"
iconText: screen.panel.isUnlocking ? "󰑐" : "󰌋"
iconSpinning: screen.panel.isUnlocking
selected: true
accent: Color.accent
fontFamily: screen.panel.fontFamily
focusable: true
enabled: !screen.panel.isUnlocking
onClicked: screen.panel.unlockVault()
}
}
UnlockCaption {
visible: screen.panel.errorMessage !== ""
text: screen.panel.errorMessage
color: screen.panel.urgent
horizontalAlignment: Text.AlignHCenter
}
UnlockCaption {
visible: screen.panel.status === "unauthenticated"
text: "Sign in from the Bitwarden panel before using vault SSH keys."
color: screen.panel.urgent
horizontalAlignment: Text.AlignHCenter
}
Row {
width: parent.width
spacing: Style.space(8)
Button {
text: "Not now (Esc)"
iconText: "󰅘"
fontFamily: screen.panel.fontFamily
fontSize: Style.font.bodySmall
focusable: true
onClicked: screen.panel.denySshRequest()
}
Button {
visible: screen.panel.sshUnlockPendingCount > 1
text: "Deny all (" + screen.panel.sshUnlockPendingCount + ")"
iconText: "󰅙"
fontFamily: screen.panel.fontFamily
fontSize: Style.font.bodySmall
focusable: true
onClicked: screen.panel.denyAllSshRequests()
}
Item { width: Math.max(0, parent.width - Style.space(screen.panel.sshUnlockPendingCount > 1 ? 280 : 160)); height: 1 }
Text {
textFormat: Text.PlainText
anchors.verticalCenter: parent.verticalCenter
text: screen.panel.sshPromptRemainingSec + "s left"
color: screen.panel.sshPromptRemainingSec <= 5
? screen.panel.urgent : screen.panel.dim
font.family: screen.panel.fontFamily
font.pixelSize: Style.font.caption
}
}
}
@@ -0,0 +1,127 @@
import QtQuick
import qs.Commons
import qs.Ui
// A transient panel-local notice. It anchors to its parent as an overlay and
// deliberately reports no height to the parent's content layout.
BorderSurface {
id: root
required property string statusMessage
required property string errorMessage
required property bool statusSuppressed
required property color foreground
required property color surfaceColor
required property color accentColor
required property color urgentColor
required property string fontFamily
readonly property bool showsError: root.errorMessage !== ""
readonly property bool showsStatus: root.statusMessage !== "" && !root.statusSuppressed
readonly property bool shown: showsError || showsStatus
readonly property color tone: showsError ? root.urgentColor : root.accentColor
// Optional recovery offered alongside an error. Empty means none.
property string actionLabel: ""
signal errorDismissed()
signal actionRequested()
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.space(10)
width: Math.min(parent.width - Style.space(20), Style.space(390))
implicitHeight: noticeRow.implicitHeight + Style.space(16)
z: 20
visible: opacity > 0
enabled: shown
opacity: shown ? 1 : 0
color: root.surfaceColor
radius: Style.cornerRadius
borderSpec: Border.surfaceSpec("menu", "border", root.tone, 1)
Accessible.role: Accessible.AlertMessage
Accessible.name: (root.showsError ? "Needs attention: " : "Status: ") + noticeMessage.text
Accessible.ignored: !root.shown
Behavior on opacity {
NumberAnimation { duration: 140; easing.type: Easing.OutQuad }
}
// Consume pointer presses on the floating surface so covered controls
// cannot be activated through it.
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.AllButtons
}
Row {
id: noticeRow
anchors.fill: parent
anchors.margins: Style.space(8)
spacing: Style.space(8)
Text {
textFormat: Text.PlainText
id: noticeIcon
anchors.verticalCenter: parent.verticalCenter
text: root.showsError ? "󰅚" : "󰋼"
color: root.tone
font.family: root.fontFamily
font.pixelSize: Style.font.body
}
Column {
anchors.verticalCenter: parent.verticalCenter
width: parent.width - noticeIcon.implicitWidth - Style.space(8)
- (dismissNoticeButton.visible
? dismissNoticeButton.implicitWidth + Style.space(8)
: 0)
spacing: Style.space(2)
Text {
textFormat: Text.PlainText
width: parent.width
text: root.showsError ? "NEEDS ATTENTION" : "STATUS"
color: root.tone
font.family: root.fontFamily
font.pixelSize: Style.font.caption
font.bold: true
}
Text {
textFormat: Text.PlainText
id: noticeMessage
width: parent.width
text: root.showsError ? root.errorMessage : root.statusMessage
color: root.foreground
font.family: root.fontFamily
font.pixelSize: Style.font.bodySmall
wrapMode: Text.Wrap
}
}
// An error the user can do something about carries the doing with it. A
// failed save is the case this exists for: the message says the vault
// refused it, and the button is the way back to what was typed.
PanelActionButton {
id: noticeActionButton
visible: root.showsError && root.actionLabel !== ""
anchors.verticalCenter: parent.verticalCenter
iconText: "󰑌"
tooltipText: root.actionLabel
fontFamily: root.fontFamily
onClicked: root.actionRequested()
}
PanelActionButton {
id: dismissNoticeButton
visible: root.showsError
anchors.verticalCenter: parent.verticalCenter
iconText: "󰅖"
tooltipText: "Dismiss message"
fontFamily: root.fontFamily
onClicked: root.errorDismissed()
}
}
}
@@ -0,0 +1,37 @@
import QtQuick
// Wheel scrolling for a Flickable, at a rate chosen here rather than inherited.
//
// Qt moves a Flickable by the platform's wheel-scroll-lines, which is tuned for
// a full-screen document and is a crawl in a panel three hundred pixels tall:
// several turns of the wheel to cross one screen of settings. The step below is
// a multiple of that, applied identically everywhere so no two views in the
// panel scroll at different speeds.
//
// Placed inside the Flickable it drives, which it takes as its target. It
// accepts the event, so the Flickable's own slower handling does not also run.
WheelHandler {
id: root
required property Flickable view
// Pixels per wheel notch. A notch is 120 eighths-of-a-degree. Roughly twice
// what Qt would move on its own -- enough that a screenful is a couple of
// turns rather than half a dozen, and not so much that a notch overshoots
// the thing being scrolled to. One number, one place; every view reads it.
property real step: 90
acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad
onWheel: function(event) {
var notches = event.angleDelta.y / 120
if (notches === 0) return
// A touchpad sends many small deltas rather than whole notches, and
// multiplying those the same way overshoots wildly. Scale the fractional
// part as it comes; only whole notches get the full step.
var limit = Math.max(0, root.view.contentHeight - root.view.height)
var next = root.view.contentY - notches * root.step
root.view.contentY = Math.max(0, Math.min(limit, next))
event.accepted = true
}
}

Some files were not shown because too many files have changed in this diff Show More