Skip to content

Commit

Permalink
Brightness documentation, check support, configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
infeeeee committed Sep 21, 2024
1 parent 024b4f3 commit 4dc7f18
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 63 deletions.
2 changes: 1 addition & 1 deletion IoTuring/Configurator/ConfiguratorObject.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def ConfigurationPreset(cls) -> MenuPreset:
return MenuPreset()

@classmethod
def AllowMultiInstance(cls):
def AllowMultiInstance(cls) -> bool:
""" Return True if this Entity can have multiple instances, useful for customizable entities
These entities are the ones that must have a tag to be recognized """
return cls.ALLOW_MULTI_INSTANCE
Expand Down
208 changes: 146 additions & 62 deletions IoTuring/Entity/Deployments/Brightness/Brightness.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from pathlib import Path
import re
import sys

from IoTuring.Entity.Entity import Entity
from IoTuring.Configurator.MenuPreset import MenuPreset
from IoTuring.Entity.EntityData import EntitySensor, EntityCommand
Expand All @@ -7,11 +10,6 @@
from IoTuring.Entity.ValueFormat import ValueFormatterOptions


import re
import os
import sys


KEY_CMD = "command"
KEY_STATE = "state"
KEY_BRIGHTNESS = "value"
Expand All @@ -20,13 +18,31 @@


class BrightnessCmds:
"""Base class storing commands for setting and getting brightness.
In child classes override _GetValue and _SetValue methods.
Value should be a string from/to this methods,
conversion to int and scaling happens in Get and Set methods.
"""

def __init__(
self,
scale: float = 100,
decimals: int = 0,
set_command: str = "{}",
get_command: str = "",
) -> None:
"""Set parameters for this platform
Args:
scale (float, optional): The maximum value of brightness. Defaults to 100.
decimals (int, optional): Required decimals. Defaults to 0.
set_command (str, optional): Terminal command to set brightness.
"{}" will be replaced with brightness value. Defaults to "{}".
Ignore with custom _SetValue method in subclasses.
get_command (str, optional): Terminal command to get brightness. Defaults to "".
Ignore with custom _GetValue method in subclasses.
"""
self.scale = scale
self.decimals = decimals
self.set_command = set_command
Expand All @@ -37,7 +53,8 @@ def Set(self, value: int) -> None:
raise Exception("Invalid value")
scaled_value = (value / 255) * self.scale
scaled_value = round(scaled_value, self.decimals)
scaled_value = int(scaled_value) if self.decimals == 0 else scaled_value
scaled_value = int(
scaled_value) if self.decimals == 0 else scaled_value
self._SetValue(value_str=str(scaled_value))

def _SetValue(self, value_str: str) -> None:
Expand All @@ -53,6 +70,14 @@ def _GetValue(self) -> str:
value_str = OsD.RunCommand(self.get_command).stdout
return value_str

@classmethod
def CheckPlatformSupported(cls) -> None:
raise NotImplementedError("Should be implemented in subclasses")

@classmethod
def AllowMultiInstance(cls) -> bool:
return False


class Brightness_Macos(BrightnessCmds):
def __init__(self) -> None:
Expand All @@ -68,13 +93,20 @@ def _GetValue(self) -> str:
brightness = re.findall("display 0: brightness.*$", stdout)[0][22:30]
return brightness

@classmethod
def CheckPlatformSupported(cls) -> None:
if not OsD.CommandExists("brightness"):
raise Exception(
"Brightness not available, have you installed 'brightness' on Homebrew ?"
)


class Brightness_Win(BrightnessCmds):
def __init__(self, monitor_id: int = 0) -> None:
super().__init__()

import pythoncom
import wmi
import pythoncom # type: ignore
import wmi # type: ignore

pythoncom.CoInitialize()
self.monitor_id = monitor_id
Expand All @@ -88,10 +120,19 @@ def _SetValue(self, value_str: str) -> None:
def _GetValue(self) -> str:
return self.wmi.WmiMonitorBrightness()[self.monitor_id].CurrentBrightness

@classmethod
def CheckPlatformSupported(cls) -> None:
if ["wmi", "pythoncom"] not in sys.modules:
raise Exception(
"Brightness not available, have you installed 'wmi' on pip ?"
)


class Brightness_Linux_ACPI(BrightnessCmds):
ROOT_PATH = Path("/sys/class/backlight")

def __init__(self, configuredGPU: str) -> None:
self.gpu_path = Path(f"/sys/class/backlight/{configuredGPU}")
self.gpu_path = self.ROOT_PATH.joinpath(configuredGPU)
scale = int(self.get_from_file("max_brightness"))
super().__init__(scale=scale)

Expand All @@ -107,33 +148,56 @@ def get_from_file(self, file_name: str) -> str:
content = file.read()
return content.strip("\n")

@classmethod
def get_gpus(cls) -> list[str]:
return [str(d.name) for d in cls.ROOT_PATH.iterdir() if d.is_dir()]

@classmethod
def CheckPlatformSupported(cls) -> None:
if not cls.ROOT_PATH.exists():
raise Exception("ACPI: dir not found!")
if not [f for f in cls.ROOT_PATH.iterdir()]:
raise Exception("ACPI: No Gpu found!")

@classmethod
def AllowMultiInstance(cls) -> bool:
return bool(len(cls.get_gpus()) > 1)


