auto-sync: 2026-09-05 11:32:32
This commit is contained in:
7 files changed
+704
-1
No files matched your search
@@ -0,0 +1,31 @@
|
||||
-- colors.lua (Catppuccin Latte palette)
|
||||
local colors = {
|
||||
rosewater = "rgb(dc8a78)",
|
||||
flamingo = "rgb(dd7878)",
|
||||
pink = "rgb(ea76cb)",
|
||||
mauve = "rgb(8839ef)",
|
||||
red = "rgb(d20f39)",
|
||||
maroon = "rgb(e64553)",
|
||||
peach = "rgb(fe640b)",
|
||||
yellow = "rgb(df8e1d)",
|
||||
green = "rgb(40a02b)",
|
||||
teal = "rgb(179299)",
|
||||
sky = "rgb(04a5e5)",
|
||||
sapphire = "rgb(209fb5)",
|
||||
blue = "rgb(1e66f5)",
|
||||
lavender = "rgb(7287fd)",
|
||||
text = "rgb(4c4f69)",
|
||||
subtext1 = "rgb(5c5f77)",
|
||||
subtext0 = "rgb(6c6f85)",
|
||||
overlay2 = "rgb(7c7f93)",
|
||||
overlay1 = "rgb(8c8fa1)",
|
||||
overlay0 = "rgb(9ca0b0)",
|
||||
surface2 = "rgb(acb0be)",
|
||||
surface1 = "rgb(bcc0cc)",
|
||||
surface0 = "rgb(ccd0da)",
|
||||
base = "rgb(eff1f5)",
|
||||
mantle = "rgb(e6e9ef)",
|
||||
crust = "rgb(dce0e8)",
|
||||
}
|
||||
|
||||
return colors
|
||||
@@ -39,6 +39,8 @@ exec-once = hypridle & hyprpaper & dunst & /usr/lib/polkit-gnome/polkit-gnome-au
|
||||
|
||||
exec-once = $HOME/.config/hypr/scripts/waybar.sh
|
||||
|
||||
exec-once = $HOME/.config/hypr/scripts/wallpaper-power-monitor.sh
|
||||
|
||||
exec-once = sleep 5 && darkman run
|
||||
|
||||
exec-once = thunderbird
|
||||
|
||||
@@ -0,0 +1,257 @@
|
||||
-- Hyprland Lua Configuration
|
||||
-- Migrated from hyprland.conf to official Lua config format for Hyprland 0.56+ / 0.57+
|
||||
|
||||
local colors = require("colors")
|
||||
|
||||
------------------
|
||||
---- MONITORS ----
|
||||
------------------
|
||||
-- See https://wiki.hypr.land/Configuring/Basics/Monitors/
|
||||
hl.monitor({
|
||||
output = "eDP-1",
|
||||
mode = "3072x1920@120",
|
||||
position = "0x0",
|
||||
scale = 1.5,
|
||||
})
|
||||
|
||||
hl.monitor({
|
||||
output = "HEADLESS-2",
|
||||
mode = "1920x1080@60",
|
||||
position = "3072x0",
|
||||
scale = 1.0,
|
||||
})
|
||||
|
||||
-------------------------------
|
||||
---- ENVIRONMENT VARIABLES ----
|
||||
-------------------------------
|
||||
-- See https://wiki.hypr.land/Configuring/Advanced-and-Cool/Environment-variables/
|
||||
hl.env("XCURSOR_THEME", "phinger-cursors-dark")
|
||||
hl.env("HYPRCURSOR_THEME", "phinger-cursors-dark")
|
||||
hl.env("XCURSOR_SIZE", "24")
|
||||
hl.env("QT_QPA_PLATFORMTHEME", "qt6ct")
|
||||
hl.env("QT_STYLE_OVERRIDE", "kvantum")
|
||||
hl.env("GDK_BACKEND", "wayland")
|
||||
hl.env("XDG_DATA_HOME", os.getenv("HOME") .. "/.local/share")
|
||||
|
||||
-------------------
|
||||
---- AUTOSTART ----
|
||||
-------------------
|
||||
-- See https://wiki.hypr.land/Configuring/Basics/Autostart/
|
||||
hl.on("hyprland.start", function ()
|
||||
hl.exec_cmd("dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP")
|
||||
hl.exec_cmd("hypridle & hyprpaper & dunst & /usr/lib/polkit-gnome/polkit-gnome-authentication-agent-1")
|
||||
hl.exec_cmd(os.getenv("HOME") .. "/.config/hypr/scripts/waybar.sh")
|
||||
hl.exec_cmd(os.getenv("HOME") .. "/.config/hypr/scripts/wallpaper-power-monitor.sh")
|
||||
hl.exec_cmd("sleep 5 && darkman run")
|
||||
hl.exec_cmd("thunderbird")
|
||||
end)
|
||||
|
||||
-----------------------
|
||||
---- LOOK AND FEEL ----
|
||||
-----------------------
|
||||
-- Refer to https://wiki.hypr.land/Configuring/Basics/Variables/
|
||||
hl.config({
|
||||
xwayland = {
|
||||
force_zero_scaling = true,
|
||||
},
|
||||
|
||||
general = {
|
||||
gaps_in = 3,
|
||||
gaps_out = 7,
|
||||
border_size = 3,
|
||||
col = {
|
||||
active_border = { colors = { colors.lavender, colors.sapphire }, angle = 45 },
|
||||
inactive_border = colors.surface0,
|
||||
},
|
||||
layout = "dwindle",
|
||||
allow_tearing = false,
|
||||
},
|
||||
|
||||
decoration = {
|
||||
rounding = 8,
|
||||
blur = {
|
||||
enabled = true,
|
||||
size = 16,
|
||||
passes = 2,
|
||||
},
|
||||
shadow = {
|
||||
enabled = true,
|
||||
range = 4,
|
||||
render_power = 3,
|
||||
color = "rgba(1a1a1aee)",
|
||||
},
|
||||
},
|
||||
|
||||
animations = {
|
||||
enabled = true,
|
||||
},
|
||||
|
||||
dwindle = {
|
||||
preserve_split = true,
|
||||
},
|
||||
|
||||
master = {
|
||||
new_status = "master",
|
||||
},
|
||||
|
||||
misc = {
|
||||
force_default_wallpaper = -1,
|
||||
},
|
||||
|
||||
input = {
|
||||
kb_layout = "us,ru",
|
||||
kb_variant = "",
|
||||
kb_model = "",
|
||||
kb_options = "grp:win_space_toggle",
|
||||
kb_rules = "",
|
||||
|
||||
follow_mouse = 1,
|
||||
|
||||
sensitivity = 0,
|
||||
|
||||
touchpad = {
|
||||
natural_scroll = true,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
--------------------
|
||||
---- ANIMATIONS ----
|
||||
--------------------
|
||||
hl.curve("myBezier", { type = "bezier", points = { {0.05, 0.9}, {0.1, 1.05} } })
|
||||
|
||||
hl.animation({ leaf = "windows", enabled = true, speed = 7, bezier = "myBezier" })
|
||||
hl.animation({ leaf = "windowsOut", enabled = true, speed = 7, bezier = "default", style = "popin 80%" })
|
||||
hl.animation({ leaf = "border", enabled = true, speed = 10, bezier = "default" })
|
||||
hl.animation({ leaf = "borderangle", enabled = true, speed = 8, bezier = "default" })
|
||||
hl.animation({ leaf = "fade", enabled = true, speed = 7, bezier = "default" })
|
||||
hl.animation({ leaf = "workspaces", enabled = true, speed = 6, bezier = "default" })
|
||||
|
||||
------------------
|
||||
---- GESTURES ----
|
||||
------------------
|
||||
hl.gesture({
|
||||
fingers = 3,
|
||||
direction = "horizontal",
|
||||
action = "workspace",
|
||||
})
|
||||
|
||||
-----------------
|
||||
---- DEVICES ----
|
||||
-----------------
|
||||
hl.device({
|
||||
name = "epic-mouse-v1",
|
||||
sensitivity = -0.5,
|
||||
})
|
||||
|
||||
----------------------
|
||||
---- WINDOW RULES ----
|
||||
----------------------
|
||||
-- Global Maximize Event Suppression
|
||||
hl.window_rule({
|
||||
name = "suppress-maximize-events",
|
||||
match = { class = ".*" },
|
||||
suppress_event = "maximize",
|
||||
})
|
||||
|
||||
-- Idle Inhibition
|
||||
hl.window_rule({
|
||||
name = "idle-inhibit-fullscreen",
|
||||
match = { fullscreen = true },
|
||||
idle_inhibit = "fullscreen",
|
||||
})
|
||||
|
||||
-- Firefox Picture-in-Picture
|
||||
hl.window_rule({
|
||||
name = "firefox-pip-overlay",
|
||||
match = {
|
||||
class = "(f|F)irefox.*",
|
||||
title = "Picture-in-Picture",
|
||||
},
|
||||
float = true,
|
||||
pin = true,
|
||||
suppress_event = "fullscreen maximize",
|
||||
})
|
||||
|
||||
---------------------
|
||||
---- KEYBINDINGS ----
|
||||
---------------------
|
||||
local mainMod = "SUPER"
|
||||
local terminal = "ghostty"
|
||||
local browser = "firefox-beta"
|
||||
local explorer = "nautilus"
|
||||
local launcher = "sh " .. os.getenv("HOME") .. "/.config/rofi/bin/launcher"
|
||||
local powermenu = "sh " .. os.getenv("HOME") .. "/.config/rofi/bin/powermenu"
|
||||
local screenshot = "sh " .. os.getenv("HOME") .. "/.config/rofi/bin/screenshot"
|
||||
|
||||
-- App Launchers & Window Management
|
||||
hl.bind(mainMod .. " + Q", hl.dsp.exec_cmd(terminal))
|
||||
hl.bind(mainMod .. " + W", hl.dsp.exec_cmd(browser))
|
||||
hl.bind(mainMod .. " + E", hl.dsp.exec_cmd(explorer))
|
||||
hl.bind(mainMod .. " + R", hl.dsp.exec_cmd(launcher))
|
||||
hl.bind(mainMod .. " + M", hl.dsp.exec_cmd(powermenu))
|
||||
hl.bind(mainMod .. " + print", hl.dsp.exec_cmd(screenshot))
|
||||
hl.bind(mainMod .. " + C", hl.dsp.window.close())
|
||||
hl.bind(mainMod .. " + ESCAPE", hl.dsp.exit())
|
||||
hl.bind(mainMod .. " + D", hl.dsp.window.pseudo())
|
||||
hl.bind(mainMod .. " + F", hl.dsp.window.float({ action = "toggle" }))
|
||||
hl.bind(mainMod .. " + S", hl.dsp.layout("togglesplit"))
|
||||
hl.bind(mainMod .. " + V", hl.dsp.window.fullscreen())
|
||||
|
||||
-- Focus with arrow keys
|
||||
hl.bind(mainMod .. " + left", hl.dsp.focus({ direction = "left" }))
|
||||
hl.bind(mainMod .. " + right", hl.dsp.focus({ direction = "right" }))
|
||||
hl.bind(mainMod .. " + up", hl.dsp.focus({ direction = "up" }))
|
||||
hl.bind(mainMod .. " + down", hl.dsp.focus({ direction = "down" }))
|
||||
|
||||
-- Focus with HJKL
|
||||
hl.bind(mainMod .. " + H", hl.dsp.focus({ direction = "left" }))
|
||||
hl.bind(mainMod .. " + L", hl.dsp.focus({ direction = "right" }))
|
||||
hl.bind(mainMod .. " + K", hl.dsp.focus({ direction = "up" }))
|
||||
hl.bind(mainMod .. " + J", hl.dsp.focus({ direction = "down" }))
|
||||
|
||||
-- Workspaces 1-10
|
||||
for i = 1, 10 do
|
||||
local key = i % 10 -- 10 maps to key 0
|
||||
hl.bind(mainMod .. " + " .. key, hl.dsp.focus({ workspace = i }))
|
||||
hl.bind(mainMod .. " + SHIFT + " .. key, hl.dsp.window.move({ workspace = i }))
|
||||
end
|
||||
|
||||
-- Relative workspace movement
|
||||
hl.bind(mainMod .. " + SHIFT + N", hl.dsp.window.move({ workspace = "e+1" }))
|
||||
hl.bind(mainMod .. " + SHIFT + P", hl.dsp.window.move({ workspace = "e-1" }))
|
||||
|
||||
hl.bind(mainMod .. " + N", hl.dsp.focus({ workspace = "e+1" }))
|
||||
hl.bind(mainMod .. " + P", hl.dsp.focus({ workspace = "e-1" }))
|
||||
|
||||
-- Special workspace (scratchpad)
|
||||
hl.bind(mainMod .. " + A", hl.dsp.workspace.toggle_special("magic"))
|
||||
hl.bind(mainMod .. " + SHIFT + A", hl.dsp.window.move({ workspace = "special:magic" }))
|
||||
|
||||
-- Mouse scroll through workspaces
|
||||
hl.bind(mainMod .. " + mouse_down", hl.dsp.focus({ workspace = "e+1" }))
|
||||
hl.bind(mainMod .. " + mouse_up", hl.dsp.focus({ workspace = "e-1" }))
|
||||
|
||||
-- Mouse drag/resize
|
||||
hl.bind(mainMod .. " + mouse:272", hl.dsp.window.drag(), { mouse = true })
|
||||
hl.bind(mainMod .. " + mouse:273", hl.dsp.window.resize(), { mouse = true })
|
||||
|
||||
-- Brightness control
|
||||
hl.bind("XF86MonBrightnessUp", hl.dsp.exec_cmd("brightnessctl s +5%"))
|
||||
hl.bind("XF86MonBrightnessDown", hl.dsp.exec_cmd("brightnessctl s 5%-"))
|
||||
|
||||
-- Volume and Media Control
|
||||
hl.bind("XF86AudioRaiseVolume", hl.dsp.exec_cmd("pamixer -i 5"))
|
||||
hl.bind("XF86AudioLowerVolume", hl.dsp.exec_cmd("pamixer -d 5"))
|
||||
hl.bind("XF86AudioMicMute", hl.dsp.exec_cmd("pamixer --default-source -t"))
|
||||
hl.bind("XF86AudioMute", hl.dsp.exec_cmd("pamixer -t"))
|
||||
hl.bind("XF86AudioPlay", hl.dsp.exec_cmd("playerctl play-pause"))
|
||||
hl.bind("XF86AudioPause", hl.dsp.exec_cmd("playerctl play-pause"))
|
||||
hl.bind("XF86AudioNext", hl.dsp.exec_cmd("playerctl next"))
|
||||
hl.bind("XF86AudioPrev", hl.dsp.exec_cmd("playerctl previous"))
|
||||
|
||||
-- Color picker
|
||||
hl.bind(mainMod .. " + X", hl.dsp.exec_cmd("hyprpicker -a"))
|
||||
|
||||
-- Screenshot
|
||||
hl.bind("print", hl.dsp.exec_cmd("grimblast --notify copysave screen \"$HOME/Pictures/Screenshots/$(date +'%Y-%m-%d-%H%M%S.%3N.png')\""))
|
||||
Executable
+363
@@ -0,0 +1,363 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Hyprland Power & Profile Daemon
|
||||
Seamlessly synchronizes Hyprland display settings and visuals with:
|
||||
1. Power profile changes via net.hadess.PowerProfiles (Waybar / TuneD / PPD)
|
||||
2. Power supply events (AC vs Battery) via org.freedesktop.UPower / sysfs
|
||||
|
||||
Profiles:
|
||||
- power-saver:
|
||||
• Display: 60Hz (3072x1920@60) - drops panel & GPU power by ~2-4W
|
||||
• Compositor: blur & shadows disabled (reduces GPU render passes)
|
||||
• Animated wallpaper: disabled (static hyprpaper wallpaper)
|
||||
- balanced:
|
||||
• Display: 120Hz (3072x1920@120) - ultra-smooth scrolling
|
||||
• Compositor: blur & shadows enabled
|
||||
• Animated wallpaper: active when on AC, static when on battery
|
||||
- performance:
|
||||
• Display: 120Hz (3072x1920@120)
|
||||
• Compositor: blur & shadows enabled
|
||||
• Animated wallpaper: active when on AC, static when on battery
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
import signal
|
||||
import gi
|
||||
|
||||
gi.require_version("Gio", "2.0")
|
||||
gi.require_version("GLib", "2.0")
|
||||
from gi.repository import Gio, GLib
|
||||
|
||||
LOCKFILE = "/tmp/hypr-power-daemon.lock"
|
||||
LOGFILE = "/tmp/hypr-power-daemon.log"
|
||||
|
||||
|
||||
def log(msg):
|
||||
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
line = f"[{timestamp}] {msg}"
|
||||
print(line, flush=True)
|
||||
try:
|
||||
with open(LOGFILE, "a") as f:
|
||||
f.write(line + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def notify(title, message, icon="battery-symbolic", urgency="normal"):
|
||||
try:
|
||||
subprocess.Popen(
|
||||
[
|
||||
"notify-send",
|
||||
"-a", "Power Management",
|
||||
"-i", icon,
|
||||
"-u", urgency,
|
||||
title,
|
||||
message,
|
||||
],
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
except Exception as e:
|
||||
log(f"Notification error: {e}")
|
||||
|
||||
|
||||
def hyprctl(args):
|
||||
try:
|
||||
cmd = ["hyprctl"] + args
|
||||
res = subprocess.run(cmd, capture_output=True, text=True, timeout=3)
|
||||
return res.returncode == 0
|
||||
except Exception as e:
|
||||
log(f"hyprctl error with args {args}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_current_theme_mode():
|
||||
cache_file = os.path.expanduser("~/.cache/hypr_wallpaper_mode")
|
||||
if os.path.exists(cache_file):
|
||||
try:
|
||||
with open(cache_file) as f:
|
||||
mode = f.read().strip()
|
||||
if mode in ("dark", "light"):
|
||||
return mode
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
res = subprocess.run(["darkman", "get"], capture_output=True, text=True, timeout=2)
|
||||
if res.returncode == 0:
|
||||
mode = res.stdout.strip()
|
||||
if mode in ("dark", "light"):
|
||||
return mode
|
||||
except Exception:
|
||||
pass
|
||||
return "dark"
|
||||
|
||||
|
||||
def set_animated_wallpaper(enabled):
|
||||
if not enabled:
|
||||
try:
|
||||
subprocess.run(["pkill", "-f", "linux-wallpaperengine"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
mode = get_current_theme_mode()
|
||||
script = os.path.expanduser("~/.config/hypr/scripts/wallpaper-engine-switcher.sh")
|
||||
if os.path.isfile(script) and os.access(script, os.X_OK):
|
||||
subprocess.Popen([script, mode], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||
|
||||
|
||||
class PowerDaemon:
|
||||
def __init__(self):
|
||||
self.bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)
|
||||
self.active_profile = self.get_active_profile()
|
||||
self.on_battery = self.get_on_battery()
|
||||
|
||||
log(f"Initial state: profile={self.active_profile}, on_battery={self.on_battery}")
|
||||
|
||||
# Subscribe to PowerProfiles PropertiesChanged (sender=None matches any unique name)
|
||||
self.bus.signal_subscribe(
|
||||
None,
|
||||
"org.freedesktop.DBus.Properties",
|
||||
"PropertiesChanged",
|
||||
"/net/hadess/PowerProfiles",
|
||||
None,
|
||||
Gio.DBusSignalFlags.NONE,
|
||||
self.on_pp_signal,
|
||||
None,
|
||||
)
|
||||
|
||||
# Subscribe to UPower PropertiesChanged
|
||||
self.bus.signal_subscribe(
|
||||
None,
|
||||
"org.freedesktop.DBus.Properties",
|
||||
"PropertiesChanged",
|
||||
"/org/freedesktop/UPower",
|
||||
None,
|
||||
Gio.DBusSignalFlags.NONE,
|
||||
self.on_upower_signal,
|
||||
None,
|
||||
)
|
||||
|
||||
# Apply initial tuning
|
||||
self.apply_tuning(send_notification=False)
|
||||
|
||||
# Periodic check every 15 seconds
|
||||
GLib.timeout_add_seconds(15, self.periodic_check)
|
||||
|
||||
def get_active_profile(self):
|
||||
try:
|
||||
proxy = Gio.DBusProxy.new_sync(
|
||||
self.bus,
|
||||
Gio.DBusProxyFlags.NONE,
|
||||
None,
|
||||
"net.hadess.PowerProfiles",
|
||||
"/net/hadess/PowerProfiles",
|
||||
"org.freedesktop.DBus.Properties",
|
||||
None,
|
||||
)
|
||||
val = proxy.call_sync(
|
||||
"Get",
|
||||
GLib.Variant("(ss)", ("net.hadess.PowerProfiles", "ActiveProfile")),
|
||||
Gio.DBusCallFlags.NONE,
|
||||
-1,
|
||||
None,
|
||||
)
|
||||
return val.unpack()[0]
|
||||
except Exception as e:
|
||||
log(f"Error fetching ActiveProfile: {e}")
|
||||
return "balanced"
|
||||
|
||||
def get_on_battery(self):
|
||||
try:
|
||||
proxy = Gio.DBusProxy.new_sync(
|
||||
self.bus,
|
||||
Gio.DBusProxyFlags.NONE,
|
||||
None,
|
||||
"org.freedesktop.UPower",
|
||||
"/org/freedesktop/UPower",
|
||||
"org.freedesktop.DBus.Properties",
|
||||
None,
|
||||
)
|
||||
val = proxy.call_sync(
|
||||
"Get",
|
||||
GLib.Variant("(ss)", ("org.freedesktop.UPower", "OnBattery")),
|
||||
Gio.DBusCallFlags.NONE,
|
||||
-1,
|
||||
None,
|
||||
)
|
||||
return val.unpack()[0]
|
||||
except Exception as e:
|
||||
log(f"Error fetching OnBattery from UPower: {e}")
|
||||
return self.check_sysfs_battery()
|
||||
|
||||
def check_sysfs_battery(self):
|
||||
try:
|
||||
for p in os.listdir("/sys/class/power_supply"):
|
||||
type_path = os.path.join("/sys/class/power_supply", p, "type")
|
||||
if os.path.exists(type_path):
|
||||
with open(type_path) as f:
|
||||
if f.read().strip() == "Mains":
|
||||
online_path = os.path.join("/sys/class/power_supply", p, "online")
|
||||
if os.path.exists(online_path):
|
||||
with open(online_path) as f2:
|
||||
if f2.read().strip() == "1":
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
def on_pp_signal(self, conn, sender, path, iface, signal, params, user_data):
|
||||
unpacked = params.unpack()
|
||||
if len(unpacked) >= 2 and isinstance(unpacked[1], dict):
|
||||
changed_props = unpacked[1]
|
||||
if "ActiveProfile" in changed_props:
|
||||
new_profile = changed_props["ActiveProfile"]
|
||||
if new_profile != self.active_profile:
|
||||
log(f"Power profile changed: {self.active_profile} -> {new_profile}")
|
||||
self.active_profile = new_profile
|
||||
self.apply_tuning(send_notification=True)
|
||||
|
||||
def on_upower_signal(self, conn, sender, path, iface, signal, params, user_data):
|
||||
unpacked = params.unpack()
|
||||
if len(unpacked) >= 2 and isinstance(unpacked[1], dict):
|
||||
changed_props = unpacked[1]
|
||||
if "OnBattery" in changed_props:
|
||||
new_on_bat = changed_props["OnBattery"]
|
||||
if new_on_bat != self.on_battery:
|
||||
log(f"Power source changed: on_battery={new_on_bat}")
|
||||
self.on_battery = new_on_bat
|
||||
self.handle_power_source_change()
|
||||
|
||||
def handle_power_source_change(self):
|
||||
if self.on_battery:
|
||||
set_animated_wallpaper(False)
|
||||
notify(
|
||||
"Running on Battery",
|
||||
"Switched to battery power. Animated wallpaper paused.",
|
||||
icon="battery-good-symbolic",
|
||||
)
|
||||
else:
|
||||
if self.active_profile in ("balanced", "performance"):
|
||||
set_animated_wallpaper(True)
|
||||
notify(
|
||||
"Connected to AC Power",
|
||||
"Charging. Full power available.",
|
||||
icon="ac-adapter-symbolic",
|
||||
)
|
||||
|
||||
def apply_tuning(self, send_notification=True):
|
||||
profile = self.active_profile or "balanced"
|
||||
|
||||
if profile == "power-saver":
|
||||
# 1. 60Hz display refresh
|
||||
hyprctl(["keyword", "monitor", "eDP-1,3072x1920@60,0x0,1.5"])
|
||||
# 2. Disable blur & shadows
|
||||
hyprctl(["keyword", "decoration:blur:enabled", "false"])
|
||||
hyprctl(["keyword", "decoration:shadow:enabled", "false"])
|
||||
# 3. Disable animated wallpaper
|
||||
set_animated_wallpaper(False)
|
||||
|
||||
if send_notification:
|
||||
notify(
|
||||
"Power Saver Active",
|
||||
"• Display: 60Hz refresh rate\n• Compositor: blur & shadows disabled\n• SoC: max efficiency (15W cap)",
|
||||
icon="battery-good-symbolic",
|
||||
)
|
||||
|
||||
elif profile == "performance":
|
||||
# 1. 120Hz display refresh
|
||||
hyprctl(["keyword", "monitor", "eDP-1,3072x1920@120,0x0,1.5"])
|
||||
# 2. Enable blur & shadows
|
||||
hyprctl(["keyword", "decoration:blur:enabled", "true"])
|
||||
hyprctl(["keyword", "decoration:shadow:enabled", "true"])
|
||||
# 3. Animated wallpaper on AC
|
||||
if not self.on_battery:
|
||||
set_animated_wallpaper(True)
|
||||
else:
|
||||
set_animated_wallpaper(False)
|
||||
|
||||
if send_notification:
|
||||
notify(
|
||||
"Performance Mode Active",
|
||||
"• Display: 120Hz refresh rate\n• Compositor: full visual effects\n• SoC: 54W+ turbo boost unlocked",
|
||||
icon="power-profile-performance-symbolic",
|
||||
)
|
||||
|
||||
else: # balanced
|
||||
# 1. 120Hz display refresh
|
||||
hyprctl(["keyword", "monitor", "eDP-1,3072x1920@120,0x0,1.5"])
|
||||
# 2. Enable blur & shadows
|
||||
hyprctl(["keyword", "decoration:blur:enabled", "true"])
|
||||
hyprctl(["keyword", "decoration:shadow:enabled", "true"])
|
||||
# 3. Animated wallpaper on AC
|
||||
if not self.on_battery:
|
||||
set_animated_wallpaper(True)
|
||||
else:
|
||||
set_animated_wallpaper(False)
|
||||
|
||||
if send_notification:
|
||||
notify(
|
||||
"Balanced Mode Active",
|
||||
"• Display: 120Hz refresh rate\n• Compositor: full visual effects\n• SoC: dynamic balanced scaling",
|
||||
icon="battery-symbolic",
|
||||
)
|
||||
|
||||
def periodic_check(self):
|
||||
cur_prof = self.get_active_profile()
|
||||
if cur_prof != self.active_profile:
|
||||
log(f"Periodic check detected profile change: {self.active_profile} -> {cur_prof}")
|
||||
self.active_profile = cur_prof
|
||||
self.apply_tuning(send_notification=True)
|
||||
|
||||
cur_bat = self.get_on_battery()
|
||||
if cur_bat != self.on_battery:
|
||||
log(f"Periodic check detected power source change: {self.on_battery} -> {cur_bat}")
|
||||
self.on_battery = cur_bat
|
||||
self.handle_power_source_change()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def acquire_lock():
|
||||
if os.path.exists(LOCKFILE):
|
||||
try:
|
||||
with open(LOCKFILE) as f:
|
||||
pid = int(f.read().strip())
|
||||
os.kill(pid, 0)
|
||||
print(f"Daemon already running with PID {pid}. Exiting.")
|
||||
sys.exit(0)
|
||||
except (ValueError, OSError):
|
||||
pass
|
||||
with open(LOCKFILE, "w") as f:
|
||||
f.write(str(os.getpid()))
|
||||
|
||||
|
||||
def cleanup_lock(*args):
|
||||
try:
|
||||
os.remove(LOCKFILE)
|
||||
except Exception:
|
||||
pass
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
def main():
|
||||
acquire_lock()
|
||||
signal.signal(signal.SIGINT, cleanup_lock)
|
||||
signal.signal(signal.SIGTERM, cleanup_lock)
|
||||
|
||||
log("Starting Hyprland Power & Profile Daemon...")
|
||||
daemon = PowerDaemon()
|
||||
|
||||
loop = GLib.MainLoop()
|
||||
try:
|
||||
loop.run()
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
finally:
|
||||
cleanup_lock()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -11,6 +11,42 @@ if [[ "$MODE" != "dark" && "$MODE" != "light" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$HOME/.cache"
|
||||
echo "$MODE" > "$HOME/.cache/hypr_wallpaper_mode"
|
||||
|
||||
is_on_ac() {
|
||||
local has_battery=0
|
||||
for b in /sys/class/power_supply/*; do
|
||||
if [[ -f "$b/type" && "$(< "$b/type")" == "Battery" ]]; then
|
||||
has_battery=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# If no battery in system (desktop PC), treat as always on AC
|
||||
if [[ $has_battery -eq 0 ]]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check for Mains power supplies online
|
||||
for p in /sys/class/power_supply/*; do
|
||||
if [[ -f "$p/type" && "$(< "$p/type")" == "Mains" ]]; then
|
||||
if [[ -f "$p/online" && "$(< "$p/online")" -eq 1 ]]; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Fallback to upower if available
|
||||
if command -v upower >/dev/null 2>&1; then
|
||||
if upower -d 2>/dev/null | grep -q "on-battery:\s*no"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
MONITORS=($(hyprctl -i 0 monitors | grep Monitor | awk '{print $2}'))
|
||||
|
||||
# --- 1. Static Wallpaper Fallback / Base Layer ---
|
||||
@@ -29,12 +65,19 @@ if [[ -d "$STATIC_DIR" ]]; then
|
||||
fi
|
||||
|
||||
# --- 2. Video Wallpaper ---
|
||||
# If running on battery, do not start linux-wallpaperengine to conserve power.
|
||||
if ! is_on_ac; then
|
||||
echo "Running on battery power. Keeping static wallpaper and disabling video wallpaper."
|
||||
pkill -f linux-wallpaperengine || true
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CONFIG_FILE="$HOME/.config/hypr/wallpapers/${MODE}_scenes.txt"
|
||||
|
||||
# If linux-wallpaperengine isn't installed or config is missing/empty, we gracefully stop here
|
||||
# The static fallback will remain visible.
|
||||
if ! command -v linux-wallpaperengine &> /dev/null || [[ ! -s "$CONFIG_FILE" ]]; then
|
||||
pkill -x linux-wallpaperengine # clean up any running engines just in case
|
||||
pkill -f linux-wallpaperengine # clean up any running engines just in case
|
||||
exit 0
|
||||
fi
|
||||
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env bash
|
||||
# Wrapper to launch hypr-power-daemon.py
|
||||
exec python3 "$HOME/.config/hypr/scripts/hypr-power-daemon.py"
|
||||
@@ -20,3 +20,7 @@
|
||||
helper = /usr/local/share/gcm-core/git-credential-manager
|
||||
[credential "https://dev.azure.com"]
|
||||
useHttpPath = true
|
||||
[credential "https://github.com"]
|
||||
helper = !/usr/bin/gh auth git-credential
|
||||
[credential "https://gist.github.com"]
|
||||
helper = !/usr/bin/gh auth git-credential
|
||||
Reference in new issue
Block a user