Add the installer, uninstaller, and generated docs
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
# Host config for the Blob speaker tuning.
|
||||
#
|
||||
# This exists so the tuning gets its own PipeWire client rather than sharing
|
||||
# PipeWire's stock filter-chain.conf. That config merges every fragment in
|
||||
# ~/.config/pipewire/filter-chain.conf.d/, so hosting the tuning there would load
|
||||
# any unrelated filter a user keeps in that directory -- duplicating filters
|
||||
# already hosted elsewhere, and stopping them all when the tuning is switched off.
|
||||
#
|
||||
# Installed as ~/.config/pipewire/blob-speaker-tuning.conf with the tuning
|
||||
# graph merged from blob-speaker-tuning.conf.d/, and run with
|
||||
# pipewire -c blob-speaker-tuning.conf
|
||||
#
|
||||
# The contents are the minimum a filter-hosting client needs, taken from
|
||||
# /usr/share/pipewire/filter-chain.conf.
|
||||
|
||||
context.properties = {
|
||||
log.level = 0
|
||||
}
|
||||
|
||||
context.spa-libs = {
|
||||
audio.convert.* = audioconvert/libspa-audioconvert
|
||||
support.* = support/libspa-support
|
||||
}
|
||||
|
||||
context.modules = [
|
||||
# Boost the audio thread priority.
|
||||
{ name = libpipewire-module-rt
|
||||
args = { }
|
||||
flags = [ ifexists nofail ]
|
||||
}
|
||||
|
||||
# The native communication protocol.
|
||||
{ name = libpipewire-module-protocol-native }
|
||||
|
||||
# Lets this process provide nodes to PipeWire.
|
||||
{ name = libpipewire-module-client-node }
|
||||
|
||||
# Wraps nodes in an adapter with a converter and resampler.
|
||||
{ name = libpipewire-module-adapter }
|
||||
]
|
||||
@@ -0,0 +1,140 @@
|
||||
# Dell XPS 14 / XPS 16 (2026) speaker tuning.
|
||||
#
|
||||
# Biquad chain fitted to the measured response of the xps-audio-linux EasyEffects
|
||||
# profile under a dense pink-weighted multitone of 104 bin-aligned tones,
|
||||
# followed by a lookahead limiter. Measures 1.24 dB RMS against that reference
|
||||
# (0.97 dB weighted over the fit's own error metric).
|
||||
#
|
||||
# Fitted and measured on the XPS 14 (SKU 0DB9); the XPS 16 (0DBA) is covered on
|
||||
# report that the same profile suits it. See tuning.conf.
|
||||
#
|
||||
# Q below 200 Hz is capped at 1.8 on purpose. A closer magnitude fit is possible
|
||||
# with high-Q sections, but the reference produces its narrow bass features by
|
||||
# convolution, and reproducing them with high-Q biquads swung group delay 31 ms
|
||||
# across 63-80 Hz, which smears bass transients. The cap costs 0.33 dB and
|
||||
# halves the swing.
|
||||
#
|
||||
# This is a plain filter-chain sink rather than a WirePlumber smart filter. A
|
||||
# smart filter is the better shape -- it would leave the real device as the
|
||||
# default output instead of adding a second one -- but on PipeWire 1.6.8 /
|
||||
# WirePlumber 0.5.15 this graph loads and links correctly as a smart filter and
|
||||
# then passes audio through unprocessed: its controls are present and
|
||||
# mpv -> filter -> sink links are made, yet the filter's input monitor and the
|
||||
# speaker sink's monitor measure identically. Revisit when that is understood.
|
||||
#
|
||||
# Channels are wired explicitly because the limiter is a stereo plugin; a mono
|
||||
# graph is duplicated per channel and would limit each side independently,
|
||||
# shifting the stereo image on bass transients.
|
||||
|
||||
context.modules = [
|
||||
{ name = libpipewire-module-filter-chain
|
||||
args = {
|
||||
node.description = "Laptop Speakers"
|
||||
media.name = "Laptop Speakers"
|
||||
|
||||
filter.graph = {
|
||||
nodes = [
|
||||
{ type = builtin name = s0_l label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
|
||||
{ type = builtin name = s1_l label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
|
||||
{ type = builtin name = s2_l label = bq_peaking control = { "Freq" = 83.4 "Q" = 1.8 "Gain" = -8.0 } }
|
||||
{ type = builtin name = s3_l label = bq_peaking control = { "Freq" = 100.4 "Q" = 1.59 "Gain" = 7.47 } }
|
||||
{ type = builtin name = s4_l label = bq_peaking control = { "Freq" = 250.5 "Q" = 2.966 "Gain" = -4.7 } }
|
||||
{ type = builtin name = s5_l label = bq_peaking control = { "Freq" = 419.8 "Q" = 3.0 "Gain" = -5.83 } }
|
||||
{ type = builtin name = s6_l label = bq_peaking control = { "Freq" = 631.3 "Q" = 2.515 "Gain" = -10.33 } }
|
||||
{ type = builtin name = s7_l label = bq_peaking control = { "Freq" = 894.4 "Q" = 4.0 "Gain" = -2.42 } }
|
||||
{ type = builtin name = s8_l label = bq_peaking control = { "Freq" = 1355.7 "Q" = 2.884 "Gain" = 6.92 } }
|
||||
{ type = builtin name = s9_l label = bq_peaking control = { "Freq" = 1707.2 "Q" = 1.311 "Gain" = -6.54 } }
|
||||
{ type = builtin name = s10_l label = bq_peaking control = { "Freq" = 3100.0 "Q" = 0.5 "Gain" = -10.09 } }
|
||||
{ type = builtin name = s11_l label = bq_peaking control = { "Freq" = 3200.0 "Q" = 1.048 "Gain" = 3.09 } }
|
||||
{ type = builtin name = s12_l label = bq_highshelf control = { "Freq" = 6015.2 "Q" = 1.5 "Gain" = -1.34 } }
|
||||
|
||||
{ type = builtin name = s0_r label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
|
||||
{ type = builtin name = s1_r label = bq_highpass control = { "Freq" = 60.9 "Q" = 1.0 } }
|
||||
{ type = builtin name = s2_r label = bq_peaking control = { "Freq" = 83.4 "Q" = 1.8 "Gain" = -8.0 } }
|
||||
{ type = builtin name = s3_r label = bq_peaking control = { "Freq" = 100.4 "Q" = 1.59 "Gain" = 7.47 } }
|
||||
{ type = builtin name = s4_r label = bq_peaking control = { "Freq" = 250.5 "Q" = 2.966 "Gain" = -4.7 } }
|
||||
{ type = builtin name = s5_r label = bq_peaking control = { "Freq" = 419.8 "Q" = 3.0 "Gain" = -5.83 } }
|
||||
{ type = builtin name = s6_r label = bq_peaking control = { "Freq" = 631.3 "Q" = 2.515 "Gain" = -10.33 } }
|
||||
{ type = builtin name = s7_r label = bq_peaking control = { "Freq" = 894.4 "Q" = 4.0 "Gain" = -2.42 } }
|
||||
{ type = builtin name = s8_r label = bq_peaking control = { "Freq" = 1355.7 "Q" = 2.884 "Gain" = 6.92 } }
|
||||
{ type = builtin name = s9_r label = bq_peaking control = { "Freq" = 1707.2 "Q" = 1.311 "Gain" = -6.54 } }
|
||||
{ type = builtin name = s10_r label = bq_peaking control = { "Freq" = 3100.0 "Q" = 0.5 "Gain" = -10.09 } }
|
||||
{ type = builtin name = s11_r label = bq_peaking control = { "Freq" = 3200.0 "Q" = 1.048 "Gain" = 3.09 } }
|
||||
{ type = builtin name = s12_r label = bq_highshelf control = { "Freq" = 6015.2 "Q" = 1.5 "Gain" = -1.34 } }
|
||||
{ type = lv2
|
||||
name = limiter
|
||||
plugin = "http://lsp-plug.in/plugins/lv2/limiter_stereo"
|
||||
control = {
|
||||
# Both default to enabled: "alr" regulates level toward the
|
||||
# threshold and "boost" normalises the threshold up to full
|
||||
# scale. A fixed tuning must switch them off or its tone drifts
|
||||
# with programme level.
|
||||
"alr" = 0
|
||||
"boost" = 0
|
||||
"g_in" = 0.5456
|
||||
"th" = 0.891
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
links = [
|
||||
{ output = "s0_l:Out" input = "s1_l:In" }
|
||||
{ output = "s1_l:Out" input = "s2_l:In" }
|
||||
{ output = "s2_l:Out" input = "s3_l:In" }
|
||||
{ output = "s3_l:Out" input = "s4_l:In" }
|
||||
{ output = "s4_l:Out" input = "s5_l:In" }
|
||||
{ output = "s5_l:Out" input = "s6_l:In" }
|
||||
{ output = "s6_l:Out" input = "s7_l:In" }
|
||||
{ output = "s7_l:Out" input = "s8_l:In" }
|
||||
{ output = "s8_l:Out" input = "s9_l:In" }
|
||||
{ output = "s9_l:Out" input = "s10_l:In" }
|
||||
{ output = "s10_l:Out" input = "s11_l:In" }
|
||||
{ output = "s11_l:Out" input = "s12_l:In" }
|
||||
{ output = "s12_l:Out" input = "limiter:in_l" }
|
||||
{ output = "s0_r:Out" input = "s1_r:In" }
|
||||
{ output = "s1_r:Out" input = "s2_r:In" }
|
||||
{ output = "s2_r:Out" input = "s3_r:In" }
|
||||
{ output = "s3_r:Out" input = "s4_r:In" }
|
||||
{ output = "s4_r:Out" input = "s5_r:In" }
|
||||
{ output = "s5_r:Out" input = "s6_r:In" }
|
||||
{ output = "s6_r:Out" input = "s7_r:In" }
|
||||
{ output = "s7_r:Out" input = "s8_r:In" }
|
||||
{ output = "s8_r:Out" input = "s9_r:In" }
|
||||
{ output = "s9_r:Out" input = "s10_r:In" }
|
||||
{ output = "s10_r:Out" input = "s11_r:In" }
|
||||
{ output = "s11_r:Out" input = "s12_r:In" }
|
||||
{ output = "s12_r:Out" input = "limiter:in_r" }
|
||||
]
|
||||
|
||||
inputs = [ "s0_l:In" "s0_r:In" ]
|
||||
outputs = [ "limiter:out_l" "limiter:out_r" ]
|
||||
}
|
||||
|
||||
audio.channels = 2
|
||||
audio.position = [ FL FR ]
|
||||
|
||||
capture.props = {
|
||||
node.name = "blob_speaker_tuning"
|
||||
media.class = Audio/Sink
|
||||
}
|
||||
playback.props = {
|
||||
node.name = "blob_speaker_tuning_output"
|
||||
node.passive = true
|
||||
target.object = "@SPEAKER_SINK@"
|
||||
# This stream is the filter's output and is a movable sink input like any
|
||||
# other, so anything that reroutes "all streams" to a newly selected
|
||||
# output would drag the processing along with it -- onto headphones, or
|
||||
# into the tuning's own sink, which is a cycle. Pin it.
|
||||
node.dont-move = true
|
||||
# If the speaker sink is not present yet -- the tuning host can start
|
||||
# before the device is discovered -- WirePlumber would otherwise link this
|
||||
# output to whatever default exists, quietly tuning the wrong device while
|
||||
# the tuning sink still looks healthy. Wait for the named target instead.
|
||||
# Both are needed: without linger, WirePlumber destroys the node rather
|
||||
# than waiting (see its scripts/linking/find-defined-target.lua).
|
||||
node.dont-fallback = true
|
||||
node.linger = true
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,39 @@
|
||||
## Dell XPS 14 / XPS 16 (2026) internal speakers.
|
||||
##
|
||||
## Thirteen biquads and a lookahead limiter, applied as a PipeWire filter-chain
|
||||
## in front of the internal speaker sink. The stock Linux path already loads
|
||||
## Dell's Cirrus smart-amplifier firmware; this adds the perceptual voicing the
|
||||
## Windows Waves layer provides and Linux does not.
|
||||
|
||||
description="Dell XPS 14/16 (2026) speakers"
|
||||
## Matched on the DMI product SKU, which is what Dell keys the Cirrus speaker
|
||||
## firmware on -- 10280db9 for the XPS 14 and 10280dba for the XPS 16 -- so it
|
||||
## identifies the speaker hardware itself rather than a marketing name. Compared as
|
||||
## whole values, so this cannot widen to the rest of the XPS line.
|
||||
##
|
||||
## 0DB9 XPS 14 -- measured here, see below
|
||||
## 0DBA XPS 16 -- included on report that this profile suits it, not measured
|
||||
match_sku=("0DB9" "0DBA")
|
||||
## Unescaped dots: this is passed to awk as a string, where a backslash escape
|
||||
## would be consumed before the regex sees it.
|
||||
sink_pattern='^alsa_output.*sof_sdw.*HiFi__Speaker__sink$'
|
||||
|
||||
## Provenance. Derived by measuring the response of the xps-clone EasyEffects
|
||||
## profile from https://github.com/spencerbull/xps-audio-linux (MIT) and fitting
|
||||
## a biquad chain to it. No upstream asset is redistributed: the convolution
|
||||
## impulse response is not carried, so this tuning has no binary blob and is
|
||||
## sample-rate agnostic.
|
||||
derived_from="xps-audio-linux xps-clone (MIT, spencerbull)"
|
||||
validated_by="dhh"
|
||||
validated_on="2026-07-24"
|
||||
## The measurements below were taken on the XPS 14 (0DB9). The XPS 16 (0DBA) is
|
||||
## covered on report rather than measurement; re-measure there before treating
|
||||
## these figures as describing it.
|
||||
validated_hardware="XPS 14 DA14260 (0DB9)"
|
||||
|
||||
## Measured against that reference under a dense pink-weighted multitone of 104
|
||||
## bin-aligned tones. See docs/AUDIO-TUNING.md for how to reproduce these.
|
||||
magnitude_rms_db="1.24"
|
||||
bass_group_delay_swing_ms="13.2"
|
||||
limiter_headroom_db="1.6" ## worst-case peak on a hot master vs -1 dBFS
|
||||
dynamic_range_delta_lu="0.1"
|
||||
@@ -0,0 +1,215 @@
|
||||
{
|
||||
// Blob menu definition.
|
||||
// JSONC is used for Neovim-friendly highlighting, comments, and trailing commas.
|
||||
// IDs are object keys. Dotted IDs imply hierarchy: trigger.share.file belongs
|
||||
// under trigger.share.
|
||||
// Kind is inferred: action -> action, target -> link, otherwise submenu.
|
||||
// Dotted IDs define the tree. Use provider:"name" only when the submenu
|
||||
// calls provider_name() or a command named "name" to return JSON rows.
|
||||
// Optional fields:
|
||||
// aliases alternate `blob menu summon <name>` routes; also searchable.
|
||||
// Reserved for established names users already type; new entries
|
||||
// should not normally add any.
|
||||
// iconFont font family used for the icon glyph, when it differs from the menu font
|
||||
// title header text shown when the submenu is open; defaults to label
|
||||
// when shell condition; hide row when it fails
|
||||
// checked shell condition; append ✓ when it succeeds
|
||||
|
||||
// Root Menu
|
||||
"apps": {"icon":"","label":"Apps","aliases":["app","applications"],"provider":"apps"},
|
||||
"blob": {"icon":"","label":"Blob"},
|
||||
"learn": {"icon":"","label":"Learn"},
|
||||
"trigger": {"icon":"","label":"Trigger"},
|
||||
"style": {"icon":"","label":"Style"},
|
||||
"setup": {"icon":"","label":"Setup","aliases":["settings"]},
|
||||
"install": {"icon":"","label":"Install"},
|
||||
"remove": {"icon":"","label":"Remove","aliases":["uninstall"]},
|
||||
"update": {"icon":"","label":"Update","aliases":["restart","refresh"]},
|
||||
"about": {"icon":"","label":"About","action":"blob-launch-about"},
|
||||
"system": {"icon":"","label":"System","aliases":["power-menu"]},
|
||||
|
||||
// Blob
|
||||
"blob.wallpaper": {"icon":"","label":"Wallpaper","action":"blob-wallpaper-set --menu","description":"Pick a wallpaper from ~/wallpapers"},
|
||||
"blob.theme": {"icon":"","label":"Theme","action":"blob-theme-menu","description":"Pick a local or bundled color theme"},
|
||||
"blob.dynamic": {"icon":"","label":"Dynamic theme","action":"blob-theme-dynamic","description":"Recolor from the current wallpaper"},
|
||||
"blob.glass": {"icon":"","label":"Toggle glass","action":"blob-toggle-glass toggle","checked":"test -f $HOME/.local/state/blob/toggles/hypr/blob-glass.lua"},
|
||||
|
||||
// System
|
||||
"system.screensaver": {"icon":"","label":"Screensaver","action":"blob-launch-screensaver force"},
|
||||
"system.lock": {"icon":"","label":"Lock","action":"blob-system-lock"},
|
||||
"system.suspend": {"icon":"","label":"Suspend","when":"! blob-toggle-enabled suspend-off","action":"systemctl suspend"},
|
||||
"system.hibernate": {"icon":"","label":"Hibernate","when":"blob-hibernation-available","action":"systemctl hibernate"},
|
||||
"system.logout": {"icon":"","label":"Logout","action":"blob-system-logout"},
|
||||
"system.reboot": {"icon":"","label":"Reboot","action":"blob-system-reboot"},
|
||||
"system.shutdown": {"icon":"","label":"Shutdown","action":"blob-system-shutdown"},
|
||||
|
||||
// Learn
|
||||
"learn.keybindings": {"icon":"","label":"Keybindings","action":"blob-menu-keybindings"},
|
||||
"learn.hyprland": {"icon":"","label":"Hyprland","action":"blob-launch-browser 'https://wiki.hypr.land/'"},
|
||||
"learn.arch": {"icon":"","label":"Arch","action":"blob-launch-browser 'https://wiki.archlinux.org/title/Main_page'"},
|
||||
"learn.neovim": {"icon":"","label":"Neovim","action":"blob-launch-browser 'https://www.lazyvim.org/keymaps'"},
|
||||
"learn.bash": {"icon":"","label":"Bash","action":"blob-launch-browser 'https://devhints.io/bash'"},
|
||||
|
||||
// Trigger
|
||||
"trigger.emoji": {"icon":"","label":"Emoji","aliases":["emoji","emojis"],"action":"blob-menu-emoji"},
|
||||
"trigger.reminder": {"icon":"","label":"Reminder","aliases":["reminder"]},
|
||||
"trigger.capture": {"icon":"","label":"Capture","aliases":["capture","screenshot","screenrecord","screen-record","screenrecording"]},
|
||||
"trigger.capture.screenshot": {"icon":"","label":"Screenshot","action":"blob-capture-screenshot"},
|
||||
"trigger.capture.screenrecord.stop": {"icon":"","label":"Stop Screenrecording","when":"pgrep -f '^gpu-screen-recorder'","action":"blob-capture-record --stop-recording"},
|
||||
"trigger.capture.screenrecord": {"icon":"","label":"Screenrecord"},
|
||||
"trigger.capture.text": {"icon":"","label":"Text","action":"blob-capture-text"},
|
||||
"trigger.capture.qr": {"icon":"","label":"QR Code","action":"blob-capture-qr"},
|
||||
"trigger.capture.color": {"icon":"","label":"Color","action":"pkill hyprpicker || hyprpicker -a"},
|
||||
"trigger.capture.screenrecord.no-audio": {"icon":"","label":"With no audio","action":"blob-capture-record"},
|
||||
"trigger.capture.screenrecord.desktop-audio": {"icon":"","label":"With desktop audio","action":"blob-capture-record --with-desktop-audio"},
|
||||
"trigger.capture.screenrecord.microphone": {"icon":"","label":"With desktop + microphone audio","action":"blob-capture-record --with-desktop-audio --with-microphone-audio"},
|
||||
"trigger.capture.screenrecord.webcam": {"icon":"","label":"With desktop + microphone audio + webcam","when":"blob-hw-webcam","action":"blob-capture-record-webcam"},
|
||||
"trigger.share": {"icon":"","label":"Share","aliases":["share"]},
|
||||
"trigger.toggle": {"icon":"","label":"Toggle","aliases":["toggle","toggles"]},
|
||||
"trigger.hardware": {"icon":"","label":"Hardware","aliases":["hardware","hw"]},
|
||||
"trigger.tests": {"icon":"","label":"Speed Test"},
|
||||
"trigger.hardware.laptop-display": {"icon":"","label":"Laptop Display","when":"blob-hw-laptop","action":"blob-hypr-monitor-internal toggle"},
|
||||
"trigger.hardware.mirror-display": {"icon":"","label":"Mirror Display","when":"blob-hw-laptop","action":"blob-hypr-monitor-mirror toggle"},
|
||||
"trigger.hardware.hybrid-gpu": {"icon":"","label":"Hybrid GPU","when":"blob-hw-hybrid-gpu","action":"blob-launch-floating blob-toggle-hybrid-gpu"},
|
||||
"trigger.hardware.touchpad": {"icon":"","label":"Touchpad","when":"blob-hw-touchpad","action":"blob-toggle-touchpad"},
|
||||
"trigger.hardware.touchpad-haptics": {"icon":"","label":"Touchpad Haptics"},
|
||||
"trigger.hardware.touchpad-haptics.low": {"icon":"","label":"low","when":"blob-hw-dell-xps-haptic-touchpad && blob-cmd-present dell-xps-touchpad-haptics","checked":"[[ \"$(dell-xps-touchpad-haptics get)\" == \"low\" ]]","action":"dell-xps-touchpad-haptics set low"},
|
||||
"trigger.hardware.touchpad-haptics.mid": {"icon":"","label":"mid","when":"blob-hw-dell-xps-haptic-touchpad && blob-cmd-present dell-xps-touchpad-haptics","checked":"[[ \"$(dell-xps-touchpad-haptics get)\" == \"mid\" ]]","action":"dell-xps-touchpad-haptics set mid"},
|
||||
"trigger.hardware.touchpad-haptics.high": {"icon":"","label":"high","when":"blob-hw-dell-xps-haptic-touchpad && blob-cmd-present dell-xps-touchpad-haptics","checked":"[[ \"$(dell-xps-touchpad-haptics get)\" == \"high\" ]]","action":"dell-xps-touchpad-haptics set high"},
|
||||
"trigger.hardware.touchscreen": {"icon":"","label":"Touchscreen","when":"blob-hw-touchscreen","action":"blob-toggle-touchscreen"},
|
||||
"trigger.reminder.set": {"icon":"","label":"Set one","aliases":["reminder-set","remind"],"action":"blob-reminder -i"},
|
||||
"trigger.reminder.show": {"icon":"","label":"Show all","action":"blob-reminder show"},
|
||||
"trigger.reminder.clear": {"icon":"","label":"Clear all","action":"blob-reminder clear"},
|
||||
"trigger.share.clipboard": {"icon":"","label":"Clipboard","action":"blob-menu-share clipboard"},
|
||||
"trigger.share.file": {"icon":"","label":"File","action":"blob-menu-share file"},
|
||||
"trigger.share.folder": {"icon":"","label":"Folder","action":"blob-menu-share folder"},
|
||||
"trigger.share.receive": {"icon":"","label":"Receive","action":"uwsm-app -- localsend"},
|
||||
"trigger.toggle.idle-lock": {"icon":"","label":"Stay Awake","action":"blob-toggle-idle"},
|
||||
"trigger.toggle.notifications": {"icon":"","label":"Notifications","action":"blob-toggle-silencing"},
|
||||
"trigger.toggle.crash-capture": {"icon":"","label":"Crash Capture","action":"blob-toggle-crash-capture"},
|
||||
"trigger.toggle.screensaver": {"icon":"","label":"Screensaver","action":"blob-toggle-screensaver"},
|
||||
"trigger.toggle.nightlight": {"icon":"","label":"Nightlight","action":"blob-toggle-nightlight"},
|
||||
"trigger.toggle.top-bar": {"icon":"","label":"Menu Bar","action":"blob-toggle-bar"},
|
||||
"trigger.toggle.battery-percentage": {"icon":"","label":"Battery Percentage","when":"blob-hw-laptop","action":"blob-shell blob.power togglePercentage"},
|
||||
"trigger.toggle.workspace-layout": {"icon":"","label":"Workspace Layout","action":"blob-hypr-workspace-layout-toggle"},
|
||||
"trigger.toggle.window-gaps": {"icon":"","label":"Window Gaps","action":"blob-hypr-window-gaps-toggle"},
|
||||
"trigger.toggle.one-window-ratio": {"icon":"","label":"1-Window Ratio","action":"blob-hypr-window-single-square-aspect-toggle"},
|
||||
"trigger.tests.network-speedtest": {"icon":"","label":"Network Speed Test","action":"blob-shell shell summon blob.speedtest"},
|
||||
"trigger.tests.disk-speedtest": {"icon":"","label":"Disk Speed Test","action":"blob-shell shell summon blob.disk-speedtest"},
|
||||
|
||||
// Style
|
||||
"style.theme": {"icon":"","label":"Theme","aliases":["theme","themes"],"action":"theme=$(blob-theme-switcher); [[ -n $theme ]] && blob-theme-set \"$theme\""},
|
||||
"style.background": {"icon":"","label":"Background","aliases":["background","wallpaper"],"action":"background=$(blob-bg-switcher); [[ -n $background ]] && blob-bg-set \"$background\""},
|
||||
"style.font": {"icon":"","label":"Font","provider":"fonts"},
|
||||
"style.bar": {"icon":"","label":"Menu Bar"},
|
||||
"style.bar.position": {"icon":"","label":"Position"},
|
||||
"style.bar.transparency": {"icon":"","label":"Transparency","action":"blob-bar transparent toggle"},
|
||||
"style.hyprland": {"icon":"","label":"Hyprland","action":"blob-launch-config-editor \"$HOME/.config/hypr/looknfeel.lua\""},
|
||||
"style.screensaver": {"icon":"","label":"Screensaver"},
|
||||
"style.about": {"icon":"","label":"About"},
|
||||
"style.bar.position.top": {"icon":"","label":"Top","action":"blob-bar position top"},
|
||||
"style.bar.position.bottom": {"icon":"","label":"Bottom","action":"blob-bar position bottom"},
|
||||
"style.bar.position.left": {"icon":"","label":"Left","action":"blob-bar position left"},
|
||||
"style.bar.position.right": {"icon":"","label":"Right","action":"blob-bar position right"},
|
||||
"style.about.text": {"icon":"","label":"Edit Text","action":"blob-branding-about text"},
|
||||
"style.about.image": {"icon":"","label":"Set From Image","action":"blob-branding-about image"},
|
||||
"style.about.default": {"icon":"","label":"Restore Default","action":"blob-branding-about reset"},
|
||||
"style.screensaver.text": {"icon":"","label":"Edit Text","action":"blob-branding-screensaver text"},
|
||||
"style.screensaver.image": {"icon":"","label":"Set From Image","action":"blob-branding-screensaver image"},
|
||||
"style.screensaver.default": {"icon":"","label":"Restore Default","action":"blob-branding-screensaver reset"},
|
||||
|
||||
// Setup
|
||||
"setup.monitors": {"icon":"","label":"Monitors","action":"blob-launch-config-editor \"$HOME/.config/hypr/monitors.lua\""},
|
||||
"setup.keybindings": {"icon":"","label":"Keybindings","when":"[[ -f ~/.config/hypr/bindings.lua ]]","action":"blob-launch-config-editor \"$HOME/.config/hypr/bindings.lua\""},
|
||||
"setup.input": {"icon":"","label":"Input","when":"[[ -f ~/.config/hypr/input.lua ]]","action":"blob-launch-config-editor \"$HOME/.config/hypr/input.lua\""},
|
||||
"setup.network": {"icon":"","label":"Network","aliases":["network"]},
|
||||
"setup.network.dns": {"icon":"","label":"DNS","aliases":["dns"]},
|
||||
"setup.network.dns.dhcp": {"icon":"","label":"DHCP","checked":"[[ \"$(blob-network-dns)\" == \"DHCP\" ]]","action":"blob-network-dns DHCP"},
|
||||
"setup.network.dns.cloudflare": {"icon":"","label":"Cloudflare","checked":"[[ \"$(blob-network-dns)\" == \"Cloudflare\" ]]","action":"blob-network-dns Cloudflare"},
|
||||
"setup.network.dns.google": {"icon":"","label":"Google","checked":"[[ \"$(blob-network-dns)\" == \"Google\" ]]","action":"blob-network-dns Google"},
|
||||
"setup.network.dns.custom": {"icon":"","label":"Custom","checked":"[[ \"$(blob-network-dns)\" == \"Custom\" ]]","action":"blob-launch-floating 'blob-network-dns Custom'"},
|
||||
"setup.network.qr": {"icon":"","label":"QR Code","aliases":["wifi-qr"],"when":"[[ $(blob-network-status) == wifi* ]]","action":"blob-shell shell summon blob.wifiqr"},
|
||||
"setup.default": {"icon":"","label":"Defaults","aliases":["default","defaults"]},
|
||||
"setup.default.browser": {"icon":"","label":"Browser","title":"Default Browser"},
|
||||
"setup.default.browser.chromium": {"icon":"","label":"Chromium","when":"blob-cmd-present chromium","checked":"[[ \"$(blob-default-browser)\" == \"chromium\" ]]","action":"blob-default-browser chromium"},
|
||||
"setup.default.browser.chrome": {"icon":"","label":"Chrome","when":"blob-cmd-present google-chrome-stable","checked":"[[ \"$(blob-default-browser)\" == \"chrome\" ]]","action":"blob-default-browser chrome"},
|
||||
"setup.default.browser.brave": {"icon":"","label":"Brave","when":"blob-cmd-present brave","checked":"[[ \"$(blob-default-browser)\" == \"brave\" ]]","action":"blob-default-browser brave"},
|
||||
"setup.default.browser.brave-origin": {"icon":"","label":"Brave Origin","when":"blob-cmd-present brave-origin","checked":"[[ \"$(blob-default-browser)\" == \"brave-origin\" ]]","action":"blob-default-browser brave-origin"},
|
||||
"setup.default.browser.edge": {"icon":"","label":"Edge","when":"blob-cmd-present microsoft-edge-stable","checked":"[[ \"$(blob-default-browser)\" == \"edge\" ]]","action":"blob-default-browser edge"},
|
||||
"setup.default.browser.firefox": {"icon":"","label":"Firefox","when":"blob-cmd-present firefox","checked":"[[ \"$(blob-default-browser)\" == \"firefox\" ]]","action":"blob-default-browser firefox"},
|
||||
"setup.default.browser.zen": {"icon":"","label":"Zen","when":"blob-cmd-present zen-browser","checked":"[[ \"$(blob-default-browser)\" == \"zen\" ]]","action":"blob-default-browser zen"},
|
||||
"setup.default.terminal": {"icon":"","label":"Terminal","title":"Default Terminal"},
|
||||
"setup.default.terminal.alacritty": {"icon":"","label":"Alacritty","when":"blob-cmd-present alacritty","checked":"[[ \"$(blob-default-terminal)\" == \"alacritty\" ]]","action":"blob-default-terminal alacritty"},
|
||||
"setup.default.terminal.foot": {"icon":"","label":"Foot","when":"blob-cmd-present foot","checked":"[[ \"$(blob-default-terminal)\" == \"foot\" ]]","action":"blob-default-terminal foot"},
|
||||
"setup.default.terminal.ghostty": {"icon":"","label":"Ghostty","when":"blob-cmd-present ghostty","checked":"[[ \"$(blob-default-terminal)\" == \"ghostty\" ]]","action":"blob-default-terminal ghostty"},
|
||||
"setup.default.terminal.kitty": {"icon":"","label":"Kitty","when":"blob-cmd-present kitty","checked":"[[ \"$(blob-default-terminal)\" == \"kitty\" ]]","action":"blob-default-terminal kitty"},
|
||||
"setup.default.editor": {"icon":"","label":"Editor","title":"Default Editor"},
|
||||
"setup.default.editor.neovim": {"icon":"","label":"Neovim","when":"blob-cmd-present nvim","checked":"[[ \"$(blob-default-editor)\" == \"nvim\" ]]","action":"blob-default-editor nvim"},
|
||||
"setup.default.editor.vscode": {"icon":"","label":"VSCode","when":"blob-cmd-present code","checked":"[[ \"$(blob-default-editor)\" == \"code\" ]]","action":"blob-default-editor code"},
|
||||
"setup.default.editor.cursor": {"icon":"","iconFont":"blob","label":"Cursor","when":"blob-cmd-present cursor","checked":"[[ \"$(blob-default-editor)\" == \"cursor\" ]]","action":"blob-default-editor cursor"},
|
||||
"setup.default.editor.zed": {"icon":"","label":"Zed","when":"blob-cmd-present zeditor","checked":"[[ \"$(blob-default-editor)\" == \"zeditor\" ]]","action":"blob-default-editor zed"},
|
||||
"setup.default.editor.sublime": {"icon":"","label":"Sublime Text","when":"blob-cmd-present sublime_text","checked":"[[ \"$(blob-default-editor)\" == \"sublime_text\" ]]","action":"blob-default-editor sublime_text"},
|
||||
"setup.default.editor.helix": {"icon":"","label":"Helix","when":"blob-cmd-present helix","checked":"[[ \"$(blob-default-editor)\" == \"helix\" ]]","action":"blob-default-editor helix"},
|
||||
"setup.default.editor.vim": {"icon":"","label":"Vim","when":"blob-cmd-present vim","checked":"[[ \"$(blob-default-editor)\" == \"vim\" ]]","action":"blob-default-editor vim"},
|
||||
"setup.default.editor.emacs": {"icon":"","label":"Emacs","when":"blob-cmd-present emacs","checked":"[[ \"$(blob-default-editor)\" == \"emacs\" ]]","action":"blob-default-editor emacs"},
|
||||
"setup.plugin": {"icon":"","label":"Plugins","aliases":["plugin","plugins"]},
|
||||
"setup.plugin.enable": {"icon":"","label":"Enable Plugin","action":"blob-menu-plugin enable"},
|
||||
"setup.plugin.disable": {"icon":"","label":"Disable Plugin","action":"blob-menu-plugin disable"},
|
||||
"setup.plugin.add": {"icon":"","label":"Add Plugin","action":"blob-launch-floating 'blob-plugin-add'"},
|
||||
"setup.plugin.clone": {"icon":"","label":"Clone Plugin","action":"blob-menu-plugin clone"},
|
||||
"setup.plugin.remove": {"icon":"","label":"Remove Plugin","when":"compgen -G \"$HOME/.config/blob/plugins/*/manifest.json\"","action":"blob-menu-plugin remove"},
|
||||
"setup.security": {"icon":"","label":"Security"},
|
||||
"setup.config": {"icon":"","label":"Config"},
|
||||
"setup.security.fingerprint": {"icon":"","label":"Fingerprint","when":"blob-hw-fingerprint","action":"blob-launch-floating blob-setup-security-fingerprint"},
|
||||
"setup.security.fido2": {"icon":"","label":"Fido2","action":"blob-launch-floating blob-setup-security-fido2"},
|
||||
"setup.security.sshd": {"icon":"","label":"SSHD","action":"blob-launch-floating blob-setup-security-sshd"},
|
||||
"setup.security.passwordless-sudo": {"icon":"","label":"Passwordless Sudo","action":"blob-launch-floating blob-sudo-passwordless"},
|
||||
"setup.security.sudoless-docker": {"icon":"","label":"Sudoless Docker","when":"blob-sudo-docker --configured","action":"blob-launch-floating blob-setup-security-sudoless-docker"},
|
||||
"setup.config.hyprland": {"icon":"","label":"Hyprland","action":"blob-launch-config-editor \"$HOME/.config/hypr/hyprland.lua\""},
|
||||
"setup.config.hyprsunset": {"icon":"","label":"Hyprsunset","action":"blob-launch-config-editor ~/.config/hypr/hyprsunset.conf && blob-restart-hyprsunset"},
|
||||
"setup.config.xcompose": {"icon":"","label":"XCompose","action":"blob-launch-config-editor ~/.XCompose && blob-restart-xcompose"},
|
||||
"setup.direct-boot": {"icon":"","label":"Direct Boot","action":"blob-launch-floating blob-setup-direct-boot"},
|
||||
|
||||
// Install
|
||||
"install.package": {"icon":"","label":"Package","action":"xdg-terminal-exec --app-id=org.blob.terminal blob-pkg-install"},
|
||||
"install.aur": {"icon":"","label":"AUR","action":"xdg-terminal-exec --app-id=org.blob.terminal blob-pkg-aur-install"},
|
||||
"install.tui": {"icon":"","label":"TUI","action":"blob-launch-floating blob-tui-install"},
|
||||
"install.style": {"icon":"","label":"Style"},
|
||||
"install.style.theme": {"icon":"","label":"Theme","action":"blob-launch-floating blob-theme-install"},
|
||||
"install.style.background": {"icon":"","label":"Background","action":"blob-bg-install"},
|
||||
"install.style.font": {"icon":"","label":"Font"},
|
||||
"install.style.font.cascadia": {"icon":"","label":"Cascadia Mono","action":"blob-install-font 'Cascadia Mono' ttf-cascadia-mono-nerd 'CaskaydiaMono Nerd Font'"},
|
||||
"install.style.font.meslo": {"icon":"","label":"Meslo LG Mono","action":"blob-install-font 'Meslo LG Mono' ttf-meslo-nerd 'MesloLGL Nerd Font'"},
|
||||
"install.style.font.fira": {"icon":"","label":"Fira Code","action":"blob-install-font 'Fira Code' ttf-firacode-nerd 'FiraCode Nerd Font'"},
|
||||
"install.style.font.victor": {"icon":"","label":"Victor Code","action":"blob-install-font 'Victor Code' ttf-victor-mono-nerd 'VictorMono Nerd Font'"},
|
||||
"install.style.font.bitstream": {"icon":"","label":"Bitstream Vera Mono","action":"blob-install-font 'Bitstream Vera Code' ttf-bitstream-vera-mono-nerd 'BitstromWera Nerd Font'"},
|
||||
"install.style.font.iosevka": {"icon":"","label":"Iosevka","action":"blob-install-font Iosevka ttf-iosevka-nerd 'Iosevka Nerd Font Mono'"},
|
||||
|
||||
// Remove
|
||||
"remove.package": {"icon":"","label":"Package","action":"xdg-terminal-exec --app-id=org.blob.terminal blob-pkg-remove"},
|
||||
"remove.tui": {"icon":"","label":"TUI","when":"grep -qE '^Exec=.*(\\$TERMINAL|xdg-terminal-exec).*-e' $HOME/.local/share/applications/*.desktop","action":"blob-tui-remove"},
|
||||
"remove.theme": {"icon":"","label":"Theme","action":"blob-theme-remove"},
|
||||
|
||||
// Update
|
||||
"update.blob": {"icon":"","iconFont":"blob","label":"Blob","action":"blob-launch-floating blob-update"},
|
||||
"update.config": {"icon":"","label":"Config","title":"Reset to default"},
|
||||
"update.themes": {"icon":"","label":"Extra Themes","when":"blob-theme-extras","action":"blob-launch-floating blob-theme-update"},
|
||||
"update.process": {"icon":"","label":"Process","title":"Restart"},
|
||||
"update.hardware": {"icon":"","label":"Hardware","title":"Restart"},
|
||||
"update.firmware": {"icon":"","label":"Firmware","action":"blob-launch-floating blob-update-firmware"},
|
||||
"update.password": {"icon":"","label":"Password"},
|
||||
"update.timezone": {"icon":"","label":"Timezone","action":"blob-menu-timezone"},
|
||||
"update.time": {"icon":"","label":"Time","action":"blob-launch-floating blob-update-time"},
|
||||
"update.process.hyprsunset": {"icon":"","label":"Hyprsunset","action":"blob-restart-hyprsunset"},
|
||||
"update.process.shell": {"icon":"","label":"Shell","action":"blob-shell-restart"},
|
||||
"update.config.hyprland": {"icon":"","label":"Hyprland","action":"blob-launch-floating blob-refresh-hyprland"},
|
||||
"update.config.hyprsunset": {"icon":"","label":"Hyprsunset","action":"blob-launch-floating blob-refresh-hyprsunset"},
|
||||
"update.config.plymouth": {"icon":"","label":"Plymouth","action":"blob-launch-floating blob-refresh-plymouth"},
|
||||
"update.config.shell": {"icon":"","label":"Shell","action":"blob-launch-floating blob-refresh-shell"},
|
||||
"update.hardware.audio": {"icon":"","label":"Audio","action":"blob-launch-floating blob-audio-restart"},
|
||||
"update.hardware.wifi": {"icon":"","label":"Wi-Fi","action":"blob-launch-floating blob-network-restart"},
|
||||
"update.hardware.bluetooth": {"icon":"","label":"Bluetooth","action":"blob-launch-floating blob-bluetooth-restart"},
|
||||
"update.hardware.trackpad": {"icon":"","label":"Trackpad","action":"blob-launch-floating blob-restart-trackpad"},
|
||||
"update.password.drive": {"icon":"","label":"Drive Encryption","action":"blob-launch-floating blob-drive-password"},
|
||||
"update.password.user": {"icon":"","label":"User","action":"blob-launch-floating passwd"},
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[main]
|
||||
font=JetBrainsMono Nerd Font:size=18
|
||||
pad=0x0
|
||||
|
||||
[colors-dark]
|
||||
background=000000
|
||||
foreground=ffffff
|
||||
@@ -0,0 +1,5 @@
|
||||
-- App-specific tweaks.
|
||||
local paths = require("default.hypr.paths")
|
||||
local require_all = require("default.hypr.require_all")
|
||||
|
||||
require_all.files(paths.blob_path .. "/default/hypr/apps", "default.hypr.apps")
|
||||
@@ -0,0 +1 @@
|
||||
o.window("^(1[p|P]assword)$", { no_screen_share = true, tag = "+floating-window" })
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Battle.net launches under Proton; all its windows share class steam_app_battlenet.
|
||||
|
||||
-- The actual launcher: float-centered.
|
||||
o.window({ class = "^steam_app_battlenet$", title = "^Battle\\.net$" }, {
|
||||
float = true,
|
||||
center = true,
|
||||
size = { 1280, 800 },
|
||||
})
|
||||
|
||||
-- Installer: drop decorations and backdrop blur/shadow so the Blizzard chrome
|
||||
-- isn't framed by the WM.
|
||||
o.window({ class = "^steam_app_battlenet$", title = "^Battle\\.net Setup$" }, {
|
||||
decorate = false,
|
||||
no_blur = true,
|
||||
no_shadow = true,
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
o.window("^(Bitwarden)$", { no_screen_share = true, tag = "+floating-window" })
|
||||
|
||||
o.window("chrome-nngceckbapebfimnlniiiahkandclblb-Default", {
|
||||
no_screen_share = true,
|
||||
tag = "+floating-window",
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Browser tags and styling.
|
||||
o.window("((google-)?[cC]hrom(e|ium)|[bB]rave-browser|[mM]icrosoft-edge|Vivaldi-stable|helium)", { tag = "+chromium-based-browser" })
|
||||
o.window("([fF]irefox|zen|librewolf)", { tag = "+firefox-based-browser" })
|
||||
o.window({ tag = "chromium-based-browser" }, { tag = "-default-opacity", tile = true, opacity = "1.0 0.985" })
|
||||
o.window({ tag = "firefox-based-browser" }, { tag = "-default-opacity", opacity = "1.0 0.985" })
|
||||
|
||||
-- Video apps: remove chromium browser tag so they don't get opacity applied.
|
||||
o.window("(^.+-youtube\\.com__.*$|^.+-app\\.zoom\\.us__wc_home.*$)", { tag = "-chromium-based-browser" })
|
||||
o.window("(^.+-youtube\\.com__.*$|^.+-app\\.zoom\\.us__wc_home.*$)", { tag = "-default-opacity" })
|
||||
|
||||
-- Hide screen sharing notification windows.
|
||||
o.window({ title = ".*is sharing.*" }, { workspace = "special silent" })
|
||||
@@ -0,0 +1,11 @@
|
||||
-- DaVinci Resolve window focus handling. Kept fully opaque: the default
|
||||
-- translucency distorts colour-critical grading work.
|
||||
o.window(".*[Rr]esolve.*", {
|
||||
float = true,
|
||||
stay_focused = true,
|
||||
tag = "-default-opacity",
|
||||
opacity = "1 1",
|
||||
})
|
||||
|
||||
o.window({ class = ".*[Rr]esolve.*", title = "^DaVinci Resolve( Studio)? - .+$" }, { fullscreen = true })
|
||||
o.window({ class = ".*[Rr]esolve.*", title = "^(DaVinci Resolve( Studio)? - .+|Project Manager)$" }, { stay_focused = false })
|
||||
@@ -0,0 +1 @@
|
||||
o.window("GeForceNOW", { idle_inhibit = "fullscreen" })
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Hermes Desktop's frameless HUD manages its own geometry.
|
||||
o.window({ class = "^Hermes$", title = "^Hermes HUD$" }, {
|
||||
tag = "-default-opacity",
|
||||
float = true,
|
||||
border_size = 0,
|
||||
opacity = "1 1",
|
||||
})
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Disable mouse focus (see https://github.com/basecamp/omarchy/pull/5183#issuecomment-4189299971).
|
||||
o.window("^(jetbrains-.*)$", { no_follow_mouse = true })
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Float LocalSend and fzf file picker.
|
||||
o.window("(Share|localsend)", { float = true, center = true })
|
||||
o.window("localsend", { size = { 1100, 700 } })
|
||||
@@ -0,0 +1 @@
|
||||
o.window("com.moonlight_stream.Moonlight", { fullscreen = true, idle_inhibit = "fullscreen" })
|
||||
@@ -0,0 +1,14 @@
|
||||
-- Window and layer rules for the Blob Quickshell surfaces. The
|
||||
-- shell-wide bar / menu / popouts are layer-shell.
|
||||
|
||||
-- Keep the bar instant: no layer-shell fade/slide animation.
|
||||
hl.layer_rule({ match = { namespace = "blob-bar" }, no_anim = true, animation = "none" })
|
||||
|
||||
-- Launcher, image selector, emojis, clipboard overlays, and keyboard-driven
|
||||
-- panels should pop without compositor layer fades. Panels keep their own
|
||||
-- QML opacity transition for normal open/close, and skip it for panel handoff.
|
||||
hl.layer_rule({ match = { namespace = "^(blob-menu|blob-image-selector|blob-emojis|blob-clipboard|blob-keyboard-panel)$" }, no_anim = true, animation = "none" })
|
||||
|
||||
-- Dev gallery is the main shell workbench; open it maximized like
|
||||
-- SUPER+ALT+F so component previews have the whole workspace.
|
||||
o.window({ class = "^org.quickshell$", title = "^Blob shell – dev gallery$" }, { maximize = true })
|
||||
@@ -0,0 +1,24 @@
|
||||
-- Picture-in-picture overlays.
|
||||
o.window({ title = "(Picture.?in.?[Pp]icture)" }, { tag = "+pip" })
|
||||
o.window({ tag = "pip" }, {
|
||||
tag = "-default-opacity",
|
||||
float = true,
|
||||
pin = true,
|
||||
size = { 600, 338 },
|
||||
keep_aspect_ratio = true,
|
||||
border_size = 0,
|
||||
opacity = "1 1",
|
||||
move = { "(monitor_w-window_w-40)", "(monitor_h*0.04)" },
|
||||
})
|
||||
|
||||
-- Google Meet PiP uses the meeting title instead of "Picture-in-Picture".
|
||||
o.window({ tag = "chromium-based-browser", title = "^Meet - .+" }, {
|
||||
tag = "-default-opacity",
|
||||
float = true,
|
||||
pin = true,
|
||||
size = { 600, 338 },
|
||||
keep_aspect_ratio = true,
|
||||
border_size = 0,
|
||||
opacity = "1 1",
|
||||
move = { "(monitor_w-window_w-40)", "(monitor_h-window_h-40)" },
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
o.window("qemu", { tag = "-default-opacity", opacity = "1 1" })
|
||||
@@ -0,0 +1,6 @@
|
||||
o.window("com.libretro.RetroArch", {
|
||||
fullscreen = true,
|
||||
tag = "-default-opacity",
|
||||
opacity = "1 1",
|
||||
idle_inhibit = "fullscreen",
|
||||
})
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Remove the 1px border around the slurp region selection used by screenshots.
|
||||
hl.layer_rule({ match = { namespace = "selection" }, no_anim = true, animation = "none" })
|
||||
@@ -0,0 +1,4 @@
|
||||
o.window("steam", { float = true, idle_inhibit = "fullscreen" })
|
||||
o.window({ class = "steam", title = "Steam" }, { center = true, size = { 1100, 700 } })
|
||||
o.window("steam.*", { tag = "-default-opacity", opacity = "1 1" })
|
||||
o.window({ class = "steam", title = "Friends List" }, { size = { 460, 800 } })
|
||||
@@ -0,0 +1,54 @@
|
||||
-- Floating windows.
|
||||
o.window({ tag = "floating-window" }, { float = true })
|
||||
o.window({ tag = "floating-window" }, { center = true })
|
||||
o.window({ tag = "floating-window" }, { size = { 875, 600 } })
|
||||
|
||||
o.window(
|
||||
"(org.blob.btop|org.blob.terminal|org.blob.bash|org.codeberg.dnkl.foot|org.gnome.NautilusPreviewer|org.gnome.Evince|Blob|About|TUI.float|imv|mpv)",
|
||||
{
|
||||
tag = "+floating-window",
|
||||
}
|
||||
)
|
||||
|
||||
-- The portal only ever shows dialogs — file pickers, screen shares, permission
|
||||
-- prompts — so every one of its windows belongs in the floating treatment,
|
||||
-- whatever the app that asked for it titled it.
|
||||
o.window("xdg-desktop-portal-gtk", { tag = "+floating-window" })
|
||||
o.window({
|
||||
class = "(sublime_text|DesktopEditors|org.gnome.Nautilus)",
|
||||
title = "^(Open.*Files?|Open [F|f]older.*|Save.*Files?|Save.*As|Save|All Files|.*wants to [open|save].*|[C|c]hoose.*)",
|
||||
}, { tag = "+floating-window" })
|
||||
|
||||
-- The About fastfetch layout needs more columns than the standard float provides.
|
||||
-- This size only covers the first launch: blob-launch-about measures the
|
||||
-- rendered content, remembers the size that hugs it, and applies that as its own
|
||||
-- rule before every later launch.
|
||||
o.window("org.blob.about", { float = true })
|
||||
o.window("org.blob.about", { center = true })
|
||||
o.window("org.blob.about", { size = { 920, 480 } })
|
||||
|
||||
|
||||
-- Fullscreen screensaver.
|
||||
o.window("org.blob.screensaver", { fullscreen = true })
|
||||
o.window("org.blob.screensaver", { float = true })
|
||||
o.window("org.blob.screensaver", { animation = "slide" })
|
||||
|
||||
-- No transparency on media windows.
|
||||
o.window(
|
||||
"^(zoom|vlc|mpv|org.kde.kdenlive|com.obsproject.Studio|com.github.PintaProject.Pinta|imv|org.gnome.NautilusPreviewer)$",
|
||||
{
|
||||
tag = "-default-opacity",
|
||||
}
|
||||
)
|
||||
o.window(
|
||||
"^(zoom|vlc|mpv|org.kde.kdenlive|com.obsproject.Studio|com.github.PintaProject.Pinta|imv|org.gnome.NautilusPreviewer)$",
|
||||
{
|
||||
opacity = "1 1",
|
||||
}
|
||||
)
|
||||
|
||||
-- Popped window rounding.
|
||||
o.window({ tag = "pop" }, { rounding = 8 })
|
||||
|
||||
-- Prevent idle while open.
|
||||
o.window({ tag = "noidle" }, { idle_inhibit = "always" })
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Prevent Telegram from stealing focus on new messages.
|
||||
o.window("org.telegram.desktop", { focus_on_activate = false })
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Define terminal tag so themes and bindings can single terminals out. Blob
|
||||
-- launches TUIs and its own terminal windows under dedicated app-ids
|
||||
-- (org.blob.btop, org.blob.terminal, TUI.float, ...), so match those too.
|
||||
-- The class is matched in full, so foot's other app-id needs spelling out.
|
||||
o.window(
|
||||
"(Alacritty|kitty|com.mitchellh.ghostty|foot|org\\.codeberg\\.dnkl\\.foot|wezterm|org\\.blob\\..*|TUI\\..*)",
|
||||
{ tag = "+terminal" }
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
-- The 8:9 portrait presets scale from monitor height so they occupy the same
|
||||
-- share of anything from a 1080p monitor to a scaled 6K display.
|
||||
o.window("^WebcamOverlay-small$", {
|
||||
size = { "(monitor_h*4/25)", "(monitor_h*9/50)" },
|
||||
move = { "(monitor_w-monitor_h*4/25-40)", "(monitor_h-monitor_h*9/50-40)" },
|
||||
})
|
||||
o.window("^WebcamOverlay-medium$", {
|
||||
size = { "(monitor_h*2/9)", "(monitor_h/4)" },
|
||||
move = { "(monitor_w-monitor_h*2/9-40)", "(monitor_h-monitor_h/4-40)" },
|
||||
})
|
||||
o.window("^WebcamOverlay-large$", {
|
||||
size = { "(monitor_h*3/10)", "(monitor_h*27/80)" },
|
||||
move = { "(monitor_w-monitor_h*3/10-40)", "(monitor_h-monitor_h*27/80-40)" },
|
||||
})
|
||||
|
||||
-- The dedicated app id keeps this out of mpv's generic centered floating rules,
|
||||
-- so the camera appears at its final corner position.
|
||||
o.window({ class = "^WebcamOverlay-(small|medium|large)$", title = "^WebcamOverlay$" }, {
|
||||
tag = "-default-opacity",
|
||||
float = true,
|
||||
pin = true,
|
||||
no_initial_focus = true,
|
||||
no_dim = true,
|
||||
opacity = "1 1",
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
hl.on("hyprland.start", function()
|
||||
-- Slow app launch fix -- set systemd vars before starting session services.
|
||||
hl.exec_cmd("systemctl --user import-environment $(env | cut -d'=' -f 1)")
|
||||
hl.exec_cmd("dbus-update-activation-environment --systemd --all")
|
||||
|
||||
hl.exec_cmd("blob-launch-shell")
|
||||
hl.exec_cmd("blob-powerprofiles-init")
|
||||
hl.exec_cmd(o.launch("blob-hypr-monitor-watch"))
|
||||
hl.exec_cmd(o.launch("udiskie --automount --no-notify --no-tray"))
|
||||
|
||||
-- Run post-boot hooks after startup config has loaded.
|
||||
hl.exec_cmd("sleep 2 && blob-hook post-boot")
|
||||
end)
|
||||
@@ -0,0 +1,4 @@
|
||||
local paths = require("default.hypr.paths")
|
||||
local require_all = require("default.hypr.require_all")
|
||||
|
||||
require_all.files(paths.blob_path .. "/default/hypr/bindings", "default.hypr.bindings")
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Essential application bindings.
|
||||
o.bind("SUPER + RETURN", "Terminal", { blob = "terminal" })
|
||||
o.bind("SUPER + SHIFT + RETURN", "Browser", { blob = "browser" })
|
||||
o.bind("SUPER + SHIFT + F", "File manager", { blob = "nautilus" })
|
||||
o.bind("SUPER + ALT + SHIFT + F", "File manager (cwd)", { blob = "nautilus-cwd" })
|
||||
o.bind("SUPER + SHIFT + B", "Browser", { blob = "browser" })
|
||||
o.bind("SUPER + SHIFT + ALT + B", "Browser (private)", { blob = "browser --private" })
|
||||
o.bind("SUPER + SHIFT + N", "Editor", { blob = "editor" })
|
||||
|
||||
if o.preinstalled_bindings_enabled() then
|
||||
o.bind("SUPER + SHIFT + M", "Music", { blob = "spotify" })
|
||||
o.bind("SUPER + SHIFT + D", "Docker", { tui = "blob-launch-docker-tui" })
|
||||
o.bind("SUPER + SHIFT + G", "Signal", { blob = "signal" })
|
||||
o.bind("SUPER + SHIFT + O", "Obsidian", { launch = "obsidian", focus = "^obsidian$" })
|
||||
end
|
||||
@@ -0,0 +1,48 @@
|
||||
-- Send with explicit mods to the focused surface by omitting the window target,
|
||||
-- so universal clipboard shortcuts reach both normal windows and focused
|
||||
-- layer-shell surfaces such as Blob panels. A virtual keyboard (wtype) won't
|
||||
-- do: the physically held SUPER merges into the injected chord at the seat.
|
||||
-- The down/up split works around Hyprland send_shortcut sometimes leaving
|
||||
-- synthetic key state stuck/repeating.
|
||||
-- https://github.com/hyprwm/Hyprland/discussions/14099
|
||||
local function send_shortcut_once(mods, key)
|
||||
return function()
|
||||
hl.dispatch(hl.dsp.send_key_state({ mods = mods, key = key, state = "down" }))
|
||||
|
||||
hl.timer(function()
|
||||
hl.dispatch(hl.dsp.send_key_state({ mods = mods, key = key, state = "up" }))
|
||||
end, { timeout = 50, type = "oneshot" })
|
||||
end
|
||||
end
|
||||
|
||||
-- Lean on the terminal tag from default/hypr/apps/terminals.lua so there's one
|
||||
-- definition of what counts as a terminal. Dynamic tags carry a trailing "*".
|
||||
local function active_window_is_terminal()
|
||||
local window = hl.get_active_window()
|
||||
if not window then
|
||||
return false
|
||||
end
|
||||
|
||||
for _, tag in ipairs(window.tags or {}) do
|
||||
if tag:gsub("%*$", "") == "terminal" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local function universal_clipboard_shortcut(default_mods, default_key, terminal_mods, terminal_key)
|
||||
return function()
|
||||
if active_window_is_terminal() then
|
||||
send_shortcut_once(terminal_mods, terminal_key)()
|
||||
else
|
||||
send_shortcut_once(default_mods, default_key)()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
o.bind("SUPER + C", "Universal copy", universal_clipboard_shortcut("CTRL", "C", "CTRL", "Insert"))
|
||||
o.bind("SUPER + V", "Universal paste", universal_clipboard_shortcut("CTRL", "V", "SHIFT", "Insert"))
|
||||
o.bind("SUPER + X", "Universal cut", send_shortcut_once("CTRL", "X"))
|
||||
o.bind("SUPER + CTRL + V", "Clipboard manager", "blob-shell shell toggle blob.clipboard")
|
||||
@@ -0,0 +1,34 @@
|
||||
-- Volume, brightness, keyboard backlight, and touchpad controls.
|
||||
o.bind("XF86AudioRaiseVolume", "Volume up", "blob-audio-volume raise", { locked = true, repeating = true })
|
||||
o.bind("XF86AudioLowerVolume", "Volume down", "blob-audio-volume lower", { locked = true, repeating = true })
|
||||
o.bind("XF86AudioMute", "Mute", "blob-audio-volume mute-toggle", { locked = true })
|
||||
o.bind("XF86AudioMicMute", "Mute microphone", "blob-audio-input-mute", { locked = true })
|
||||
o.bind("XF86MonBrightnessUp", "Brightness up", "blob-brightness-display +5%", { locked = true, repeating = true })
|
||||
o.bind("XF86MonBrightnessDown", "Brightness down", "blob-brightness-display 5%-", { locked = true, repeating = true })
|
||||
o.bind("SHIFT + XF86MonBrightnessUp", "Brightness maximum", "blob-brightness-display 100%", { locked = true, repeating = true })
|
||||
o.bind("SHIFT + XF86MonBrightnessDown", "Brightness minimum", "blob-brightness-display 1%", { locked = true, repeating = true })
|
||||
o.bind("XF86KbdBrightnessUp", "Keyboard brightness up", "blob-brightness-keyboard up", { locked = true, repeating = true })
|
||||
o.bind("XF86KbdBrightnessDown", "Keyboard brightness down", "blob-brightness-keyboard down", { locked = true, repeating = true })
|
||||
o.bind("XF86KbdLightOnOff", "Keyboard backlight cycle", "blob-brightness-keyboard cycle", { locked = true })
|
||||
o.bind_toggle("XF86TouchpadToggle", "Toggle touchpad", "touchpad", { locked = true })
|
||||
o.bind("XF86TouchpadOn", "Enable touchpad", "blob-toggle-touchpad on", { locked = true })
|
||||
o.bind("XF86TouchpadOff", "Disable touchpad", "blob-toggle-touchpad off", { locked = true })
|
||||
|
||||
-- Precise volume and brightness controls.
|
||||
o.bind("ALT + XF86AudioRaiseVolume", "Volume up precise", "blob-audio-volume +1", { locked = true, repeating = true })
|
||||
o.bind("ALT + XF86AudioLowerVolume", "Volume down precise", "blob-audio-volume -1", { locked = true, repeating = true })
|
||||
o.bind("ALT + XF86MonBrightnessUp", "Brightness up precise", "blob-brightness-display +1%", { locked = true, repeating = true })
|
||||
o.bind("ALT + XF86MonBrightnessDown", "Brightness down precise", "blob-brightness-display 1%-", { locked = true, repeating = true })
|
||||
|
||||
-- Media controls.
|
||||
o.bind("XF86AudioNext", "Next track", "blob-shell media next", { locked = true })
|
||||
o.bind("ALT + XF86AudioPlay", "Next track", "blob-shell media next", { locked = true })
|
||||
o.bind("XF86AudioPause", "Pause", "blob-shell media playPause", { locked = true })
|
||||
o.bind("XF86AudioPlay", "Play", "blob-shell media playPause", { locked = true })
|
||||
o.bind("XF86AudioPrev", "Previous track", "blob-shell media previous", { locked = true })
|
||||
o.bind("ALT + SHIFT + XF86AudioPlay", "Previous track", "blob-shell media previous", { locked = true })
|
||||
o.bind("XF86Eject", "Eject media", "eject", { locked = true })
|
||||
|
||||
o.bind("SHIFT + XF86AudioMute", "Switch audio output", "blob-audio-sink-switch", { locked = true })
|
||||
o.bind("SHIFT + XF86AudioPause", "Switch media source", "blob-audio-source-switch", { locked = true })
|
||||
o.bind("SHIFT + XF86AudioPlay", "Switch media source", "blob-audio-source-switch", { locked = true })
|
||||
@@ -0,0 +1,95 @@
|
||||
o.bind("SUPER + W", "Close window", hl.dsp.window.close())
|
||||
o.bind("CTRL + ALT + DELETE", "Close all windows", "blob-hypr-window-close-all")
|
||||
|
||||
o.bind("SUPER + J", "Toggle window split", hl.dsp.layout("togglesplit"))
|
||||
o.bind("SUPER + P", "Pseudo window", hl.dsp.window.pseudo())
|
||||
o.bind("SUPER + T", "Toggle window floating/tiling", hl.dsp.window.float({ action = "toggle" }))
|
||||
o.bind("SUPER + F", "Full screen", hl.dsp.window.fullscreen({ mode = "fullscreen" }))
|
||||
o.bind("SUPER + CTRL + F", "Tiled full screen", "blob-hypr-window-tiled-fullscreen-toggle")
|
||||
o.bind("SUPER + ALT + F", "Full width", hl.dsp.window.fullscreen({ mode = "maximized" }))
|
||||
o.bind("SUPER + O", "Pop window out (float & pin)", "blob-hypr-window-pop")
|
||||
o.bind("SUPER + ALT + Home", "Save window width", "blob-hypr-window-width save")
|
||||
o.bind("SUPER + Home", "Restore window width", "blob-hypr-window-width restore")
|
||||
o.bind("SUPER + L", "Toggle workspace layout", "blob-hypr-workspace-layout-toggle")
|
||||
|
||||
o.bind("SUPER + LEFT", "Focus on left window", hl.dsp.focus({ direction = "l" }))
|
||||
o.bind("SUPER + RIGHT", "Focus on right window", hl.dsp.focus({ direction = "r" }))
|
||||
o.bind("SUPER + UP", "Focus on above window", hl.dsp.focus({ direction = "u" }))
|
||||
o.bind("SUPER + DOWN", "Focus on below window", hl.dsp.focus({ direction = "d" }))
|
||||
|
||||
for workspace = 1, 10 do
|
||||
local key = "code:" .. tostring(workspace + 9)
|
||||
o.bind("SUPER + " .. key, "Switch to workspace " .. workspace, hl.dsp.focus({ workspace = tostring(workspace) }))
|
||||
o.bind("SUPER + SHIFT + " .. key, "Move window to workspace " .. workspace, hl.dsp.window.move({ workspace = tostring(workspace) }))
|
||||
o.bind("SUPER + SHIFT + ALT + " .. key, "Move window silently to workspace " .. workspace, hl.dsp.window.move({ workspace = tostring(workspace), follow = false }))
|
||||
end
|
||||
|
||||
o.bind("SUPER + S", "Toggle scratchpad", hl.dsp.workspace.toggle_special("scratchpad"))
|
||||
o.bind("SUPER + ALT + S", "Move window to scratchpad", hl.dsp.window.move({ workspace = "special:scratchpad", follow = false }))
|
||||
|
||||
o.bind("SUPER + TAB", "Next workspace", hl.dsp.focus({ workspace = "e+1" }))
|
||||
o.bind("SUPER + SHIFT + TAB", "Previous workspace", hl.dsp.focus({ workspace = "e-1" }))
|
||||
o.bind("SUPER + CTRL + TAB", "Former workspace", hl.dsp.focus({ workspace = "previous" }))
|
||||
|
||||
o.bind("SUPER + SHIFT + ALT + LEFT", "Move workspace to left monitor", hl.dsp.workspace.move({ monitor = "l" }))
|
||||
o.bind("SUPER + SHIFT + ALT + RIGHT", "Move workspace to right monitor", hl.dsp.workspace.move({ monitor = "r" }))
|
||||
o.bind("SUPER + SHIFT + ALT + UP", "Move workspace to up monitor", hl.dsp.workspace.move({ monitor = "u" }))
|
||||
o.bind("SUPER + SHIFT + ALT + DOWN", "Move workspace to down monitor", hl.dsp.workspace.move({ monitor = "d" }))
|
||||
|
||||
o.bind("SUPER + SHIFT + LEFT", "Swap window to the left", hl.dsp.window.swap({ direction = "l" }))
|
||||
o.bind("SUPER + SHIFT + RIGHT", "Swap window to the right", hl.dsp.window.swap({ direction = "r" }))
|
||||
o.bind("SUPER + SHIFT + UP", "Swap window up", hl.dsp.window.swap({ direction = "u" }))
|
||||
o.bind("SUPER + SHIFT + DOWN", "Swap window down", hl.dsp.window.swap({ direction = "d" }))
|
||||
|
||||
o.bind("ALT + TAB", "Focus on next window", hl.dsp.window.cycle_next())
|
||||
o.bind("ALT + SHIFT + TAB", "Focus on previous window", hl.dsp.window.cycle_next({ next = false }))
|
||||
o.bind("ALT + TAB", "Reveal active window on top", hl.dsp.window.bring_to_top())
|
||||
o.bind("ALT + SHIFT + TAB", "Reveal active window on top", hl.dsp.window.bring_to_top())
|
||||
|
||||
o.bind("CTRL + ALT + TAB", "Focus on next monitor", hl.dsp.focus({ monitor = "+1" }))
|
||||
o.bind("CTRL + ALT + SHIFT + TAB", "Focus on previous monitor", hl.dsp.focus({ monitor = "-1" }))
|
||||
|
||||
o.bind("SUPER + code:20", "Expand window left", hl.dsp.window.resize({ x = -100, y = 0, relative = true }))
|
||||
o.bind("SUPER + code:21", "Shrink window left", hl.dsp.window.resize({ x = 100, y = 0, relative = true }))
|
||||
o.bind("SUPER + SHIFT + code:20", "Shrink window up", hl.dsp.window.resize({ x = 0, y = -100, relative = true }))
|
||||
o.bind("SUPER + SHIFT + code:21", "Expand window down", hl.dsp.window.resize({ x = 0, y = 100, relative = true }))
|
||||
|
||||
o.bind("SUPER + ALT + code:20", "Expand window left a little", hl.dsp.window.resize({ x = -25, y = 0, relative = true }))
|
||||
o.bind("SUPER + ALT + code:21", "Shrink window left a little", hl.dsp.window.resize({ x = 25, y = 0, relative = true }))
|
||||
o.bind("SUPER + SHIFT + ALT + code:20", "Shrink window up a little", hl.dsp.window.resize({ x = 0, y = -25, relative = true }))
|
||||
o.bind("SUPER + SHIFT + ALT + code:21", "Expand window down a little", hl.dsp.window.resize({ x = 0, y = 25, relative = true }))
|
||||
|
||||
o.bind("SUPER + CTRL + code:20", "Expand window left a lot", hl.dsp.window.resize({ x = -300, y = 0, relative = true }))
|
||||
o.bind("SUPER + CTRL + code:21", "Shrink window left a lot", hl.dsp.window.resize({ x = 300, y = 0, relative = true }))
|
||||
o.bind("SUPER + CTRL + SHIFT + code:20", "Shrink window up a lot", hl.dsp.window.resize({ x = 0, y = -300, relative = true }))
|
||||
o.bind("SUPER + CTRL + SHIFT + code:21", "Expand window down a lot", hl.dsp.window.resize({ x = 0, y = 300, relative = true }))
|
||||
|
||||
o.bind("SUPER + mouse_down", "Scroll active workspace forward", hl.dsp.focus({ workspace = "e+1" }))
|
||||
o.bind("SUPER + mouse_up", "Scroll active workspace backward", hl.dsp.focus({ workspace = "e-1" }))
|
||||
|
||||
o.bind("SUPER + mouse:272", "Move window", hl.dsp.window.drag(), { mouse = true })
|
||||
o.bind("SUPER + mouse:273", "Resize window", hl.dsp.window.resize(), { mouse = true })
|
||||
|
||||
o.bind("SUPER + G", "Toggle window grouping", hl.dsp.group.toggle())
|
||||
o.bind("SUPER + ALT + G", "Move active window out of group", hl.dsp.window.move({ out_of_group = true }))
|
||||
|
||||
o.bind("SUPER + ALT + LEFT", "Move window to group on left", hl.dsp.window.move({ into_group = "l" }))
|
||||
o.bind("SUPER + ALT + RIGHT", "Move window to group on right", hl.dsp.window.move({ into_group = "r" }))
|
||||
o.bind("SUPER + ALT + UP", "Move window to group on top", hl.dsp.window.move({ into_group = "u" }))
|
||||
o.bind("SUPER + ALT + DOWN", "Move window to group on bottom", hl.dsp.window.move({ into_group = "d" }))
|
||||
|
||||
o.bind("SUPER + ALT + TAB", "Next window in group", hl.dsp.group.next())
|
||||
o.bind("SUPER + ALT + SHIFT + TAB", "Previous window in group", hl.dsp.group.prev())
|
||||
|
||||
o.bind("SUPER + CTRL + LEFT", "Move grouped window focus left", hl.dsp.group.prev())
|
||||
o.bind("SUPER + CTRL + RIGHT", "Move grouped window focus right", hl.dsp.group.next())
|
||||
|
||||
o.bind("SUPER + ALT + mouse_down", "Next window in group", hl.dsp.group.next())
|
||||
o.bind("SUPER + ALT + mouse_up", "Previous window in group", hl.dsp.group.prev())
|
||||
|
||||
for index = 1, 5 do
|
||||
o.bind("SUPER + ALT + code:" .. tostring(index + 9), "Switch to group window " .. index, hl.dsp.group.active({ index = index }))
|
||||
end
|
||||
|
||||
o.bind("SUPER + SLASH", "Monitor scaling up", "blob-hypr-monitor-scaling up")
|
||||
o.bind("SUPER + ALT + SLASH", "Monitor scaling down", "blob-hypr-monitor-scaling down")
|
||||
@@ -0,0 +1,121 @@
|
||||
o.bind("SUPER + SPACE", "Blob menu", "blob-menu toggle")
|
||||
o.bind("SUPER + ALT + SPACE", "Apps menu", "blob-menu toggle apps")
|
||||
o.bind("SUPER + CTRL + E", "Emojis", "blob-shell shell toggle blob.emojis")
|
||||
o.bind("SUPER + CTRL + C", "Capture menu", "blob-menu toggle capture")
|
||||
o.bind("SUPER + CTRL + O", "Toggle menu", "blob-menu toggle toggle")
|
||||
o.bind("SUPER + CTRL + H", "Hardware menu", "blob-menu toggle hardware")
|
||||
o.bind("SUPER + SHIFT + code:201", "Blob menu", "blob-menu toggle root")
|
||||
o.bind("SUPER + ESCAPE", "System menu", "blob-menu toggle system")
|
||||
o.bind("XF86PowerOff", "Power menu", "blob-menu toggle system", { locked = true })
|
||||
o.bind("SUPER + K", "Keybindings", "blob-menu-keybindings")
|
||||
|
||||
o.bind_toggle("SUPER + SHIFT + SPACE", "Toggle top bar", "bar")
|
||||
o.bind("SUPER + CTRL + SPACE", "Background switcher", "blob-menu toggle background")
|
||||
o.bind("SUPER + SHIFT + CTRL + SPACE", "Theme menu", "blob-menu toggle theme")
|
||||
o.bind("SUPER + BACKSPACE", "Toggle window transparency", "blob-hypr-window-transparency-toggle")
|
||||
o.bind("SUPER + SHIFT + BACKSPACE", "Toggle window gaps", "blob-hypr-window-gaps-toggle")
|
||||
o.bind("SUPER + CTRL + BACKSPACE", "Toggle single-window square aspect", "blob-hypr-window-single-square-aspect-toggle")
|
||||
|
||||
-- xkbcommon names the comma keysym "comma"; the upper-case "COMMA" does not match.
|
||||
o.bind("SUPER + comma", "Dismiss last notification", "blob-shell notifications dismissOne")
|
||||
o.bind("SUPER + SHIFT + comma", "Dismiss all notifications", "blob-shell notifications dismissAll")
|
||||
o.bind_toggle("SUPER + CTRL + comma", "Toggle silencing notifications", "notification-silencing")
|
||||
o.bind("SUPER + ALT + comma", "Invoke last notification", "blob-shell notifications invokeLast")
|
||||
o.bind("SUPER + SHIFT + ALT + comma", "Open notification history", "blob-shell notifications showHistory")
|
||||
|
||||
o.bind_toggle("SUPER + CTRL + I", "Toggle locking on idle", "idle")
|
||||
o.bind_toggle("SUPER + CTRL + N", "Toggle nightlight", "nightlight")
|
||||
o.bind("SUPER + CTRL + Delete", "Toggle laptop display", "blob-hypr-monitor-internal toggle")
|
||||
o.bind("SUPER + CTRL + ALT + Delete", "Toggle laptop display mirroring", "blob-hypr-monitor-mirror toggle")
|
||||
o.bind("switch:on:Lid Switch", nil, "blob-system-lid", { locked = true })
|
||||
o.bind("switch:off:Lid Switch", nil, "blob-hypr-monitor-clamshell", { locked = true })
|
||||
|
||||
o.bind("PRINT", "Screenshot", "blob-capture-screenshot")
|
||||
o.bind("ALT + PRINT", "Screenrecording", "blob-capture-record --stop-recording || blob-menu toggle trigger.capture.screenrecord")
|
||||
o.bind("SUPER + ALT + code:34", "Make webcam overlay smaller", "blob-capture-webcam-resize smaller")
|
||||
o.bind("SUPER + ALT + code:35", "Make webcam overlay larger", "blob-capture-webcam-resize larger")
|
||||
o.bind("SUPER + PRINT", "Color picker", "pkill hyprpicker || hyprpicker -a")
|
||||
o.bind("SUPER + CTRL + PRINT", "Extract text (OCR) from screenshot", "blob-capture-text")
|
||||
|
||||
-- Keyboard control for the slurp region picker (see blob-capture-region).
|
||||
-- The binds live exactly as long as a selection layer is on screen (slurp
|
||||
-- opens one per monitor), so they cannot leak or get stuck.
|
||||
-- Unbinding by key would take a same-key binding out of the user's own config
|
||||
-- with it, so each handle is kept and removed individually.
|
||||
local selection_layers = 0
|
||||
local selection_binds = {}
|
||||
|
||||
hl.on("layer.opened", function(layer)
|
||||
if layer.namespace == "selection" then
|
||||
selection_layers = selection_layers + 1
|
||||
if selection_layers == 1 then
|
||||
selection_binds = {
|
||||
hl.bind("RETURN", hl.dsp.exec_cmd("blob-capture-region --take-window"), { description = "Capture highlighted window" }),
|
||||
hl.bind("CTRL + RETURN", hl.dsp.exec_cmd("blob-capture-region --take-fullscreen"), { description = "Capture entire screen" }),
|
||||
hl.bind("TAB", hl.dsp.exec_cmd("blob-capture-region --select-window next"), { description = "Select next window to capture" }),
|
||||
hl.bind("CTRL + TAB", hl.dsp.exec_cmd("blob-capture-region --select-window prev"), { description = "Select previous window to capture" }),
|
||||
}
|
||||
for _, direction in ipairs({ "left", "right", "up", "down" }) do
|
||||
table.insert(
|
||||
selection_binds,
|
||||
hl.bind(direction:upper(), hl.dsp.exec_cmd("blob-capture-region --select-window " .. direction), { description = "Select window to capture" })
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
hl.on("layer.closed", function(layer)
|
||||
if layer.namespace == "selection" and selection_layers > 0 then
|
||||
selection_layers = selection_layers - 1
|
||||
if selection_layers == 0 then
|
||||
for _, keybind in ipairs(selection_binds) do
|
||||
keybind:unbind()
|
||||
end
|
||||
selection_binds = {}
|
||||
end
|
||||
end
|
||||
end)
|
||||
|
||||
o.bind("SUPER + CTRL + S", "Share", "blob-menu toggle share")
|
||||
|
||||
o.bind("SUPER + CTRL + PERIOD", "Transcode", "blob-transcode")
|
||||
|
||||
o.bind("SUPER + CTRL + R", "Set reminder", "blob-menu toggle reminder-set")
|
||||
o.bind("SUPER + CTRL + ALT + R", "Show reminders", "blob-reminder show")
|
||||
o.bind("SUPER + SHIFT + CTRL + R", "Clear reminders", "blob-reminder clear")
|
||||
|
||||
o.bind("SUPER + CTRL + ALT + T", "Show time", "blob-notification-time")
|
||||
o.bind("SUPER + CTRL + ALT + B", "Show battery remaining", "blob-notification-battery")
|
||||
o.bind("SUPER + CTRL + ALT + W", "Toggle weather", "blob-notification-weather")
|
||||
|
||||
o.bind("SUPER + CTRL + A", "Audio", "blob-shell shell toggle blob.audio")
|
||||
o.bind("SUPER + CTRL + B", "Bluetooth", "blob-shell shell toggle blob.bluetooth")
|
||||
o.bind("SUPER + CTRL + D", "Display", "blob-shell shell toggle blob.monitor")
|
||||
o.bind("SUPER + CTRL + ALT + D", "Calendar", "blob-shell shell toggle blob.clock")
|
||||
o.bind("SUPER + CTRL + W", "Network", "blob-shell shell toggle blob.network")
|
||||
o.bind("SUPER + CTRL + P", "Power", "blob-shell shell toggle blob.power")
|
||||
o.bind("SUPER + CTRL + T", "Activity", { tui = "btop" })
|
||||
|
||||
-- The letters above name a panel; the numbers count them. 1 is the leftmost
|
||||
-- panel in the bar's right section, and a widget with no panel of its own (the
|
||||
-- tray) is not counted, so the number matches the icon a user would point at.
|
||||
-- A bar with fewer panels than this leaves the tail of the range doing nothing.
|
||||
for panel = 1, 9 do
|
||||
o.bind(
|
||||
"SUPER + CTRL + code:" .. tostring(panel + 9),
|
||||
"Bar panel " .. panel,
|
||||
"blob-shell -q shell togglePanelAt right " .. panel
|
||||
)
|
||||
end
|
||||
|
||||
o.bind("SUPER + CTRL + Z", "Zoom in", function()
|
||||
local zoom = hl.get_config("cursor.zoom_factor") or 1
|
||||
hl.config({ cursor = { zoom_factor = zoom + 1 } })
|
||||
end)
|
||||
|
||||
o.bind("SUPER + CTRL + ALT + Z", "Reset zoom", function()
|
||||
hl.config({ cursor = { zoom_factor = 1 } })
|
||||
end)
|
||||
|
||||
o.bind("SUPER + CTRL + L", "Lock system", "blob-system-lock")
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Blob Hyprland setup: helpers, defaults, and current theme overrides.
|
||||
|
||||
require("default.hypr.helpers")
|
||||
local require_optional = require("default.hypr.require_optional")
|
||||
|
||||
-- Use Blob defaults, but don't edit these directly.
|
||||
require("default.hypr.autostart")
|
||||
if _G.blob_default_bindings ~= false then
|
||||
require("default.hypr.bindings.media")
|
||||
require("default.hypr.bindings.clipboard")
|
||||
require("default.hypr.bindings.tiling")
|
||||
require("default.hypr.bindings.utilities")
|
||||
require_optional.module("default.hypr.bindings.applications")
|
||||
end
|
||||
require("default.hypr.envs")
|
||||
require("default.hypr.looknfeel")
|
||||
require("default.hypr.input")
|
||||
require("default.hypr.windows")
|
||||
|
||||
-- Current theme overrides.
|
||||
require_optional.module("blob.current.theme.hyprland")
|
||||
@@ -0,0 +1,39 @@
|
||||
-- Hyprland bootstrap for Blob's Lua module path.
|
||||
|
||||
local home = os.getenv("HOME")
|
||||
local reload_prefixes = {
|
||||
"default.hypr",
|
||||
"hypr",
|
||||
"blob.current.theme",
|
||||
}
|
||||
|
||||
local function should_reload_module(module)
|
||||
for _, prefix in ipairs(reload_prefixes) do
|
||||
if module == prefix or module:sub(1, #prefix + 1) == prefix .. "." then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
local modules_to_reload = {}
|
||||
for module in pairs(package.loaded) do
|
||||
if should_reload_module(module) then
|
||||
table.insert(modules_to_reload, module)
|
||||
end
|
||||
end
|
||||
|
||||
for _, module in ipairs(modules_to_reload) do
|
||||
package.loaded[module] = nil
|
||||
end
|
||||
|
||||
-- Load generated state from ~/.local/state, user modules from ~/.config, and
|
||||
-- Blob defaults from $BLOB_PATH.
|
||||
package.path = home
|
||||
.. "/.local/state/?.lua;"
|
||||
.. home
|
||||
.. "/.config/?.lua;"
|
||||
.. (os.getenv("BLOB_PATH") or (home .. "/.local/share/blob"))
|
||||
.. "/?.lua;"
|
||||
.. package.path
|
||||
@@ -0,0 +1,21 @@
|
||||
-- Disable a Hyprland input device whose name was stored as data, not Lua.
|
||||
-- Device names come from USB descriptors and must never be loaded as code.
|
||||
|
||||
local paths = require("default.hypr.paths")
|
||||
|
||||
return function(kind)
|
||||
-- Hardcoded to ~/.local/state to match blob-toggle-input and the
|
||||
-- sibling bash toggle tools, which all write there regardless of
|
||||
-- XDG_STATE_HOME.
|
||||
local file = io.open(paths.home .. "/.local/state/blob/toggles/hypr/" .. kind .. "-disabled-name", "r")
|
||||
if not file then
|
||||
return
|
||||
end
|
||||
|
||||
local name = file:read("*l")
|
||||
file:close()
|
||||
|
||||
if name and name ~= "" then
|
||||
hl.device({ name = name, enabled = false })
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
local paths = require("default.hypr.paths")
|
||||
local require_optional = require("default.hypr.require_optional")
|
||||
|
||||
-- GUM environment variables for styling purposes.
|
||||
require_optional.module("blob.current.theme.gum_env")
|
||||
|
||||
-- Cursor size.
|
||||
hl.env("XCURSOR_SIZE", "24")
|
||||
hl.env("HYPRCURSOR_SIZE", "24")
|
||||
|
||||
-- Force all apps to use Wayland.
|
||||
hl.env("GDK_BACKEND", "wayland,x11,*")
|
||||
hl.env("QT_QPA_PLATFORM", "wayland;xcb")
|
||||
hl.env("QT_QPA_PLATFORMTHEME", "gtk3")
|
||||
hl.env("MOZ_ENABLE_WAYLAND", "1")
|
||||
hl.env("ELECTRON_OZONE_PLATFORM_HINT", "wayland")
|
||||
hl.env("OZONE_PLATFORM", "wayland")
|
||||
hl.env("XDG_SESSION_TYPE", "wayland")
|
||||
|
||||
-- Allow better support for screen sharing (Google Meet, Discord, etc).
|
||||
hl.env("XDG_CURRENT_DESKTOP", "Hyprland")
|
||||
hl.env("XDG_SESSION_DESKTOP", "Hyprland")
|
||||
|
||||
-- Use XCompose file.
|
||||
hl.env("XCOMPOSEFILE", paths.home .. "/.XCompose")
|
||||
|
||||
-- hyprctl setenv doesn't reach keybind dispatcher env; use hl.env.
|
||||
hl.env("BLOB_PATH", paths.blob_path)
|
||||
|
||||
local bin_dir = paths.blob_path .. "/bin"
|
||||
local kept = {}
|
||||
for entry in (os.getenv("PATH") or "/usr/local/bin:/usr/bin"):gmatch("[^:]+") do
|
||||
if entry ~= bin_dir then table.insert(kept, entry) end
|
||||
end
|
||||
table.insert(kept, 1, bin_dir)
|
||||
hl.env("PATH", table.concat(kept, ":"))
|
||||
|
||||
-- Hardware-specific environment.
|
||||
require("default.hypr.nvidia")
|
||||
|
||||
hl.config({
|
||||
xwayland = {
|
||||
force_zero_scaling = true,
|
||||
},
|
||||
|
||||
ecosystem = {
|
||||
no_update_news = true,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,154 @@
|
||||
-- Shared helpers for Hyprland Lua configuration.
|
||||
|
||||
o = o or {}
|
||||
|
||||
local function shell_quote(value)
|
||||
return "'" .. tostring(value):gsub("'", "'\\''") .. "'"
|
||||
end
|
||||
|
||||
o.shell_quote = shell_quote
|
||||
|
||||
local function file_exists(path)
|
||||
local file = io.open(path, "r")
|
||||
if file then
|
||||
file:close()
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
-- Hyprland reaps its own children, so os.execute() can't retrieve an exit status
|
||||
-- from inside the compositor. Read a marker off stdout instead.
|
||||
function o.shell_succeeds(command)
|
||||
-- Subshell, so the redirection covers every command rather than binding to
|
||||
-- the last one and letting an earlier one write its own OK into the pipe.
|
||||
local pipe = io.popen("( " .. command .. " ) >/dev/null 2>&1 && echo OK")
|
||||
if not pipe then
|
||||
return false
|
||||
end
|
||||
|
||||
local output = pipe:read("*a") or ""
|
||||
pipe:close()
|
||||
|
||||
return output:find("OK", 1, true) ~= nil
|
||||
end
|
||||
|
||||
function o.cmd_present(command)
|
||||
if command:find("/", 1, true) then
|
||||
return file_exists(command)
|
||||
end
|
||||
|
||||
local path = os.getenv("PATH") or "/usr/local/bin:/usr/bin"
|
||||
for directory in (path .. ":"):gmatch("([^:]*):") do
|
||||
if file_exists((directory ~= "" and directory or ".") .. "/" .. command) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
return false
|
||||
end
|
||||
|
||||
function o.cmd_missing(command)
|
||||
return not o.cmd_present(command)
|
||||
end
|
||||
|
||||
local function command_from(value, description)
|
||||
if type(value) ~= "table" then
|
||||
return value
|
||||
end
|
||||
|
||||
if value.blob then
|
||||
return "blob-launch-" .. value.blob
|
||||
elseif value.focus and value.launch then
|
||||
return o.launch_sole(value.focus, value.launch)
|
||||
elseif value.launch then
|
||||
return o.launch(value.launch)
|
||||
elseif value.webapp then
|
||||
if value.focus then
|
||||
return o.launch_webapp_sole(description, value.webapp)
|
||||
else
|
||||
return o.launch_webapp(value.webapp)
|
||||
end
|
||||
elseif value.tui then
|
||||
if value.focus then
|
||||
return "blob-launch-or-focus-tui " .. shell_quote(value.tui)
|
||||
else
|
||||
return "blob-launch-tui " .. shell_quote(value.tui)
|
||||
end
|
||||
end
|
||||
|
||||
return value
|
||||
end
|
||||
|
||||
function o.preinstalled_bindings_enabled()
|
||||
if _G.blob_preinstalled_bindings ~= nil then
|
||||
return _G.blob_preinstalled_bindings == true
|
||||
end
|
||||
|
||||
return not file_exists((os.getenv("HOME") or "") .. "/.local/state/blob/preinstalls-removed")
|
||||
end
|
||||
|
||||
function o.bind(keys, description, dispatcher, options)
|
||||
local opts = options or {}
|
||||
|
||||
if description then
|
||||
opts.description = description
|
||||
end
|
||||
|
||||
dispatcher = command_from(dispatcher, description)
|
||||
|
||||
if type(dispatcher) == "string" then
|
||||
dispatcher = hl.dsp.exec_cmd(dispatcher)
|
||||
end
|
||||
|
||||
hl.bind(keys, dispatcher, opts)
|
||||
end
|
||||
|
||||
function o.launch(command)
|
||||
return "uwsm-app -- " .. command
|
||||
end
|
||||
|
||||
function o.exec_on_start(command)
|
||||
hl.on("hyprland.start", function()
|
||||
hl.exec_cmd(command)
|
||||
end)
|
||||
end
|
||||
|
||||
function o.launch_on_start(command)
|
||||
o.exec_on_start(o.launch(command))
|
||||
end
|
||||
|
||||
function o.launch_webapp(url)
|
||||
return "blob-launch-webapp " .. shell_quote(url)
|
||||
end
|
||||
|
||||
function o.launch_webapp_sole(name, url)
|
||||
return "blob-launch-or-focus-webapp " .. shell_quote(name) .. " " .. shell_quote(url)
|
||||
end
|
||||
|
||||
function o.launch_sole(match, command)
|
||||
return "blob-launch-or-focus " .. shell_quote(match) .. " " .. shell_quote(o.launch(command))
|
||||
end
|
||||
|
||||
function o.bind_toggle(keys, description, toggle, options)
|
||||
o.bind(keys, description, "blob-toggle-" .. toggle, options)
|
||||
end
|
||||
|
||||
function o.notify(message)
|
||||
return "blob-notify-send -u low " .. shell_quote(message)
|
||||
end
|
||||
|
||||
function o.window(match, rules)
|
||||
rules.match = rules.match or {}
|
||||
|
||||
if type(match) == "string" then
|
||||
rules.match.class = match
|
||||
else
|
||||
for key, value in pairs(match) do
|
||||
rules.match[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
hl.window_rule(rules)
|
||||
end
|
||||
@@ -0,0 +1,79 @@
|
||||
-- https://wiki.hypr.land/Configuring/Basics/Variables/#input
|
||||
|
||||
local function read_vconsole()
|
||||
local values = {}
|
||||
local file = io.open("/etc/vconsole.conf", "r")
|
||||
if not file then
|
||||
return values
|
||||
end
|
||||
|
||||
for line in file:lines() do
|
||||
local key, value = line:match("^%s*([%w_]+)%s*=%s*(.-)%s*$")
|
||||
if key and value then
|
||||
value = value:gsub("%s+#.*$", "")
|
||||
value = value:gsub('^"(.*)"$', "%1")
|
||||
value = value:gsub("^'(.*)'$", "%1")
|
||||
values[key] = value
|
||||
end
|
||||
end
|
||||
|
||||
file:close()
|
||||
return values
|
||||
end
|
||||
|
||||
-- Layouts that can't type Latin letters. Keep in sync with the list in
|
||||
-- etc/mkinitcpio.conf.d/blob_hooks.conf.
|
||||
local non_latin_layouts =
|
||||
" af am ara bd bg by et ge gr il in iq ir kg kh kz la lk mk mm mn mv np rs ru sy th tj ua "
|
||||
|
||||
local vconsole = read_vconsole()
|
||||
|
||||
local kb_layout = vconsole.XKBLAYOUT or "us"
|
||||
local kb_variant = vconsole.XKBVARIANT or ""
|
||||
-- CapsLock is the compose key, so Caps Lock itself has to live somewhere else.
|
||||
-- Both Shifts together is the usual home for it, but it's easy to hit by
|
||||
-- accident while typing. The _cancel variant sets Caps Lock the same way and
|
||||
-- releases it on the next lone Shift, so a misfire clears itself.
|
||||
local kb_options = "compose:caps,shift:both_capslock_cancel"
|
||||
|
||||
-- Hyprland resolves keybindings against the first entry in kb_layout, not the
|
||||
-- layout that's currently active, so Blob's Latin-keysym bindings (SUPER + W
|
||||
-- and friends) only fire when a Latin layout leads. Installing with a non-Latin
|
||||
-- one would otherwise leave the desktop unusable.
|
||||
if non_latin_layouts:find(" " .. kb_layout:match("^[^,]*") .. " ", 1, true) then
|
||||
kb_layout = "us," .. kb_layout
|
||||
kb_variant = "," .. kb_variant
|
||||
-- Reach the original layout with Left Alt + Right Alt.
|
||||
kb_options = kb_options .. ",grp:alts_toggle"
|
||||
end
|
||||
|
||||
hl.config({
|
||||
input = {
|
||||
kb_layout = kb_layout,
|
||||
kb_variant = kb_variant,
|
||||
kb_model = "",
|
||||
kb_options = kb_options,
|
||||
kb_rules = "",
|
||||
follow_mouse = 1,
|
||||
sensitivity = 0,
|
||||
|
||||
repeat_rate = 40,
|
||||
repeat_delay = 250,
|
||||
numlock_by_default = true,
|
||||
|
||||
touchpad = {
|
||||
natural_scroll = false,
|
||||
clickfinger_behavior = true,
|
||||
scroll_factor = 0.4,
|
||||
},
|
||||
},
|
||||
|
||||
misc = {
|
||||
key_press_enables_dpms = true,
|
||||
mouse_move_enables_dpms = true,
|
||||
},
|
||||
})
|
||||
|
||||
-- Scroll nicely in the terminal.
|
||||
o.window("(Alacritty|kitty|foot)", { scroll_touchpad = 1.5 })
|
||||
o.window("com.mitchellh.ghostty", { scroll_touchpad = 0.2 })
|
||||
@@ -0,0 +1,126 @@
|
||||
-- Refer to https://wiki.hypr.land/Configuring/Basics/Variables/
|
||||
|
||||
local active_border_color = { colors = { "rgba(33ccffee)", "rgba(00ff99ee)" }, angle = 45 }
|
||||
local inactive_border_color = "rgba(595959aa)"
|
||||
|
||||
hl.config({
|
||||
general = {
|
||||
gaps_in = 5,
|
||||
gaps_out = 10,
|
||||
border_size = 2,
|
||||
|
||||
col = {
|
||||
active_border = active_border_color,
|
||||
inactive_border = inactive_border_color,
|
||||
},
|
||||
|
||||
resize_on_border = false,
|
||||
allow_tearing = false,
|
||||
layout = "dwindle",
|
||||
},
|
||||
|
||||
decoration = {
|
||||
rounding = 0,
|
||||
|
||||
shadow = {
|
||||
enabled = false,
|
||||
},
|
||||
|
||||
blur = {
|
||||
enabled = false,
|
||||
},
|
||||
},
|
||||
|
||||
group = {
|
||||
col = {
|
||||
border_active = active_border_color,
|
||||
border_inactive = inactive_border_color,
|
||||
},
|
||||
|
||||
groupbar = {
|
||||
font_size = 12,
|
||||
font_family = "monospace",
|
||||
font_weight_active = "ultraheavy",
|
||||
font_weight_inactive = "normal",
|
||||
indicator_height = 1,
|
||||
indicator_gap = 5,
|
||||
height = 22,
|
||||
gaps_in = 5,
|
||||
gaps_out = 0,
|
||||
text_color = "rgb(ffffff)",
|
||||
text_color_inactive = "rgba(ffffff90)",
|
||||
col = {
|
||||
active = "rgba(00000040)",
|
||||
inactive = "rgba(00000020)",
|
||||
},
|
||||
gradients = true,
|
||||
gradient_rounding = 0,
|
||||
gradient_round_only_edges = false,
|
||||
},
|
||||
},
|
||||
|
||||
animations = {
|
||||
enabled = true,
|
||||
},
|
||||
})
|
||||
|
||||
-- Default animations, see https://wiki.hypr.land/Configuring/Advanced-and-Cool/Animations/
|
||||
hl.curve("easeOutQuint", { type = "bezier", points = { { 0.23, 1 }, { 0.32, 1 } } })
|
||||
hl.curve("easeInOutCubic", { type = "bezier", points = { { 0.65, 0.05 }, { 0.36, 1 } } })
|
||||
hl.curve("linear", { type = "bezier", points = { { 0, 0 }, { 1, 1 } } })
|
||||
hl.curve("almostLinear", { type = "bezier", points = { { 0.5, 0.5 }, { 0.75, 1.0 } } })
|
||||
hl.curve("quick", { type = "bezier", points = { { 0.15, 0 }, { 0.1, 1 } } })
|
||||
|
||||
hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" })
|
||||
hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" })
|
||||
hl.animation({ leaf = "windows", enabled = true, speed = 3.79, bezier = "easeOutQuint" })
|
||||
hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, bezier = "easeOutQuint", style = "popin 87%" })
|
||||
hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "linear", style = "popin 87%" })
|
||||
hl.animation({ leaf = "fadeIn", enabled = true, speed = 1.73, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "fadeOut", enabled = true, speed = 1.46, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "fade", enabled = true, speed = 3.03, bezier = "quick" })
|
||||
hl.animation({ leaf = "fadeSwitch", enabled = false })
|
||||
hl.animation({ leaf = "layers", enabled = true, speed = 3.81, bezier = "easeOutQuint" })
|
||||
hl.animation({ leaf = "layersIn", enabled = true, speed = 4, bezier = "easeOutQuint", style = "fade" })
|
||||
hl.animation({ leaf = "layersOut", enabled = true, speed = 1.5, bezier = "linear", style = "fade" })
|
||||
hl.animation({ leaf = "fadeLayersIn", enabled = true, speed = 1.79, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "fadeLayersOut", enabled = true, speed = 1.39, bezier = "almostLinear" })
|
||||
hl.animation({ leaf = "workspaces", enabled = false })
|
||||
hl.animation({ leaf = "specialWorkspace", enabled = true, speed = 3, bezier = "easeOutQuint", style = "slidevert" })
|
||||
|
||||
hl.config({
|
||||
dwindle = {
|
||||
preserve_split = true,
|
||||
force_split = 2,
|
||||
},
|
||||
|
||||
scrolling = {
|
||||
column_width = 0.49,
|
||||
},
|
||||
|
||||
master = {
|
||||
new_status = "master",
|
||||
},
|
||||
|
||||
misc = {
|
||||
disable_hyprland_logo = true,
|
||||
disable_splash_rendering = true,
|
||||
disable_scale_notification = true,
|
||||
focus_on_activate = true,
|
||||
anr_missed_pings = 3,
|
||||
on_focus_under_fullscreen = 1,
|
||||
initial_workspace_tracking = 0,
|
||||
-- Let a fresh shell re-acquire the session lock after the lock client
|
||||
-- died, so blob-shell-restart can recover the LOCK failsafe.
|
||||
allow_session_lock_restore = true,
|
||||
},
|
||||
|
||||
cursor = {
|
||||
hide_on_key_press = true,
|
||||
warp_on_change_workspace = 1,
|
||||
},
|
||||
|
||||
binds = {
|
||||
hide_special_on_workspace_change = true,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,19 @@
|
||||
local paths = require("default.hypr.paths")
|
||||
|
||||
local nvidia = paths.blob_path .. "/bin/blob-hw-nvidia"
|
||||
local nvidia_gsp = paths.blob_path .. "/bin/blob-hw-nvidia-gsp"
|
||||
local nvidia_without_gsp = paths.blob_path .. "/bin/blob-hw-nvidia-without-gsp"
|
||||
|
||||
-- These detectors read cached sysfs IDs rather than shelling out to lspci.
|
||||
-- lspci reads PCI config space, which resumes a runtime-suspended GPU, and on a
|
||||
-- hybrid laptop that wake alone outlasts Hyprland's 1.5s config reload budget.
|
||||
if o.shell_succeeds(o.shell_quote(nvidia)) then
|
||||
if o.shell_succeeds(o.shell_quote(nvidia_gsp)) then
|
||||
hl.env("NVD_BACKEND", "direct")
|
||||
hl.env("LIBVA_DRIVER_NAME", "nvidia")
|
||||
hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia")
|
||||
elseif o.shell_succeeds(o.shell_quote(nvidia_without_gsp)) then
|
||||
hl.env("NVD_BACKEND", "egl")
|
||||
hl.env("__GLX_VENDOR_LIBRARY_NAME", "nvidia")
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
-- Shared path constants for Blob's Hyprland Lua modules.
|
||||
-- Lua files loaded with require() have separate local scopes, so modules that
|
||||
-- need these paths import this table instead of repeating os.getenv() lookups.
|
||||
|
||||
local home = os.getenv("HOME")
|
||||
|
||||
-- A variable that is set but empty means "unset" (XDG Base Directory spec);
|
||||
-- bash's ${VAR:-fallback} in the sibling tools treats it the same way.
|
||||
local function env_or(name, fallback)
|
||||
local value = os.getenv(name)
|
||||
if value == nil or value == "" then
|
||||
return fallback
|
||||
end
|
||||
return value
|
||||
end
|
||||
|
||||
return {
|
||||
home = home,
|
||||
config_home = env_or("XDG_CONFIG_HOME", home .. "/.config"),
|
||||
state_home = env_or("XDG_STATE_HOME", home .. "/.local/state"),
|
||||
blob_path = env_or("BLOB_PATH", home .. "/.local/share/blob"),
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
-- Require every *.lua file in a directory in sorted order.
|
||||
-- Used for Blob extension-style folders such as default/hypr/apps,
|
||||
-- default/hypr/bindings, and ~/.local/state/blob/toggles/hypr.
|
||||
-- Pass a module prefix for normal package.path modules, e.g.
|
||||
-- require_all.files(paths.blob_path .. "/default/hypr/apps", "default.hypr.apps")
|
||||
-- Pass nil as the prefix when the directory itself has been added to package.path.
|
||||
-- Pass options.exclude as a set of base names (without ".lua") to skip; a legacy
|
||||
-- file that must never be loaded as code stays on disk for a migration to remove.
|
||||
|
||||
local M = {}
|
||||
|
||||
local function shell_quote(path)
|
||||
return "'" .. path:gsub("'", "'\\''") .. "'"
|
||||
end
|
||||
|
||||
function M.files(dir, module_prefix, options)
|
||||
local exclude = options and options.exclude or {}
|
||||
local handle = io.popen("find " .. shell_quote(dir) .. " -maxdepth 1 -type f -name '*.lua' -printf '%f\\n' 2>/dev/null | sort")
|
||||
if handle then
|
||||
for filename in handle:lines() do
|
||||
local name = filename:gsub("%.lua$", "")
|
||||
if not exclude[name] then
|
||||
local module = name
|
||||
if module_prefix then
|
||||
module = module_prefix .. "." .. module
|
||||
end
|
||||
|
||||
if options and options.reload then
|
||||
package.loaded[module] = nil
|
||||
end
|
||||
|
||||
require(module)
|
||||
end
|
||||
end
|
||||
handle:close()
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Require a module only when it can be found on package.path.
|
||||
-- Errors inside existing modules still surface normally.
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.module(module)
|
||||
if package.searchpath(module, package.path) then
|
||||
return require(module)
|
||||
end
|
||||
end
|
||||
|
||||
return M
|
||||
@@ -0,0 +1,23 @@
|
||||
local paths = require("default.hypr.paths")
|
||||
local require_all = require("default.hypr.require_all")
|
||||
|
||||
local toggles_dir = paths.state_home .. "/blob/toggles/hypr"
|
||||
package.path = toggles_dir .. "/?.lua;" .. package.path
|
||||
|
||||
-- touchpad-disabled.lua / touchscreen-disabled.lua were generated Lua in older
|
||||
-- versions and could carry an injected USB device name. They must never be loaded
|
||||
-- as code again: exclude them so a not-yet-migrated install cannot execute a
|
||||
-- leftover payload on reload. The migration recovers the name and deletes them.
|
||||
require_all.files(toggles_dir, nil, {
|
||||
reload = true,
|
||||
exclude = {
|
||||
["touchpad-disabled"] = true,
|
||||
["touchscreen-disabled"] = true,
|
||||
},
|
||||
})
|
||||
|
||||
local disabled_input_device = require("default.hypr.disabled-input-device")
|
||||
disabled_input_device("touchpad")
|
||||
disabled_input_device("touchscreen")
|
||||
|
||||
require("default.hypr.workspace-layouts")
|
||||
@@ -0,0 +1,2 @@
|
||||
-- This directory is intended for permanent config toggle flags.
|
||||
-- Do not remove this file; the directory always needs at least one file.
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Avoid overly wide single-window layouts on wide screens.
|
||||
hl.config({
|
||||
layout = {
|
||||
single_window_aspect_ratio = { 1, 1 },
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Remove all window gaps and borders.
|
||||
hl.config({
|
||||
general = {
|
||||
gaps_out = 0,
|
||||
gaps_in = 0,
|
||||
border_size = 0,
|
||||
},
|
||||
|
||||
decoration = {
|
||||
rounding = 0,
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
-- See https://wiki.hypr.land/Configuring/Basics/Window-Rules/
|
||||
|
||||
o.window(".*", { suppress_event = "maximize" })
|
||||
|
||||
-- Tag all windows for default opacity (apps can override with -default-opacity tag).
|
||||
o.window(".*", { tag = "+default-opacity" })
|
||||
|
||||
-- Fix some dragging issues with XWayland.
|
||||
o.window(
|
||||
{
|
||||
class = "^$",
|
||||
title = "^$",
|
||||
xwayland = true,
|
||||
float = true,
|
||||
fullscreen = false,
|
||||
pin = false,
|
||||
},
|
||||
{ no_focus = true }
|
||||
)
|
||||
|
||||
-- App-specific tweaks (may remove default-opacity tag).
|
||||
require("default.hypr.apps")
|
||||
|
||||
-- Apply default opacity after apps have had a chance to opt out.
|
||||
o.window({ tag = "default-opacity" }, { opacity = "0.985 0.96" })
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Restore workspace layouts saved by blob-hypr-workspace-layout-toggle.
|
||||
|
||||
local paths = require("default.hypr.paths")
|
||||
local require_all = require("default.hypr.require_all")
|
||||
|
||||
local layouts_dir = paths.state_home .. "/blob/workspace-layouts"
|
||||
|
||||
require_all.files(layouts_dir, "blob.workspace-layouts", { reload = true })
|
||||
@@ -0,0 +1,2 @@
|
||||
[Manager]
|
||||
DefaultTimeoutStopSec=5s
|
||||
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Use the Vfio to Integrated trick to turn off NVIDIA dgpu when in integrated mode
|
||||
# without needing to restart the computer. This is needed because computers like the Asus G14
|
||||
# will wake after suspend in Hybrid mode, even if the system was in Integrated mode before
|
||||
# suspending.
|
||||
|
||||
restore_marker=/run/blob-force-igpu-integrated
|
||||
sleep_action=${SYSTEMD_SLEEP_ACTION:-$2}
|
||||
[[ -x /usr/bin/supergfxctl ]] || exit 0
|
||||
|
||||
switch_mode() {
|
||||
local expected="$1" current
|
||||
|
||||
if ! /usr/bin/timeout --kill-after=1s 3s /usr/bin/supergfxctl -m "$expected"; then
|
||||
echo "Could not request the GPU transition to $expected mode" >&2
|
||||
return 1
|
||||
fi
|
||||
for _ in {1..10}; do
|
||||
if current=$(/usr/bin/timeout --kill-after=1s 2s /usr/bin/supergfxctl -g 2>/dev/null) &&
|
||||
[[ $current == "$expected" ]]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "Could not confirm the GPU transition to $expected mode" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
pre)
|
||||
# Remember the mode this sleep cycle started in. supergfxctl persists the
|
||||
# temporary hibernate switch to Vfio, so post must not consult that mutable
|
||||
# value when deciding whether to restore Integrated mode.
|
||||
if [[ -L $restore_marker ]]; then
|
||||
exit 1
|
||||
elif [[ ! -f $restore_marker ]]; then
|
||||
/usr/bin/grep -Eq '"mode"[[:space:]]*:[[:space:]]*"Integrated"' /etc/supergfxd.conf 2>/dev/null || exit 0
|
||||
/usr/bin/install -m 0600 -o root -g root -T /dev/null "$restore_marker"
|
||||
fi
|
||||
|
||||
# Before hibernating, switch to Vfio so the nvidia driver is detached from the dGPU.
|
||||
# Without this, hibernate resume fails because the nvidia driver can't freeze a
|
||||
# powered-off dGPU (returns -EIO), which aborts the entire resume.
|
||||
if [[ $sleep_action == "hibernate" ]]; then
|
||||
switch_mode Vfio
|
||||
fi
|
||||
;;
|
||||
post)
|
||||
[[ -f $restore_marker && ! -L $restore_marker ]] || exit 0
|
||||
|
||||
# small delay so the device is fully re-enumerated
|
||||
sleep 4
|
||||
|
||||
# force-bind dGPU to vfio (fully detached from nvidia)
|
||||
switch_mode Vfio
|
||||
|
||||
# then go back to Integrated, which powers it off again
|
||||
switch_mode Integrated
|
||||
/usr/bin/rm -f -- "$restore_marker"
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,20 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Turn off keyboard backlight before hibernate to prevent hang on power-off.
|
||||
# The ASUS keyboard controller can block S4 shutdown if LEDs are active.
|
||||
|
||||
sleep_action=${SYSTEMD_SLEEP_ACTION:-$2}
|
||||
|
||||
if [[ $1 == "pre" && $sleep_action == "hibernate" ]]; then
|
||||
device=""
|
||||
for candidate in /sys/class/leds/*kbd_backlight*; do
|
||||
if [[ -e "$candidate" ]]; then
|
||||
device="$(basename "$candidate")"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$device" ]]; then
|
||||
brightnessctl -d "$device" set 0 >/dev/null 2>&1
|
||||
fi
|
||||
fi
|
||||
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Lazy-unmount gvfsd-fuse filesystems before suspend/hibernate to prevent the
|
||||
# kernel's process freeze from timing out. FUSE daemons (like gvfsd-fuse from
|
||||
# Nautilus) can block in uninterruptible sleep during freeze, causing suspend
|
||||
# to silently fail. After wake, restart gvfs so the FUSE mount is restored.
|
||||
|
||||
if [[ $1 == "pre" ]]; then
|
||||
while IFS=' ' read -r _ mountpoint fstype _; do
|
||||
if [[ $fstype == fuse.gvfsd-fuse ]]; then
|
||||
mountpoint=$(printf '%b' "$mountpoint")
|
||||
fusermount3 -uz "$mountpoint" 2>/dev/null || fusermount -uz "$mountpoint" 2>/dev/null || true
|
||||
fi
|
||||
done < /proc/mounts
|
||||
fi
|
||||
|
||||
if [[ $1 == "post" ]]; then
|
||||
# Run in background — user.slice is still frozen at this point, so a
|
||||
# synchronous restart would block the thaw for up to 90 seconds.
|
||||
(
|
||||
sleep 5
|
||||
for uid_dir in /run/user/*; do
|
||||
uid=$(basename "$uid_dir")
|
||||
if [[ -S $uid_dir/bus ]]; then
|
||||
sudo -u "#$uid" env \
|
||||
DBUS_SESSION_BUS_ADDRESS="unix:path=$uid_dir/bus" \
|
||||
XDG_RUNTIME_DIR="$uid_dir" \
|
||||
systemctl --user restart gvfs-daemon.service 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
) &
|
||||
fi
|
||||
@@ -0,0 +1,3 @@
|
||||
[Service]
|
||||
ExecStart=
|
||||
ExecStart=/usr/bin/updatedb --prune-bind-mounts=no --add-prunepaths=/.snapshots
|
||||
@@ -0,0 +1,6 @@
|
||||
[Service]
|
||||
# Delay startup to avoid race condition with display manager initialization
|
||||
# when booting in Integrated mode. Without this delay, the system can freeze
|
||||
# on boot because supergfxd tries to disable the dGPU while the display
|
||||
# subsystem is still initializing.
|
||||
ExecStartPre=/bin/sleep 5
|
||||
@@ -0,0 +1,16 @@
|
||||
# Make user apps the only thing systemd-oomd is allowed to kill.
|
||||
#
|
||||
# Hyprland runs in session.slice, as wayland-wm@hyprland.desktop.service, while
|
||||
# everything launched through uwsm-app lands in app.slice/app-*.scope. Marking
|
||||
# only app.slice as a kill candidate means the compositor is structurally
|
||||
# ineligible: oomd takes the browser or terminal that caused the pressure, and
|
||||
# the session survives to show the notification about it. Setting this on
|
||||
# user@.service instead would put the compositor back in the candidate pool.
|
||||
#
|
||||
# Swap kill is a backstop for the slower shape of the same problem, where swap
|
||||
# fills before pressure spikes. It uses the global SwapUsedLimit (90%) from
|
||||
# /etc/systemd/oomd.conf.d/10-blob.conf, which also carries the pressure
|
||||
# thresholds.
|
||||
[Slice]
|
||||
ManagedOOMMemoryPressure=kill
|
||||
ManagedOOMSwap=kill
|
||||
@@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=Announce process crashes and offer an AI diagnosis
|
||||
# Needs the session bus to notify, and uwsm-app to open the diagnosis terminal.
|
||||
# Both are up only after graphical-session.target.
|
||||
After=graphical-session.target
|
||||
PartOf=graphical-session.target
|
||||
ConditionEnvironment=WAYLAND_DISPLAY
|
||||
# Set by blob-toggle-crash-capture. Checked here so a disabled watcher stays
|
||||
# disabled across logins without the unit having to be disabled.
|
||||
ConditionPathExists=!%h/.local/state/blob/toggles/crash-capture-off
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/blob-crash-watch
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,31 @@
|
||||
[Unit]
|
||||
Description=Fcitx5 input method (XCompose sequences)
|
||||
# fcitx5 turns the CapsLock compose sequences in ~/.XCompose into text for
|
||||
# Wayland clients.
|
||||
#
|
||||
# Wait for the compositor: fcitx5 needs WAYLAND_DISPLAY and DISPLAY, which uwsm
|
||||
# imports into the user manager before it reaches graphical-session.target.
|
||||
After=graphical-session.target
|
||||
# The wayland connection dies with the compositor, so follow the session rather
|
||||
# than linger against a socket that is gone.
|
||||
PartOf=graphical-session.target
|
||||
# After= is ordering only -- it does not stop anything from starting this unit
|
||||
# while the target is inactive. An `blob update` over SSH has a live user
|
||||
# manager (pam_systemd) and no graphical session, and a fcitx5 started there
|
||||
# comes up with no WAYLAND_DISPLAY and no way to reach any client. Worse, it
|
||||
# stays active, so the later graphical-session.target activation won't pull in a
|
||||
# working one -- Wants= does not restart what is already running. Skip the start
|
||||
# instead; the unit stays enabled and starts for real at graphical login.
|
||||
ConditionEnvironment=WAYLAND_DISPLAY
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# notificationitem duplicates the tray entry blob already renders itself.
|
||||
ExecStart=/usr/bin/fcitx5 --disable notificationitem
|
||||
# always, not on-failure: fcitx5 exits 0 when it detects another instance owning
|
||||
# its bus name, and a clean exit still leaves the user with no input method.
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=Lock Blob before suspend
|
||||
# The monitor calls into the running Blob shell. Wait until UWSM has imported
|
||||
# BLOB_PATH and WAYLAND_DISPLAY, but keep the default target ordering so the
|
||||
# monitor starts before graphical-session.target is reached.
|
||||
After=dbus.socket wayland-session-waitenv.service
|
||||
Requires=dbus.socket
|
||||
PartOf=graphical-session.target
|
||||
ConditionEnvironment=BLOB_PATH
|
||||
ConditionEnvironment=WAYLAND_DISPLAY
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/usr/bin/blob-system-sleep-monitor
|
||||
Restart=always
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,32 @@
|
||||
[Unit]
|
||||
Description=Blob speaker tuning filter-chain
|
||||
Documentation=https://github.com/basecamp/omarchy/blob/master/docs/AUDIO-TUNING.md
|
||||
# WirePlumber does the linking, so starting before it is up risks the output being
|
||||
# linked before the speaker device has been discovered.
|
||||
After=pipewire.service wireplumber.service
|
||||
Requires=pipewire.service
|
||||
Wants=wireplumber.service
|
||||
# Restart with the audio daemon, since the filter-chain loses its connection when
|
||||
# PipeWire goes away.
|
||||
PartOf=pipewire.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# Hosts the tuning as a PipeWire *client* rather than loading it into the daemon
|
||||
# from pipewire.conf.d, which is only read at daemon startup. That is what lets
|
||||
# the tuning be switched on and off without restarting pipewire-pulse -- a
|
||||
# restart drops every PulseAudio client's connection, and applications that do
|
||||
# not reconnect (Spotify) have to be restarted by hand.
|
||||
#
|
||||
# It also contains failure: a malformed tuning breaks only this service, where a
|
||||
# bad drop-in in the daemon's own config stops PipeWire from starting at all.
|
||||
#
|
||||
# The config name is deliberately not PipeWire's stock filter-chain.conf, which
|
||||
# merges every fragment in ~/.config/pipewire/filter-chain.conf.d/ and would make
|
||||
# this service host unrelated user filters too.
|
||||
ExecStart=/usr/bin/pipewire -c blob-speaker-tuning.conf
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,23 @@
|
||||
[Unit]
|
||||
Description=Bluetooth pairing agent (auto-accept)
|
||||
Documentation=man:bt-agent(1)
|
||||
ConditionPathIsDirectory=/sys/class/bluetooth
|
||||
# bluez must be reachable on the system bus before we can register.
|
||||
After=dbus.socket
|
||||
Requires=dbus.socket
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# If bluetoothd is unavailable (for example in VMs or machines without a
|
||||
# usable adapter), skip cleanly instead of entering a restart loop.
|
||||
ExecCondition=/usr/bin/systemctl is-active --quiet bluetooth.service
|
||||
# NoInputNoOutput auto-accepts pair requests. Safe because the adapter
|
||||
# is only `pairable: true` when the user explicitly opens the blob
|
||||
# bluetoothPanel and starts scanning; outside that window bluez refuses
|
||||
# inbound pair attempts at a lower layer.
|
||||
ExecStart=/usr/bin/bt-agent -c NoInputNoOutput
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
@@ -0,0 +1,2 @@
|
||||
[Service]
|
||||
TimeoutStopSec=5s
|
||||
@@ -0,0 +1,93 @@
|
||||
# Main background, empty for terminal default, need to be empty if you want transparent background
|
||||
theme[main_bg]="{{ background }}"
|
||||
|
||||
# Main text color
|
||||
theme[main_fg]="{{ foreground }}"
|
||||
|
||||
# Title color for boxes
|
||||
theme[title]="{{ foreground }}"
|
||||
|
||||
# Highlight color for keyboard shortcuts
|
||||
theme[hi_fg]="{{ accent }}"
|
||||
|
||||
# Background color of selected item in processes box
|
||||
theme[selected_bg]="{{ selection }}"
|
||||
|
||||
# Foreground color of selected item in processes box
|
||||
theme[selected_fg]="{{ accent }}"
|
||||
|
||||
# Color of inactive/disabled text
|
||||
theme[inactive_fg]="{{ muted }}"
|
||||
|
||||
# Color of text appearing on top of graphs, i.e uptime and current network graph scaling
|
||||
theme[graph_text]="{{ light_foreground }}"
|
||||
|
||||
# Background color of the percentage meters
|
||||
theme[meter_bg]="{{ selection }}"
|
||||
|
||||
# Misc colors for processes box including mini cpu graphs, details memory graph and details status text
|
||||
theme[proc_misc]="{{ light_foreground }}"
|
||||
|
||||
# CPU, Memory, Network, Proc box outline colors
|
||||
theme[cpu_box]="{{ magenta }}"
|
||||
theme[mem_box]="{{ green }}"
|
||||
theme[net_box]="{{ red }}"
|
||||
theme[proc_box]="{{ accent }}"
|
||||
|
||||
# Box divider line and small boxes line color
|
||||
theme[div_line]="{{ muted }}"
|
||||
|
||||
# Temperature graph color (Green -> Yellow -> Red)
|
||||
theme[temp_start]="{{ green }}"
|
||||
theme[temp_mid]="{{ yellow }}"
|
||||
theme[temp_end]="{{ red }}"
|
||||
|
||||
# CPU graph colors (Teal -> Blue -> Magenta)
|
||||
theme[cpu_start]="{{ cyan }}"
|
||||
theme[cpu_mid]="{{ blue }}"
|
||||
theme[cpu_end]="{{ magenta }}"
|
||||
|
||||
# Mem/Disk free meter
|
||||
theme[free_start]="{{ magenta }}"
|
||||
theme[free_mid]="{{ blue }}"
|
||||
theme[free_end]="{{ cyan }}"
|
||||
|
||||
# Mem/Disk cached meter
|
||||
theme[cached_start]="{{ blue }}"
|
||||
theme[cached_mid]="{{ cyan }}"
|
||||
theme[cached_end]="{{ magenta }}"
|
||||
|
||||
# Mem/Disk available meter
|
||||
theme[available_start]="{{ yellow }}"
|
||||
theme[available_mid]="{{ red }}"
|
||||
theme[available_end]="{{ red }}"
|
||||
|
||||
# Mem/Disk used meter (Green -> Teal -> Blue)
|
||||
theme[used_start]="{{ green }}"
|
||||
theme[used_mid]="{{ cyan }}"
|
||||
theme[used_end]="{{ blue }}"
|
||||
|
||||
# Download graph colors
|
||||
theme[download_start]="{{ yellow }}"
|
||||
theme[download_mid]="{{ red }}"
|
||||
theme[download_end]="{{ red }}"
|
||||
|
||||
# Upload graph colors (Green -> Teal -> Blue)
|
||||
theme[upload_start]="{{ green }}"
|
||||
theme[upload_mid]="{{ cyan }}"
|
||||
theme[upload_end]="{{ blue }}"
|
||||
|
||||
# Process box color gradient for threads, mem and cpu usage
|
||||
theme[process_start]="{{ cyan }}"
|
||||
theme[process_mid]="{{ blue }}"
|
||||
theme[process_end]="{{ magenta }}"
|
||||
|
||||
# Graph gradient colors (spectrum shades from background to foreground)
|
||||
theme[gradient_color_0]="{{ background }}"
|
||||
theme[gradient_color_1]="{{ lighter_background }}"
|
||||
theme[gradient_color_2]="{{ selection }}"
|
||||
theme[gradient_color_3]="{{ muted }}"
|
||||
theme[gradient_color_4]="{{ dark_foreground }}"
|
||||
theme[gradient_color_5]="{{ foreground }}"
|
||||
theme[gradient_color_6]="{{ light_foreground }}"
|
||||
theme[gradient_color_7]="{{ bright_foreground }}"
|
||||
@@ -0,0 +1 @@
|
||||
{{ background_rgb }}
|
||||
@@ -0,0 +1,25 @@
|
||||
[colors-dark]
|
||||
foreground={{ foreground_strip }}
|
||||
background={{ background_strip }}
|
||||
selection-foreground={{ selection_foreground_strip }}
|
||||
selection-background={{ selection_background_strip }}
|
||||
|
||||
cursor={{ background_strip }} {{ bright_foreground_strip }}
|
||||
|
||||
regular0={{ background_strip }}
|
||||
regular1={{ red_strip }}
|
||||
regular2={{ green_strip }}
|
||||
regular3={{ yellow_strip }}
|
||||
regular4={{ blue_strip }}
|
||||
regular5={{ purple_strip }}
|
||||
regular6={{ cyan_strip }}
|
||||
regular7={{ foreground_strip }}
|
||||
|
||||
bright0={{ muted_strip }}
|
||||
bright1={{ bright_red_strip }}
|
||||
bright2={{ bright_green_strip }}
|
||||
bright3={{ bright_yellow_strip }}
|
||||
bright4={{ bright_blue_strip }}
|
||||
bright5={{ bright_magenta_strip }}
|
||||
bright6={{ bright_cyan_strip }}
|
||||
bright7={{ bright_foreground_strip }}
|
||||
@@ -0,0 +1,94 @@
|
||||
@define-color foreground {{ foreground }};
|
||||
@define-color background {{ background }};
|
||||
@define-color accent {{ accent }};
|
||||
@define-color muted {{ muted }};
|
||||
@define-color card_bg {{ lighter_background }};
|
||||
@define-color text_dark {{ background }};
|
||||
@define-color accent_hover {{ bright_blue }};
|
||||
@define-color selected_tab {{ accent }};
|
||||
@define-color text {{ foreground }};
|
||||
|
||||
* {
|
||||
all: unset;
|
||||
font-family: JetBrains Mono NF;
|
||||
color: @foreground;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.window {
|
||||
background: alpha(@background, 0.95);
|
||||
border: solid 2px @accent;
|
||||
margin: 4px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
tabs {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
tabs > tab {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
color: @text;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
tabs > tab:checked > .tab-label, tabs > tab:active > .tab-label {
|
||||
text-decoration: underline currentColor;
|
||||
color: @selected_tab;
|
||||
}
|
||||
|
||||
tabs > tab:focus > .tab-label {
|
||||
color: @foreground;
|
||||
}
|
||||
|
||||
.page {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.image-label {
|
||||
font-size: 12px;
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
flowboxchild > .card, button > .card {
|
||||
transition: all 0.2s ease;
|
||||
border: solid 2px transparent;
|
||||
border-color: @background;
|
||||
border-radius: 5px;
|
||||
background-color: @card_bg;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
flowboxchild:hover > .card, button:hover > .card, flowboxchild:active > .card, flowboxchild:selected > .card, button:active > .card, button:selected > .card, button:focus > .card {
|
||||
border: solid 2px @accent;
|
||||
}
|
||||
|
||||
.image {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.region-button {
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 5px;
|
||||
background-color: @accent;
|
||||
color: @text_dark;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.region-button > label {
|
||||
color: @text_dark;
|
||||
}
|
||||
|
||||
.region-button:not(:disabled):hover, .region-button:not(:disabled):focus {
|
||||
background-color: @accent_hover;
|
||||
color: @text_dark;
|
||||
}
|
||||
|
||||
.region-button:disabled {
|
||||
background-color: @muted;
|
||||
color: @background;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
local active_border_color = {{ hypr_gradient hyprland_active_border accent }}
|
||||
local inactive_border_color = {{ hypr_gradient hyprland_inactive_border rgba(595959aa) }}
|
||||
|
||||
hl.config({
|
||||
general = {
|
||||
col = {
|
||||
active_border = active_border_color,
|
||||
inactive_border = inactive_border_color,
|
||||
},
|
||||
},
|
||||
|
||||
group = {
|
||||
col = {
|
||||
border_active = active_border_color,
|
||||
border_inactive = inactive_border_color,
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,27 @@
|
||||
foreground {{ foreground }}
|
||||
background {{ background }}
|
||||
selection_foreground {{ selection_foreground }}
|
||||
selection_background {{ selection_background }}
|
||||
|
||||
cursor {{ bright_foreground }}
|
||||
cursor_text_color {{ background }}
|
||||
|
||||
active_border_color {{ accent }}
|
||||
active_tab_background {{ accent }}
|
||||
|
||||
color0 {{ background }}
|
||||
color1 {{ red }}
|
||||
color2 {{ green }}
|
||||
color3 {{ yellow }}
|
||||
color4 {{ blue }}
|
||||
color5 {{ magenta }}
|
||||
color6 {{ cyan }}
|
||||
color7 {{ foreground }}
|
||||
color8 {{ muted }}
|
||||
color9 {{ bright_red }}
|
||||
color10 {{ bright_green }}
|
||||
color11 {{ bright_yellow }}
|
||||
color12 {{ bright_blue }}
|
||||
color13 {{ bright_magenta }}
|
||||
color14 {{ bright_cyan }}
|
||||
color15 {{ bright_foreground }}
|
||||
@@ -0,0 +1,52 @@
|
||||
return {
|
||||
{
|
||||
"bjarneo/aether.nvim",
|
||||
branch = "v3",
|
||||
name = "aether",
|
||||
priority = 1000,
|
||||
opts = {
|
||||
colors = {
|
||||
bg = "{{ background }}",
|
||||
dark_bg = "{{ dark_background }}",
|
||||
darker_bg = "{{ darker_background }}",
|
||||
lighter_bg = "{{ lighter_background }}",
|
||||
|
||||
fg = "{{ foreground }}",
|
||||
dark_fg = "{{ dark_foreground }}",
|
||||
light_fg = "{{ light_foreground }}",
|
||||
bright_fg = "{{ bright_foreground }}",
|
||||
muted = "{{ muted }}",
|
||||
|
||||
red = "{{ red }}",
|
||||
yellow = "{{ yellow }}",
|
||||
orange = "{{ orange }}",
|
||||
green = "{{ green }}",
|
||||
cyan = "{{ cyan }}",
|
||||
blue = "{{ blue }}",
|
||||
magenta = "{{ magenta }}",
|
||||
brown = "{{ brown }}",
|
||||
|
||||
bright_red = "{{ bright_red }}",
|
||||
bright_yellow = "{{ bright_yellow }}",
|
||||
bright_green = "{{ bright_green }}",
|
||||
bright_cyan = "{{ bright_cyan }}",
|
||||
bright_blue = "{{ bright_blue }}",
|
||||
bright_magenta = "{{ bright_magenta }}",
|
||||
|
||||
accent = "{{ accent }}",
|
||||
cursor = "{{ bright_foreground }}",
|
||||
foreground = "{{ foreground }}",
|
||||
background = "{{ background }}",
|
||||
selection = "{{ selection }}",
|
||||
selection_foreground = "{{ selection_foreground }}",
|
||||
selection_background = "{{ selection_background }}",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"LazyVim/LazyVim",
|
||||
opts = {
|
||||
colorscheme = "aether",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
# Blob shell surfaces. Colors derive from colors.toml; sizes and the
|
||||
# typographic scale come from the keys below. Themes can ship
|
||||
# themes/<name>/shell.toml to replace this generated file.
|
||||
|
||||
[bar]
|
||||
# Alpha companions (where present) range from 0 (invisible) to 1 (opaque).
|
||||
background = "{{ background }}"
|
||||
background-alpha = 1.0
|
||||
text = "{{ foreground }}"
|
||||
# Modules calling attention to themselves (recording, voxtype, alerts, updates)
|
||||
active = "{{ red }}"
|
||||
# Cross-axis size at font base-size 12. size-horizontal is the height of
|
||||
# top/bottom bars; size-vertical is the width of left/right bars. With
|
||||
# scale-with-font enabled, these grow/shrink with [font] base-size.
|
||||
scale-with-font = true
|
||||
size-horizontal = 26
|
||||
size-vertical = 28
|
||||
|
||||
[hyprland]
|
||||
# Shared Hyprland-derived border tokens. Surface sections reference these so
|
||||
# lock, notifications, popups, and menu-style cards stay aligned with the
|
||||
# current Hyprland active-border gradient.
|
||||
active-border = "{{ shell_gradient hyprland_active_border accent }}"
|
||||
active-border-foreground = "{{ shell_gradient hyprland_active_border foreground }}"
|
||||
|
||||
[controls]
|
||||
# Shared state tokens for interactive control chrome (buttons, dropdowns,
|
||||
# tab strips, etc).
|
||||
|
||||
# Normal: idle control chrome. Border widths accept one CSS-style scalar/list:
|
||||
# N, "Y X", "T X B", or "T R B L". Per-side keys like
|
||||
# normal-border-width-left override the list. Each *-border accepts either a
|
||||
# solid color or a Hyprland-style gradient, e.g. "rgba(...) rgba(...) 45deg".
|
||||
normal-color = "{{ foreground }}"
|
||||
normal-fill-alpha = 0.04
|
||||
normal-border = "{{ foreground }}"
|
||||
normal-border-width = 1
|
||||
normal-border-alpha = 0.4
|
||||
|
||||
# Hover-cursor: mouse hover and the panel keyboard cursor.
|
||||
hover-cursor-color = "{{ foreground }}"
|
||||
hover-cursor-fill-alpha = 0.08
|
||||
hover-cursor-border = "{{ foreground }}"
|
||||
hover-cursor-border-width = 1
|
||||
hover-cursor-border-alpha = 0.25
|
||||
|
||||
# Focus: Qt activeFocus. Mirror the hover-cursor values by default so
|
||||
# mouse hover, keyboard cursor, and tab focus all read as the same state
|
||||
# — themes that want focus to stand out override these four lines.
|
||||
focus-color = "{{ foreground }}"
|
||||
focus-fill-alpha = 0.08
|
||||
focus-border = "{{ foreground }}"
|
||||
focus-border-width = 1
|
||||
focus-border-alpha = 0.25
|
||||
|
||||
# Selected: persistent chosen/current state.
|
||||
selected-color = "{{ foreground }}"
|
||||
selected-fill-alpha = 0.18
|
||||
selected-border = "{{ foreground }}"
|
||||
selected-border-width = 0
|
||||
selected-border-alpha = 1.0
|
||||
|
||||
# Momentary fills.
|
||||
pressed-fill-alpha = 0.22
|
||||
selection-fill-alpha = 0.35
|
||||
|
||||
[spacing]
|
||||
# `scale` multiplies shared margins, gaps, padding, controls, and panel
|
||||
# dimensions; components keep their proportions. With scale-with-font
|
||||
# enabled, those dimensions grow/shrink with [font] base-size too. Per-token
|
||||
# overrides (in px) below pin individual values without affecting the rest
|
||||
# of the scale. Uncomment any to tune a specific surface.
|
||||
scale = 1.0
|
||||
scale-with-font = true
|
||||
# xxs = 2
|
||||
# xs = 3
|
||||
# sm = 4
|
||||
# md = 6
|
||||
# lg = 8
|
||||
# xl = 10
|
||||
# xxl = 12
|
||||
# xxxl = 14
|
||||
# huge = 18
|
||||
# control-gap = 8
|
||||
# control-padding-x = 10
|
||||
# control-padding-y = 6
|
||||
# input-padding-y = 7
|
||||
# control-height = 28
|
||||
# popup-row-height = 28
|
||||
# row-gap = 8
|
||||
# row-padding-x = 12
|
||||
# label-gap = 4
|
||||
# panel-gap = 14
|
||||
# panel-padding = 18
|
||||
# popup-padding = 14
|
||||
# dropdown-width = 240
|
||||
# searchable-dropdown-width = 260
|
||||
# number-field-width = 120
|
||||
# searchable-popup-min-height = 220
|
||||
|
||||
[font]
|
||||
# base-size is the rem root for the type scale. Every Style.font.<token>
|
||||
# derives from it (e.g. body = base, subtitle ≈ base * 1.083,
|
||||
# heading ≈ base * 1.333). The shell only floors this at 1px; increase it
|
||||
# as much as you want.
|
||||
base-size = 12
|
||||
# Per-token overrides, in px. Uncomment any to pin a specific size without
|
||||
# affecting the rest of the scale. Useful for stylistic emphasis (a
|
||||
# minimalist theme that wants a bigger heading without scaling everything).
|
||||
# caption = 10
|
||||
# body-small = 11
|
||||
# body = 12
|
||||
# subtitle = 13
|
||||
# title = 14
|
||||
# heading = 16
|
||||
# display = 24
|
||||
# display-large = 28
|
||||
# icon-small = 11
|
||||
# icon = 14
|
||||
# icon-large = 18
|
||||
|
||||
[popups]
|
||||
# Shared by every bar flyout (dropdowns, OSD, popup cards).
|
||||
# Border accepts either a solid color or a Hyprland-style gradient. Border
|
||||
# widths accept one CSS-style scalar/list: N, "Y X", "T X B", or "T R B L";
|
||||
# individual border-width-top/right/bottom/left keys override the list.
|
||||
background = "{{ background }}"
|
||||
background-alpha = 1.0
|
||||
text = "{{ foreground }}"
|
||||
border = "hyprland.active-border"
|
||||
border-alpha = 1.0
|
||||
# border-width = 2
|
||||
|
||||
[tooltip]
|
||||
# Hover tooltips across the bar, panels, and buttons. background-alpha of
|
||||
# 0.97 mirrors the legacy hard-coded tooltip opacity.
|
||||
background = "{{ background }}"
|
||||
background-alpha = 0.97
|
||||
text = "{{ foreground }}"
|
||||
border = "hyprland.active-border-foreground"
|
||||
border-alpha = 1.0
|
||||
|
||||
[notifications]
|
||||
background = "{{ background }}"
|
||||
background-alpha = 1.0
|
||||
text = "{{ foreground }}"
|
||||
# Conventionally matches the Hyprland active-window border. Border accepts
|
||||
# either a solid color or the full active-border gradient.
|
||||
border = "hyprland.active-border"
|
||||
border-alpha = 1.0
|
||||
# border-width = 2
|
||||
countdown = "{{ accent }}"
|
||||
|
||||
[launcher]
|
||||
# Same six tokens as [menu], applied to the launcher overlay. Alpha
|
||||
# companions go from 0 (invisible) to 1 (opaque). scrim is the full-screen
|
||||
# dim layer behind the card; background is the card itself. Defaults
|
||||
# mirror [menu] with the card at 0.95 to preserve the legacy translucency.
|
||||
background = "{{ background }}"
|
||||
background-alpha = 0.95
|
||||
text = "{{ foreground }}"
|
||||
border = "hyprland.active-border-foreground"
|
||||
border-alpha = 1.0
|
||||
scrim = "{{ background }}"
|
||||
scrim-alpha = 0.5
|
||||
selected-background = "{{ foreground }}"
|
||||
selected-background-alpha = 0.08
|
||||
selected-text = "{{ accent }}"
|
||||
selected-border = "hyprland.active-border-foreground"
|
||||
selected-border-alpha = 0.25
|
||||
|
||||
[menu]
|
||||
# Cards, rows, and selected-row treatment. Alpha companions (where present)
|
||||
# go from 0 (invisible) to 1 (opaque). scrim is the full-screen dim layer
|
||||
# behind the card. Clipboard and emojis inherit these tokens.
|
||||
background = "{{ background }}"
|
||||
background-alpha = 1.0
|
||||
text = "{{ foreground }}"
|
||||
border = "hyprland.active-border-foreground"
|
||||
border-alpha = 1.0
|
||||
scrim = "{{ background }}"
|
||||
scrim-alpha = 0.5
|
||||
selected-background = "{{ foreground }}"
|
||||
selected-background-alpha = 0.08
|
||||
selected-text = "{{ accent }}"
|
||||
selected-border = "hyprland.active-border-foreground"
|
||||
selected-border-alpha = 0.25
|
||||
|
||||
[polkit]
|
||||
# Polkit authentication prompt (sudo/password dialogs). scrim is the
|
||||
# darkening layer behind the card; background is the card itself.
|
||||
# text-error tints the lock icon, password text, and placeholder when
|
||||
# authentication fails. border-alpha applies to both border and
|
||||
# border-error (the two states are mutually exclusive in time).
|
||||
background = "{{ background }}"
|
||||
background-alpha = 1.0
|
||||
text = "{{ foreground }}"
|
||||
text-error = "{{ red }}"
|
||||
border = "hyprland.active-border"
|
||||
border-error = "{{ red }}"
|
||||
border-alpha = 1.0
|
||||
scrim = "{{ background }}"
|
||||
scrim-alpha = 0.5
|
||||
# accent is the lock-icon glyph color + text-selection tint.
|
||||
accent = "{{ accent }}"
|
||||
|
||||
[lock]
|
||||
# Lock screen password input. background/background-alpha control the
|
||||
# centered input field card; border/border-active/border-error cycle
|
||||
# through idle, typing/authenticating, and wrong-password states.
|
||||
# border-alpha applies to all three border states (they are mutually
|
||||
# exclusive in time).
|
||||
background = "{{ background }}"
|
||||
background-alpha = 0.8
|
||||
text = "{{ foreground }}"
|
||||
placeholder = "{{ mix foreground background 34% }}"
|
||||
text-error = "{{ red }}"
|
||||
border = "hyprland.active-border"
|
||||
border-active = "hyprland.active-border"
|
||||
border-error = "{{ red }}"
|
||||
border-alpha = 1.0
|
||||
# selection is the text-selection tint inside the input field.
|
||||
selection = "{{ accent }}"
|
||||
selection-alpha = 0.45
|
||||
|
||||
[image-picker]
|
||||
# Carousel-style picker. The picker has no card surface, so `scrim` is
|
||||
# the full-screen wash. Per-slice dim overlays and text outlines on top
|
||||
# of the scrim track the foundational background color directly.
|
||||
# unselected-border-alpha softens carousel slices that aren't selected.
|
||||
scrim = "{{ background }}"
|
||||
scrim-alpha = 0.5
|
||||
text = "{{ foreground }}"
|
||||
selected-border = "{{ accent }}"
|
||||
selected-border-alpha = 1.0
|
||||
unselected-border = "{{ foreground }}"
|
||||
unselected-border-alpha = 0.28
|
||||
@@ -0,0 +1,24 @@
|
||||
/* Generated by blob-theme-templates from colors.toml on every
|
||||
theme change (static, dynamic, or shared) - do not edit directly.
|
||||
Consumed by zen/userChrome.css. */
|
||||
:root {
|
||||
--background: {{ background }};
|
||||
--foreground: {{ foreground }};
|
||||
--accent: {{ accent }};
|
||||
--color0: {{ color0 }};
|
||||
--color1: {{ color1 }};
|
||||
--color2: {{ color2 }};
|
||||
--color3: {{ color3 }};
|
||||
--color4: {{ color4 }};
|
||||
--color5: {{ color5 }};
|
||||
--color6: {{ color6 }};
|
||||
--color7: {{ color7 }};
|
||||
--color8: {{ color8 }};
|
||||
--color9: {{ color9 }};
|
||||
--color10: {{ color10 }};
|
||||
--color11: {{ color11 }};
|
||||
--color12: {{ color12 }};
|
||||
--color13: {{ color13 }};
|
||||
--color14: {{ color14 }};
|
||||
--color15: {{ color15 }};
|
||||
}
|
||||
Reference in New Issue
Block a user