class Brightness_Linux_Gnome(BrightnessCmds):
DBUS_COMMAND_TEMPLATE = " ".join(
[
"gdbus call --session",
"--dest org.gnome.SettingsDaemon.Power",
"--object-path /org/gnome/SettingsDaemon/Power",
"--method org.freedesktop.DBus.Properties.{}",
"org.gnome.SettingsDaemon.Power.Screen Brightness",
]
)

def __init__(self) -> None:
dbus_command = " ".join(
[
"gdbus call --session",
"--dest org.gnome.SettingsDaemon.Power",
"--object-path /org/gnome/SettingsDaemon/Power",
"--method org.freedesktop.DBus.Properties.{}",
"org.gnome.SettingsDaemon.Power.Screen Brightness",
]
)

super().__init__(
set_command=dbus_command.format("Set") + ' "<int32 {}>"',
get_command=dbus_command.format("Get"),
set_command=self.DBUS_COMMAND_TEMPLATE.format(
"Set") + ' "<int32 {}>"',
get_command=self.DBUS_COMMAND_TEMPLATE.format("Get"),
)

def _GetValue(self) -> str:
stdout = super()._GetValue()
brightness = re.findall(r"<(\d*)", stdout)[0]
return brightness

@classmethod
def CheckPlatformSupported(cls) -> None:
stdout = OsD.RunCommand(cls.DBUS_COMMAND_TEMPLATE.format("Get")).stdout
if not re.findall(r"<\d", stdout):
raise Exception("Gnome: Dbus Brightness not supported!")


class Brightness(Entity):
NAME = "Brightness"
ALLOW_MULTI_INSTANCE = True
# ALLOW_MULTI_INSTANCE depends on platform. See AllowMultiInstance()

brightness_cmds: BrightnessCmds

Expand All @@ -157,18 +221,12 @@ def Initialize(self):
)
)

if OsD.IsWindows():
self.brightness_cmds = Brightness_Win()
elif OsD.IsMacos():
self.brightness_cmds = Brightness_Macos()
elif OsD.IsLinux():
if De.GetDesktopEnvironment() == "gnome":
self.brightness_cmds = Brightness_Linux_Gnome()
else:
configuredGPU: str = self.GetFromConfigurations(CONFIG_KEY_GPU)
self.brightness_cmds = Brightness_Linux_ACPI(configuredGPU)
command_class = self.GetCommandClass()
if command_class == Brightness_Linux_ACPI:
configuredGPU: str = self.GetFromConfigurations(CONFIG_KEY_GPU)
self.brightness_cmds = Brightness_Linux_ACPI(configuredGPU)
else:
raise Exception("Unsupported OS!")
self.brightness_cmds = command_class()

def Callback(self, message):
state = message.payload.decode("utf-8")
Expand All @@ -182,40 +240,66 @@ def Update(self):
@classmethod
def ConfigurationPreset(cls):
preset = MenuPreset()
if OsD.IsLinux():
# find all GPUs in /sys/class/backlight by listing all directories
gpus = [
gpu
for gpu in os.listdir("/sys/class/backlight")
if os.path.isdir(f"/sys/class/backlight/{gpu}")
]

preset.AddEntry(
name="which GPUs backlight you want to control?",
key=CONFIG_KEY_GPU,
question_type="select",
choices=gpus,
)

if cls.GetCommandClass() == Brightness_Linux_ACPI:
gpus = Brightness_Linux_ACPI.get_gpus()

if len(gpus) > 1:
preset.AddEntry(
name="which GPUs backlight you want to control?",
key=CONFIG_KEY_GPU,
question_type="select",
choices=gpus,
default=gpus[0]
)
else:
# Set hidden default value, if only one gpu:
preset.AddEntry(
name="which GPUs backlight you want to control?",
key=CONFIG_KEY_GPU,
question_type="select",
choices=gpus,
default=gpus[0],
display_if_key_value={CONFIG_KEY_GPU: False}
)

return preset

@classmethod
def CheckSystemSupport(cls):
if OsD.IsWindows(): # TODO needs to be tested
# if wmi and pythoncom are not available, raise an exception
if ["wmi", "pythoncom"] not in sys.modules:
raise Exception(
"Brightness not available, have you installed 'wmi' on pip ?"
)
elif OsD.IsMacos(): # TODO needs to be tested
if not OsD.CommandExists("brightness"):
raise Exception(
"Brightness not avaidlable, have you installed 'brightness' on Homebrew ?"
)
cls.GetCommandClass()

@classmethod
def AllowMultiInstance(cls):
return cls.GetCommandClass().AllowMultiInstance()

@classmethod
def GetCommandClass(cls) -> type:
"""Get Brightness Command class. Raises exception if not supported"""

classes: list[type] = []
exceptions: list[str] = []

if OsD.IsWindows():
classes.append(Brightness_Win)
elif OsD.IsMacos():
classes.append(Brightness_Macos)

elif OsD.IsLinux():
if not Path("/sys/class/backlight").exists():
# TODO check if this dir always exists
raise Exception(
"Brightness not available, no backlight found in /sys/class/backlight"
)

if De.GetDesktopEnvironment() == "gnome":
classes.append(Brightness_Linux_Gnome)

classes.append(Brightness_Linux_ACPI)

else:
raise cls.UnsupportedOsException()

for cls in classes:
try:
cls.CheckPlatformSupported()
return cls
except Exception as e:
exceptions.append(str(e))

raise Exception(" ".join(exceptions))

0 comments on commit 4dc7f18

Please sign in to comment.