[NixOS] Force Brightness management to work
This commit is contained in:
@@ -1059,8 +1059,8 @@ addKeys conf@XConfig { modMask = modm } =
|
|||||||
, ((hyper .|. shiftMask, xK_q), spawn "toggle_mute_current_window.sh")
|
, ((hyper .|. shiftMask, xK_q), spawn "toggle_mute_current_window.sh")
|
||||||
, ((hctrl, xK_q), spawn "toggle_mute_current_window.sh only")
|
, ((hctrl, xK_q), spawn "toggle_mute_current_window.sh only")
|
||||||
|
|
||||||
, ((0, xF86XK_MonBrightnessUp), spawn "brightness.sh +5")
|
, ((0, xF86XK_MonBrightnessUp), spawn "brightness.sh up")
|
||||||
, ((0, xF86XK_MonBrightnessDown), spawn "brightness.sh -5")
|
, ((0, xF86XK_MonBrightnessDown), spawn "brightness.sh down")
|
||||||
|
|
||||||
] ++
|
] ++
|
||||||
|
|
||||||
|
|||||||
164
dotfiles/lib/bin/brightness.hs
Executable file
164
dotfiles/lib/bin/brightness.hs
Executable file
@@ -0,0 +1,164 @@
|
|||||||
|
#!/usr/bin/env runhaskell
|
||||||
|
|
||||||
|
import System.Environment (getArgs)
|
||||||
|
import System.Process
|
||||||
|
import System.Exit
|
||||||
|
import Data.List (isPrefixOf, isSuffixOf)
|
||||||
|
import Data.Maybe (mapMaybe, fromMaybe)
|
||||||
|
import Text.Read (readMaybe)
|
||||||
|
import Control.Monad (forM, when)
|
||||||
|
import Data.Time.Clock
|
||||||
|
import Data.Time.Format
|
||||||
|
import System.IO
|
||||||
|
|
||||||
|
-- Types
|
||||||
|
data Device = Device { deviceName :: String, deviceBrightness :: Int }
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
data BrightnessCommand = Absolute Int | Increase Int | Decrease Int | Query
|
||||||
|
deriving (Show)
|
||||||
|
|
||||||
|
-- Logging
|
||||||
|
logAction :: String -> IO ()
|
||||||
|
logAction arg = do
|
||||||
|
time <- getCurrentTime
|
||||||
|
let timeStr = formatTime defaultTimeLocale "%Y-%m-%d %H:%M:%S" time
|
||||||
|
ppid <- readProcess "sh" ["-c", "ps -p $PPID -o comm= 2>/dev/null || echo unknown"] ""
|
||||||
|
pwd <- readProcess "pwd" [] ""
|
||||||
|
let logEntry = "[" ++ timeStr ++ "] Called with: '" ++ arg ++ "' | Parent: " ++
|
||||||
|
filter (/= '\n') ppid ++ " | PWD: " ++ filter (/= '\n') pwd ++ "\n"
|
||||||
|
appendFile "/tmp/brightness.log" logEntry
|
||||||
|
|
||||||
|
-- Parse command line argument
|
||||||
|
parseArg :: String -> BrightnessCommand
|
||||||
|
parseArg "" = Query
|
||||||
|
parseArg ('+':rest) = case readMaybe rest of
|
||||||
|
Just n -> Increase n
|
||||||
|
Nothing -> Query
|
||||||
|
parseArg ('-':rest) = case readMaybe rest of
|
||||||
|
Just n -> Decrease n
|
||||||
|
Nothing -> Query
|
||||||
|
parseArg s = case readMaybe s of
|
||||||
|
Just n -> Absolute n
|
||||||
|
Nothing -> Query
|
||||||
|
|
||||||
|
-- Get list of backlight devices
|
||||||
|
getBacklightDevices :: IO [String]
|
||||||
|
getBacklightDevices = do
|
||||||
|
output <- readProcess "brightnessctl" ["--list"] ""
|
||||||
|
return $ mapMaybe extractDevice (lines output)
|
||||||
|
where
|
||||||
|
extractDevice line
|
||||||
|
| "Device '" `isInfixOf` line && "class 'backlight'" `isInfixOf` line =
|
||||||
|
let afterDevice = drop (length "Device '") line
|
||||||
|
deviceName = takeWhile (/= '\'') afterDevice
|
||||||
|
in if null deviceName then Nothing else Just deviceName
|
||||||
|
| otherwise = Nothing
|
||||||
|
|
||||||
|
-- Get brightness percentage for a device
|
||||||
|
getDeviceBrightness :: String -> IO (Maybe Int)
|
||||||
|
getDeviceBrightness device = do
|
||||||
|
output <- readProcess "brightnessctl" ["-d", device] ""
|
||||||
|
return $ extractPercentage output
|
||||||
|
where
|
||||||
|
extractPercentage s = case dropWhile (/= '(') s of
|
||||||
|
('(':rest) -> readMaybe (takeWhile (/= '%') rest)
|
||||||
|
_ -> Nothing
|
||||||
|
|
||||||
|
-- Set brightness for a device
|
||||||
|
setDeviceBrightness :: String -> String -> IO ()
|
||||||
|
setDeviceBrightness device cmd = do
|
||||||
|
_ <- readProcess "brightnessctl" ["-d", device, "set", cmd] ""
|
||||||
|
return ()
|
||||||
|
|
||||||
|
-- Build brightness command string
|
||||||
|
buildCommand :: BrightnessCommand -> String
|
||||||
|
buildCommand (Absolute n) = show n ++ "%"
|
||||||
|
buildCommand (Increase n) = show n ++ "%+"
|
||||||
|
buildCommand (Decrease n) = show n ++ "%-"
|
||||||
|
buildCommand Query = ""
|
||||||
|
|
||||||
|
-- Apply brightness change to all devices
|
||||||
|
applyBrightness :: BrightnessCommand -> IO ()
|
||||||
|
applyBrightness Query = return ()
|
||||||
|
applyBrightness cmd = do
|
||||||
|
devices <- getBacklightDevices
|
||||||
|
let cmdStr = buildCommand cmd
|
||||||
|
|
||||||
|
-- Log what we're about to do
|
||||||
|
appendFile "/tmp/brightness.log" $
|
||||||
|
" Applying: " ++ cmdStr ++ " to devices: " ++ show devices ++ "\n"
|
||||||
|
|
||||||
|
-- Apply to all devices
|
||||||
|
mapM_ (\dev -> setDeviceBrightness dev cmdStr) devices
|
||||||
|
|
||||||
|
-- Sync devices if needed
|
||||||
|
when (length devices > 1) $ do
|
||||||
|
brightnesses <- forM devices $ \dev -> do
|
||||||
|
mbBright <- getDeviceBrightness dev
|
||||||
|
return (dev, mbBright)
|
||||||
|
|
||||||
|
let validBrightnesses = [(d, b) | (d, Just b) <- brightnesses]
|
||||||
|
when (not $ null validBrightnesses) $ do
|
||||||
|
let values = map snd validBrightnesses
|
||||||
|
maxBright = maximum values
|
||||||
|
minBright = minimum values
|
||||||
|
|
||||||
|
-- If devices are out of sync
|
||||||
|
when (maxBright /= minBright) $ do
|
||||||
|
-- Use minimum when brightness is low (15% or below)
|
||||||
|
let syncValue = if maxBright <= 15 then minBright else maxBright
|
||||||
|
-- Log sync decision for debugging
|
||||||
|
appendFile "/tmp/brightness.log" $
|
||||||
|
" Syncing: max=" ++ show maxBright ++ "%, min=" ++ show minBright ++
|
||||||
|
"%, using=" ++ show syncValue ++ "%\n"
|
||||||
|
mapM_ (\dev -> setDeviceBrightness dev (show syncValue ++ "%")) devices
|
||||||
|
|
||||||
|
-- Get average brightness across all devices
|
||||||
|
getAverageBrightness :: IO Int
|
||||||
|
getAverageBrightness = do
|
||||||
|
devices <- getBacklightDevices
|
||||||
|
if null devices
|
||||||
|
then return 50 -- Default fallback
|
||||||
|
else do
|
||||||
|
brightnesses <- forM devices getDeviceBrightness
|
||||||
|
let validValues = [b | Just b <- brightnesses]
|
||||||
|
if null validValues
|
||||||
|
then return 50
|
||||||
|
else return $ sum validValues `div` length validValues
|
||||||
|
|
||||||
|
-- Send notification using rumno if available
|
||||||
|
sendNotification :: Int -> IO ()
|
||||||
|
sendNotification brightness = do
|
||||||
|
rumnoExists <- (== ExitSuccess) <$>
|
||||||
|
rawSystem "sh" ["-c", "command -v rumno >/dev/null 2>&1"]
|
||||||
|
if rumnoExists
|
||||||
|
then do
|
||||||
|
_ <- readProcess "rumno" ["notify", "-b", show brightness] ""
|
||||||
|
return ()
|
||||||
|
else putStrLn (show brightness)
|
||||||
|
|
||||||
|
-- Check if a string contains another
|
||||||
|
isInfixOf :: String -> String -> Bool
|
||||||
|
isInfixOf needle haystack = any (isPrefixOf needle) (tails haystack)
|
||||||
|
where
|
||||||
|
tails [] = [[]]
|
||||||
|
tails xs@(_:xs') = xs : tails xs'
|
||||||
|
|
||||||
|
main :: IO ()
|
||||||
|
main = do
|
||||||
|
args <- getArgs
|
||||||
|
let arg = case args of
|
||||||
|
[] -> ""
|
||||||
|
(x:_) -> x
|
||||||
|
|
||||||
|
-- Log the invocation
|
||||||
|
logAction arg
|
||||||
|
|
||||||
|
-- Parse and apply command
|
||||||
|
let cmd = parseArg arg
|
||||||
|
applyBrightness cmd
|
||||||
|
|
||||||
|
-- Get current brightness and notify
|
||||||
|
brightness <- getAverageBrightness
|
||||||
|
sendNotification brightness
|
||||||
@@ -3,6 +3,9 @@
|
|||||||
# Get the brightness argument (default to empty string to just show current)
|
# Get the brightness argument (default to empty string to just show current)
|
||||||
ARG="${1:-}"
|
ARG="${1:-}"
|
||||||
|
|
||||||
|
# Log the incoming arguments for debugging
|
||||||
|
echo "[$(date '+%Y-%m-%d %H:%M:%S')] brightness.sh called with: '$ARG' (all args: $@)" >> /tmp/brightness_bash.log
|
||||||
|
|
||||||
# Function to get current brightness percentage
|
# Function to get current brightness percentage
|
||||||
get_brightness_percentage() {
|
get_brightness_percentage() {
|
||||||
# Get list of display backlight devices only (filter by class 'backlight')
|
# Get list of display backlight devices only (filter by class 'backlight')
|
||||||
@@ -45,7 +48,13 @@ get_brightness_percentage() {
|
|||||||
# Apply brightness change if argument provided
|
# Apply brightness change if argument provided
|
||||||
if [ -n "$ARG" ]; then
|
if [ -n "$ARG" ]; then
|
||||||
# Determine if it's absolute or relative
|
# Determine if it's absolute or relative
|
||||||
if [[ "$ARG" == +* ]]; then
|
if [[ "$ARG" == "up" ]]; then
|
||||||
|
# Increase by 5%
|
||||||
|
BRIGHTNESS_CMD="5%+"
|
||||||
|
elif [[ "$ARG" == "down" ]]; then
|
||||||
|
# Decrease by 5%
|
||||||
|
BRIGHTNESS_CMD="5%-"
|
||||||
|
elif [[ "$ARG" == +* ]]; then
|
||||||
# Relative increase (e.g., +5)
|
# Relative increase (e.g., +5)
|
||||||
BRIGHTNESS_CMD="${ARG:1}%+"
|
BRIGHTNESS_CMD="${ARG:1}%+"
|
||||||
elif [[ "$ARG" == -* ]]; then
|
elif [[ "$ARG" == -* ]]; then
|
||||||
@@ -61,40 +70,11 @@ if [ -n "$ARG" ]; then
|
|||||||
DEVICES=$(brightnessctl --list 2>/dev/null | grep 'class.*backlight' | cut -d' ' -f2 | cut -d"'" -f2)
|
DEVICES=$(brightnessctl --list 2>/dev/null | grep 'class.*backlight' | cut -d' ' -f2 | cut -d"'" -f2)
|
||||||
|
|
||||||
if [ -n "$DEVICES" ]; then
|
if [ -n "$DEVICES" ]; then
|
||||||
# Apply to each device
|
# Apply to each device independently without syncing
|
||||||
|
# This allows each device to maintain its own brightness range
|
||||||
for device in $DEVICES; do
|
for device in $DEVICES; do
|
||||||
brightnessctl -d "$device" set "$BRIGHTNESS_CMD" >/dev/null 2>&1
|
brightnessctl -d "$device" set "$BRIGHTNESS_CMD" >/dev/null 2>&1
|
||||||
done
|
done
|
||||||
|
|
||||||
# Check if devices have different brightness values and sync them
|
|
||||||
device_count=$(echo "$DEVICES" | wc -w)
|
|
||||||
if [ "$device_count" -gt 1 ]; then
|
|
||||||
# Get all brightness values
|
|
||||||
max_brightness=0
|
|
||||||
all_same=true
|
|
||||||
first_brightness=""
|
|
||||||
|
|
||||||
for device in $DEVICES; do
|
|
||||||
brightness=$(brightnessctl -d "$device" 2>/dev/null | grep -oP '\d+%' | head -1 | tr -d '%')
|
|
||||||
if [ -n "$brightness" ]; then
|
|
||||||
if [ -z "$first_brightness" ]; then
|
|
||||||
first_brightness=$brightness
|
|
||||||
elif [ "$brightness" != "$first_brightness" ]; then
|
|
||||||
all_same=false
|
|
||||||
fi
|
|
||||||
if [ "$brightness" -gt "$max_brightness" ]; then
|
|
||||||
max_brightness=$brightness
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# If not all the same, set them all to the maximum
|
|
||||||
if [ "$all_same" = false ] && [ "$max_brightness" -gt 0 ]; then
|
|
||||||
for device in $DEVICES; do
|
|
||||||
brightnessctl -d "$device" set "${max_brightness}%" >/dev/null 2>&1
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
# Fallback: just run brightnessctl without specifying device
|
# Fallback: just run brightnessctl without specifying device
|
||||||
brightnessctl set "$BRIGHTNESS_CMD" >/dev/null 2>&1
|
brightnessctl set "$BRIGHTNESS_CMD" >/dev/null 2>&1
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class BrightnessManager(object):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def find_brightness(cls):
|
|
||||||
items_in_backlight_directory = os.listdir("/sys/class/backlight")
|
|
||||||
if len(items_in_backlight_directory) > 1:
|
|
||||||
logger.warning(f"More than one entry in the backlight directory {items_in_backlight_directory}")
|
|
||||||
return cls.from_path(
|
|
||||||
os.path.join("/sys/class/backlight", items_in_backlight_directory[0])
|
|
||||||
)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_path(cls, path):
|
|
||||||
logger.warning(f"Using path {path}")
|
|
||||||
return cls(
|
|
||||||
set_brightness_filepath=os.path.join(path, "brightness"),
|
|
||||||
actual_brightness_filepath=os.path.join(path, "actual_brightness"),
|
|
||||||
max_brightness_filepath=os.path.join(path, "max_brightness"),
|
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self, set_brightness_filepath, max_brightness_filepath, actual_brightness_filepath
|
|
||||||
):
|
|
||||||
self.set_brightness_filepath = set_brightness_filepath
|
|
||||||
self.max_brightness_filepath = max_brightness_filepath
|
|
||||||
self.actual_brightness_filepath = actual_brightness_filepath
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_brightness(self):
|
|
||||||
with open(self.actual_brightness_filepath) as fd:
|
|
||||||
return int(fd.read())
|
|
||||||
|
|
||||||
@property
|
|
||||||
def max_brightness(self):
|
|
||||||
with open(self.max_brightness_filepath) as fd:
|
|
||||||
return int(fd.read())
|
|
||||||
|
|
||||||
@current_brightness.setter
|
|
||||||
def current_brightness(self, brightness):
|
|
||||||
with open(self.set_brightness_filepath, 'w') as fd:
|
|
||||||
fd.write(str(brightness))
|
|
||||||
|
|
||||||
def increment_by_proportion(self, proportion):
|
|
||||||
new_brightness = self.current_brightness + int(self.max_brightness * proportion)
|
|
||||||
new_brightness = min(new_brightness, self.max_brightness)
|
|
||||||
self.current_brightness = new_brightness
|
|
||||||
|
|
||||||
@property
|
|
||||||
def current_proportion(self):
|
|
||||||
return float(self.current_brightness) / self.max_brightness
|
|
||||||
|
|
||||||
|
|
||||||
def build_parser():
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description='Interact with macbook brightness',
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--change", "-c",
|
|
||||||
help="Change volume by the given percentage",
|
|
||||||
default=0
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"--print", "-p", dest="do_print",
|
|
||||||
action='store_true',
|
|
||||||
default=False
|
|
||||||
)
|
|
||||||
return parser
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
args = build_parser().parse_args()
|
|
||||||
symlink_path = os.path.expanduser("~/.config/brightness_manager/symlink")
|
|
||||||
if os.path.exists(symlink_path):
|
|
||||||
symlink_path = os.readlink(symlink_path)
|
|
||||||
manager = BrightnessManager.from_path(symlink_path)
|
|
||||||
else:
|
|
||||||
manager = BrightnessManager.find_brightness()
|
|
||||||
manager.increment_by_proportion(float(args.change) / 100)
|
|
||||||
if args.do_print:
|
|
||||||
print(int(manager.current_proportion * 100))
|
|
||||||
Reference in New Issue
Block a user