refactored by gemini to python
This commit is contained in:
Executable
+202
@@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
import os
|
||||
|
||||
VERBOSE = True
|
||||
|
||||
def log(*args):
|
||||
if VERBOSE:
|
||||
print(*args)
|
||||
|
||||
def run_command(cmd):
|
||||
log(f"[CMD] {cmd}")
|
||||
try:
|
||||
subprocess.run(cmd, shell=True, check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
log(f"Error executing command: {e}")
|
||||
|
||||
def get_mode_string(output, mode_id):
|
||||
for mode in output.get('modes', []):
|
||||
if mode['id'] == mode_id:
|
||||
width = mode['size']['width']
|
||||
height = mode['size']['height']
|
||||
refresh_rate = round(mode['refreshRate'])
|
||||
return f"{width}x{height}@{refresh_rate}"
|
||||
return None
|
||||
|
||||
def save_profile(profile_path):
|
||||
log(f"Saving current display profile to {profile_path}...")
|
||||
try:
|
||||
with open(profile_path, 'w') as f:
|
||||
subprocess.run(['kscreen-doctor', '--json'], stdout=f, check=True)
|
||||
log("Profile saved successfully.")
|
||||
except Exception as e:
|
||||
print(f"Error saving profile: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def apply_attribute(output_name, attribute, value, value_map=None):
|
||||
if value is None:
|
||||
log(f"Output {output_name} has no attribute {attribute}, skipping...")
|
||||
return
|
||||
|
||||
# If it's a list or dict, something might be wrong or it's a complex structure we don't handle this way
|
||||
if isinstance(value, (list, dict)):
|
||||
log(f"Output {output_name} attribute {attribute} has complex value {value}, skipping...")
|
||||
return
|
||||
|
||||
str_value = str(value).lower()
|
||||
if value_map:
|
||||
if str_value in value_map:
|
||||
value = value_map[str_value]
|
||||
else:
|
||||
log(f"Warning: Value {str_value} not found in map for {attribute}, using as is.")
|
||||
|
||||
cmd = f"kscreen-doctor output.{output_name}.{attribute}.{value}"
|
||||
run_command(cmd)
|
||||
|
||||
def load_profile(profile_path):
|
||||
log(f"Loading display profile from {profile_path}...")
|
||||
if not os.path.exists(profile_path):
|
||||
print(f"Profile file not found: {profile_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
with open(profile_path, 'r') as f:
|
||||
profile = json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Error reading profile JSON: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
outputs = profile.get('outputs', [])
|
||||
|
||||
# Maps from JSON values to kscreen-doctor options
|
||||
bool_enable_map = {"true": "enable", "false": "disable"}
|
||||
bool_allow_map = {"true": "allow", "false": "disallow"}
|
||||
rgb_range_map = {
|
||||
"0": "automatic", "1": "full", "2": "limited",
|
||||
"automatic": "automatic", "full": "full", "limited": "limited"
|
||||
}
|
||||
rotation_map = {"1": "normal", "2": "left", "4": "inverted", "8": "right"}
|
||||
vrr_policy_map = {
|
||||
"0": "never", "1": "always", "2": "automatic",
|
||||
"never": "never", "always": "always", "automatic": "automatic"
|
||||
}
|
||||
|
||||
# 1. Handle Enable/Disable (Enable first to avoid no-output state)
|
||||
log("Enabling enabled outputs...")
|
||||
for out in outputs:
|
||||
if out.get('enabled'):
|
||||
run_command(f"kscreen-doctor output.{out['name']}.enable")
|
||||
|
||||
log("Disabling disabled outputs...")
|
||||
for out in outputs:
|
||||
if not out.get('enabled'):
|
||||
run_command(f"kscreen-doctor output.{out['name']}.disable")
|
||||
|
||||
# 2. Apply other attributes
|
||||
for out in outputs:
|
||||
name = out['name']
|
||||
log(f"Configuring output: {name}")
|
||||
|
||||
# WCG
|
||||
apply_attribute(name, "wcg", out.get('wcg'), bool_enable_map)
|
||||
|
||||
# SDR Brightness
|
||||
apply_attribute(name, "sdr-brightness", out.get('sdr-brightness'))
|
||||
|
||||
# VRR Policy
|
||||
apply_attribute(name, "vrrpolicy", out.get('vrrPolicy'), vrr_policy_map)
|
||||
|
||||
# RGB Range
|
||||
apply_attribute(name, "rgbrange", out.get('rgbRange'), rgb_range_map)
|
||||
|
||||
# Overscan
|
||||
apply_attribute(name, "overscan", out.get('overscan'))
|
||||
|
||||
# HDR
|
||||
apply_attribute(name, "hdr", out.get('hdr'), bool_enable_map)
|
||||
|
||||
# Brightness (0.0-1.0 to 0-100)
|
||||
brightness = out.get('brightness')
|
||||
if brightness is not None:
|
||||
apply_attribute(name, "brightness", int(float(brightness) * 100))
|
||||
|
||||
# Max BPC
|
||||
max_bpc = out.get('maxBpc')
|
||||
if max_bpc == 0:
|
||||
max_bpc = "automatic"
|
||||
apply_attribute(name, "maxbpc", max_bpc)
|
||||
|
||||
# DDC/CI
|
||||
apply_attribute(name, "ddcCi", out.get('ddcCiAllowed'), bool_allow_map)
|
||||
|
||||
# Mirroring
|
||||
replication_source_id = out.get('replicationSource', 0)
|
||||
if replication_source_id != 0:
|
||||
# Find the name of the replication source
|
||||
source_name = "none"
|
||||
for other_out in outputs:
|
||||
if other_out.get('id') == replication_source_id:
|
||||
source_name = other_out['name']
|
||||
break
|
||||
apply_attribute(name, "mirror", source_name)
|
||||
else:
|
||||
apply_attribute(name, "mirror", "none")
|
||||
|
||||
# ICC Profile
|
||||
icc_path = out.get('iccProfilePath')
|
||||
if icc_path:
|
||||
apply_attribute(name, "iccProfilePath", icc_path)
|
||||
|
||||
# Mode
|
||||
mode_id = out.get('currentModeId')
|
||||
if mode_id:
|
||||
mode_str = get_mode_string(out, mode_id)
|
||||
if mode_str:
|
||||
apply_attribute(name, "mode", mode_str)
|
||||
|
||||
# Position
|
||||
pos = out.get('pos')
|
||||
if pos:
|
||||
apply_attribute(name, "position", f"{pos['x']},{pos['y']}")
|
||||
|
||||
# Scale
|
||||
apply_attribute(name, "scale", out.get('scale'))
|
||||
|
||||
# Rotation
|
||||
apply_attribute(name, "rotation", out.get('rotation'), rotation_map)
|
||||
|
||||
# Priority and Primary
|
||||
priority = out.get('priority')
|
||||
if priority is not None:
|
||||
if priority == 1:
|
||||
run_command(f"kscreen-doctor output.{name}.primary")
|
||||
run_command(f"kscreen-doctor output.{name}.priority.{priority}")
|
||||
|
||||
log("Display configuration restored.")
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="KDE Display Profile Manager")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
# Save command
|
||||
save_parser = subparsers.add_parser("save", help="Save current display configuration to a profile")
|
||||
save_parser.add_argument("profile", help="Path to the profile file (e.g., profiles/myprofile.json)")
|
||||
|
||||
# Load command
|
||||
load_parser = subparsers.add_parser("load", help="Load a display configuration from a profile")
|
||||
load_parser.add_argument("profile", help="Path to the profile file")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.command == "save":
|
||||
save_profile(args.profile)
|
||||
elif args.command == "load":
|
||||
load_profile(args.profile)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user