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:
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"format": "omarchy-config",
|
||||||
|
"version": 1,
|
||||||
|
"synced_by": "gladimdim.config-sync"
|
||||||
|
}
|
||||||
@@ -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" })
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
-- Extra autostart processes.
|
||||||
|
-- o.launch_on_start("my-service")
|
||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
@@ -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")
|
||||||
@@ -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
|
||||||
|
# }
|
||||||
@@ -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 })
|
||||||
@@ -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,
|
||||||
|
-- },
|
||||||
|
-- })
|
||||||
@@ -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.
|
||||||
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
screencopy {
|
||||||
|
allow_token_by_default = true
|
||||||
|
custom_picker_binary = hyprland-preview-share-picker
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
██████████████████████████████████████████████████████
|
||||||
|
██████████████████████████████████████████████████████
|
||||||
|
████ ████ ████
|
||||||
|
████ ████ ████
|
||||||
|
████ █████████████████████ ████████ ████
|
||||||
|
████ █████████████████████ ████████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████████████ ████ ████
|
||||||
|
████████████ ████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████ ████ ████ ████
|
||||||
|
████ ██████████████████████████████████████ ████
|
||||||
|
████ ██████████████████████████████████████ ████
|
||||||
|
████ ████ ████
|
||||||
|
████ ████ ████
|
||||||
|
█████████████████████████████ ████████████████████
|
||||||
|
█████████████████████████████ ████████████████████
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
▄▄▄
|
||||||
|
▄█████▄ ▄███████████▄ ▄███████ ▄███████ ▄███████ ▄█ █▄ ▄█ █▄
|
||||||
|
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||||
|
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ █▀ ███ ███ ███ ███
|
||||||
|
███ ███ ███ ███ ███ ▄███▄▄▄███ ▄███▄▄▄██▀ ███ ▄███▄▄▄███▄ ███▄▄▄███
|
||||||
|
███ ███ ███ ███ ███ ▀███▀▀▀███ ▀███▀▀▀▀ ███ ▀▀███▀▀▀███ ▀▀▀▀▀▀███
|
||||||
|
███ ███ ███ ███ ███ ███ ███ ██████████ ███ █▄ ███ ███ ▄██ ███
|
||||||
|
███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███ ███
|
||||||
|
▀█████▀ ▀█ ███ █▀ ███ █▀ ███ ███ ███████▀ ███ █▀ ▀█████▀
|
||||||
|
███ █▀
|
||||||
@@ -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"
|
||||||
@@ -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"
|
||||||
@@ -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": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
[font]
|
||||||
|
base-size = 9
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
azure-glow
|
||||||
@@ -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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 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.
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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"
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
general {
|
||||||
|
col.active_border = rgb(00ccff) rgb(0055ff) 45deg
|
||||||
|
col.inactive_border = rgb(00334d)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
Yaru-blue
|
||||||
@@ -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
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
Reference in New Issue
Block a user