-- ───────────────────────────────────────────────────────────────────────────── -- Look and feel -- Tokyo Night Moon -- -- Colours here must stay in sync with quickshell/config/Theme.qml. -- accent user-selectable, see `accents` below -- blue (#82aaff) ships -- bg #222436 base -- -- Performance note: every animation below is event-driven. Nothing uses the -- "loop" style on borderangle/shadowangle/glowangle -- that style forces -- Hyprland to render at full refresh rate forever, even when nothing is -- visible, which is exactly the kind of idle GPU burn to avoid on a 4500x3000 -- display. -- ───────────────────────────────────────────────────────────────────────────── local prefs = require("prefs") -- Mirrors config/Theme.qml's `accents` map. Lua has no import path into a QML -- singleton, so the hex pairs are restated here -- just the four hex strings -- each named accent needs, not the labels, which stay UI-only. local accents = { blue = { dark = "82aaff", darkSecondary = "b172b0", light = "2e7de9", lightSecondary = "9854f1" }, orchid = { dark = "c099ff", darkSecondary = "fca7ea", light = "7847bd", lightSecondary = "9854f1" }, teal = { dark = "86e1fc", darkSecondary = "82aaff", light = "007197", lightSecondary = "2e7de9" }, green = { dark = "c3e88d", darkSecondary = "86e1fc", light = "587539", lightSecondary = "007197" }, amber = { dark = "ffc777", darkSecondary = "ff966c", light = "8c6c3e", lightSecondary = "b15c00" }, orange = { dark = "ff966c", darkSecondary = "ff757f", light = "b15c00", lightSecondary = "c64343" }, rose = { dark = "ff757f", darkSecondary = "c099ff", light = "f52a65", lightSecondary = "9854f1" }, slate = { dark = "828bb8", darkSecondary = "82aaff", light = "6172b0", lightSecondary = "2e7de9" }, } -- The accent pair a fresh session or `hyprctl reload` starts from. -- services/ColorScheme.qml overwrites this live, from the same Theme.accents -- data, once the shell settles (~1.2s after startup -- see its `settle` -- Timer). This table exists only so the compositor is never observably blue -- for a non-blue accent during the gap before that first live apply. local accentScheme = prefs.get("colorScheme", "dark") local accentPair = accents[prefs.get("accentName", "blue")] or accents.blue local accentStart = accentScheme == "light" and accentPair.light or accentPair.dark local accentEnd = accentScheme == "light" and accentPair.lightSecondary or accentPair.darkSecondary hl.config({ general = { gaps_in = prefs.get("gapsIn", 5), gaps_out = prefs.get("gapsOut", 10), border_size = prefs.get("borderSize", 2), col = { -- The focused accent role, on a diagonal so the pair is visible -- on both a tall and a wide window. Driven by the chosen -- accentName (see the `accents` table above); services/ -- ColorScheme.qml applies the same values live, and restates them -- from Theme.accent/Theme.accentSecondary on every scheme change -- too, since each accent carries a separate pair per scheme. active_border = { colors = { "rgba(" .. accentStart .. "ee)", "rgba(" .. accentEnd .. "ee)" }, angle = 115 }, -- The neutral inactive role follows the colour scheme because a -- dark neutral disappears against a light desktop. -- services/ColorScheme.qml applies the same values live; this is -- the value a fresh session starts from. inactive_border = prefs.get("colorScheme", "dark") == "light" and "rgba(a8aecb99)" or "rgba(3b426199)", }, resize_on_border = prefs.get("resizeOnBorder", true), extend_border_grab_area = prefs.getInt("borderGrabArea", 15), hover_icon_on_border = prefs.get("hoverIconOnBorder", true), -- Enables the per-window "immediate" rule used for games in rules.lua. -- Harmless on its own; tearing only happens where a rule opts in. allow_tearing = true, layout = "dwindle", snap = { enabled = true, window_gap = prefs.getInt("snapWindowGap", 10), monitor_gap = prefs.getInt("snapMonitorGap", 10), respect_gaps = prefs.get("snapRespectGaps", false), }, }, decoration = { -- 18 to match the shell's popover radius, so a window and a panel sitting -- next to each other read as the same object family. rounding = prefs.get("windowRounding", 18), rounding_power = prefs.get("roundingPower", 2), active_opacity = prefs.get("activeOpacity", 1.0), fullscreen_opacity = prefs.get("fullscreenOpacity", 1.0), inactive_opacity = prefs.get("inactiveOpacity", 1.0), blur = { enabled = prefs.get("blurEnabled", true), size = prefs.get("blurSize", 8), passes = prefs.get("blurPasses", 3), -- Required for blur to be affordable. Never turn this off. new_optimizations = true, -- Floating windows skip blurring what's behind tiled ones. Big win. xray = true, ignore_opacity = true, noise = 0.0117, contrast = 0.9, brightness = 0.85, vibrancy = 0.17, vibrancy_darkness = 0.05, popups = true, popups_ignorealpha = 0.2, -- Blurring the special workspace is expensive and rarely seen. special = false, }, shadow = { enabled = prefs.get("shadowEnabled", true), range = prefs.get("shadowRange", 20), render_power = prefs.getInt("shadowRenderPower", 3), sharp = prefs.get("shadowSharp", false), color = "rgba(15161eee)", -- Deliberately not a setting: a two-axis offset needs a control we -- do not have, and a slider bound to half a value is worse than -- leaving it alone. SystemSettings understands the vec2 shape -- already, so adding it later is only a matter of the widget. offset = { 0, 4 }, scale = 1.0, }, -- New in 0.56. Kept deliberately faint: in this direction the gradient -- border is the signature, and a strong halo would compete with it. -- This is just enough to lift the focused window off the wallpaper. -- Derived from the same accent as active_border above, not hardcoded, -- so the halo never disagrees with the border it surrounds. glow = { enabled = prefs.get("glowEnabled", true), range = prefs.get("glowRange", 8), render_power = 2, color = "rgba(" .. accentStart .. "33)", color_inactive = "rgba(00000000)", }, -- Off: costs real frame time and reads as smeary on a 60Hz panel. motion_blur = { enabled = false }, }, animations = { enabled = prefs.get("animationsEnabled", true) }, dwindle = { -- Keep the split orientation a window was created with. Closest match -- to how the Forge extension behaved on GNOME. preserve_split = prefs.get("preserveSplit", true), smart_resizing = true, }, -- Only in effect when the tiling layout is "master". Panama ships dwindle, -- but Settings offers master as a choice, and a layout you can select and -- cannot configure is barely a choice at all. master = { mfact = prefs.get("masterFactor", 0.55), orientation = prefs.get("masterOrientation", "left"), new_status = prefs.get("masterNewStatus", "slave"), new_on_top = prefs.get("masterNewOnTop", false), }, misc = { force_default_wallpaper = 0, -- Stored as "show the logo / show the splash" and written as Hyprland's -- `disable_*`, matching the `invert` flag on these entries in -- PreferenceSchema so both sides agree about which way round they are. disable_hyprland_logo = not prefs.get("hyprlandLogo", false), disable_splash_rendering = not prefs.get("hyprlandSplash", false), -- Keep native Wayland selection paste and GTK's matching preference -- in lockstep. DesktopStyle applies the GTK half only after this value -- has been read back and stored by SystemSettings. middle_click_paste = prefs.get("middleClickPaste", true), -- Same setting as Theme.fontFamily in the shell. If only the QML side -- followed the preference, the compositor and the shell would disagree -- about the interface font and nobody would notice until a tooltip -- rendered in a different typeface. font_family = prefs.get("interfaceFont", "Adwaita Sans"), -- Variable refresh rate. 3 = enable only for fullscreen windows whose -- content type is "video" or "game" -- the tag rules.lua applies to -- games. Keeps VRR off the desktop, where it causes visible flicker. vrr = prefs.getInt("vrrPolicy", 3), -- Blur behind the lock screen. session_lock_blur = true, -- Let a crashed lock screen be recovered rather than stranding you. allow_session_lock_restore = true, -- Don't let apps steal focus by shouting; matches GNOME's behaviour. focus_on_activate = false, }, render = { -- Colour management is stable and on by default in 0.56. cm_enabled = true, -- 1 = automatically flip the monitor into HDR for fullscreen content -- that asks for it, and back out afterwards. This is how games get HDR -- without the desktop paying the screencopy cost. See monitors.lua. cm_auto_hdr = prefs.getInt("autoHdr", 1), -- 2 = direct scanout only for windows tagged content = "game" -- (set by the rules in rules.lua). Bypasses compositing for real -- fullscreen games. direct_scanout = prefs.getInt("directScanoutPolicy", 2), }, cursor = { -- 2 = auto. Disables the hardware cursor when tearing, which is what -- keeps the cursor from ghosting in games. no_hardware_cursors = 2, -- Don't let the cursor break VRR on content tagged as a game. no_break_fs_vrr = 2, min_refresh_rate = 48, -- Pull the cursor theme from gsettings so GTK apps and Hyprland agree. sync_gsettings_theme = true, -- Fade the cursor out after 4s of no movement, like GNOME does. inactive_timeout = prefs.get("cursorInactiveTimeout", 4), }, ecosystem = { no_update_news = not prefs.get("hyprlandUpdateNews", false), no_donation_nag = not prefs.get("hyprlandDonationNag", false), }, xwayland = { -- Render X11 apps at 1:1 device pixels so they're sharp rather than -- bilinearly upscaled. See the note in README.md about the unavoidable -- sizing tradeoff at 1.5x. force_zero_scaling = true, }, quirks = { -- 2 = report HDR as preferred to gamescope only. Stops gamescope from -- white-screening when it expects HDR to already be live. prefer_hdr = 2, }, }) -- ── Motion ────────────────────────────────────────────────────────────────── -- speed is in deciseconds: speed = 4 means 400ms. Higher = slower. hl.curve("easeOutQuint", { type = "bezier", points = { { 0.23, 1 }, { 0.32, 1 } } }) hl.curve("almostLinear", { type = "bezier", points = { { 0.5, 0.5 }, { 0.75, 1 } } }) hl.curve("quick", { type = "bezier", points = { { 0.15, 0 }, { 0.1, 1 } } }) hl.curve("easy", { type = "spring", mass = 1, stiffness = 238.1191, dampening = 24.21279 }) -- A touch bouncier than "easy"; used for windows appearing. hl.curve("snappy", { type = "spring", mass = 1, stiffness = 320, dampening = 22 }) hl.animation({ leaf = "global", enabled = true, speed = 10, bezier = "default" }) hl.animation({ leaf = "border", enabled = true, speed = 5.39, bezier = "easeOutQuint" }) -- The focus sweep: when a window takes focus, its gradient border rotates into -- place once. Style "once" is load-bearing -- the alternative, "loop", makes -- Hyprland render at full refresh rate forever, even when no border is visible. -- That is exactly the idle GPU cost this config exists to avoid, so: never loop. hl.animation({ leaf = "borderangle", enabled = true, speed = 6, bezier = "easeOutQuint", style = "once" }) hl.animation({ leaf = "windows", enabled = true, speed = 4.79, spring = "easy" }) hl.animation({ leaf = "windowsIn", enabled = true, speed = 4.1, spring = "snappy", style = "popin 90%" }) hl.animation({ leaf = "windowsOut", enabled = true, speed = 1.49, bezier = "almostLinear", style = "popin 90%" }) hl.animation({ leaf = "fade", enabled = true, speed = 3.03, bezier = "quick" }) -- Layer surfaces = the Quickshell bar, dock, popovers and overlays. hl.animation({ leaf = "layers", enabled = true, speed = 3.81, bezier = "easeOutQuint" }) hl.animation({ leaf = "layersIn", enabled = true, speed = 3, bezier = "easeOutQuint", style = "fade" }) hl.animation({ leaf = "layersOut", enabled = true, speed = 2, bezier = "almostLinear", style = "fade" }) -- Workspace switching: horizontal slide, matching Alt+H / Alt+L direction. hl.animation({ leaf = "workspaces", enabled = true, speed = 2.5, bezier = "easeOutQuint", style = "slidefade 15%" }) hl.animation({ leaf = "zoomFactor", enabled = true, speed = 7, bezier = "quick" }) return true