[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")
|
||||
, ((hctrl, xK_q), spawn "toggle_mute_current_window.sh only")
|
||||
|
||||
, ((0, xF86XK_MonBrightnessUp), spawn "brightness.sh +5")
|
||||
, ((0, xF86XK_MonBrightnessDown), spawn "brightness.sh -5")
|
||||
, ((0, xF86XK_MonBrightnessUp), spawn "brightness.sh up")
|
||||
, ((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)
|
||||
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
|
||||
get_brightness_percentage() {
|
||||
# Get list of display backlight devices only (filter by class 'backlight')
|
||||
@@ -45,7 +48,13 @@ get_brightness_percentage() {
|
||||
# Apply brightness change if argument provided
|
||||
if [ -n "$ARG" ]; then
|
||||
# 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)
|
||||
BRIGHTNESS_CMD="${ARG:1}%+"
|
||||
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)
|
||||
|
||||
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
|
||||
brightnessctl -d "$device" set "$BRIGHTNESS_CMD" >/dev/null 2>&1
|
||||
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
|
||||
# Fallback: just run brightnessctl without specifying device
|
||||
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