Compare commits
6 Commits
gui
...
f274d5c8b3
| Author | SHA1 | Date | |
|---|---|---|---|
| f274d5c8b3 | |||
| 5e0840a59d | |||
| 71a71dcc5e | |||
| 6257e63357 | |||
| 41047adac3 | |||
| acd351f559 |
+2
-1
@@ -1,2 +1,3 @@
|
|||||||
profiles/
|
profiles/
|
||||||
*.json
|
*.json
|
||||||
|
__pycache__/
|
||||||
Executable
+329
@@ -0,0 +1,329 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Try to import PySide6 for the GUI
|
||||||
|
try:
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
|
QListWidget, QPushButton, QInputDialog, QMessageBox, QLabel,
|
||||||
|
QFileDialog
|
||||||
|
)
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
PYSIDE_AVAILABLE = True
|
||||||
|
except ImportError:
|
||||||
|
PYSIDE_AVAILABLE = False
|
||||||
|
|
||||||
|
VERBOSE = True
|
||||||
|
DEFAULT_PROFILE_DIR = Path.home() / ".local/share/kde-display-profiles"
|
||||||
|
|
||||||
|
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:
|
||||||
|
log(f"Error saving profile: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
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):
|
||||||
|
raise FileNotFoundError(f"Profile file not found: {profile_path}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(profile_path, 'r') as f:
|
||||||
|
profile = json.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
log(f"Error reading profile JSON: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
outputs = profile.get('outputs', [])
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
# 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"
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_attr(output_name, attribute, value, value_map=None):
|
||||||
|
if value is None:
|
||||||
|
return
|
||||||
|
if isinstance(value, (list, dict)):
|
||||||
|
return
|
||||||
|
|
||||||
|
str_value = str(value).lower()
|
||||||
|
if value_map:
|
||||||
|
if str_value in value_map:
|
||||||
|
value = value_map[str_value]
|
||||||
|
|
||||||
|
commands.append(f"output.{output_name}.{attribute}.{value}")
|
||||||
|
|
||||||
|
# 1. Handle Enable/Disable
|
||||||
|
for out in outputs:
|
||||||
|
status = "enable" if out.get('enabled') else "disable"
|
||||||
|
commands.append(f"output.{out['name']}.{status}")
|
||||||
|
|
||||||
|
# 2. Collect other attributes
|
||||||
|
for out in outputs:
|
||||||
|
name = out['name']
|
||||||
|
|
||||||
|
# WCG
|
||||||
|
add_attr(name, "wcg", out.get('wcg'), bool_enable_map)
|
||||||
|
# SDR Brightness
|
||||||
|
add_attr(name, "sdr-brightness", out.get('sdr-brightness'))
|
||||||
|
# VRR Policy
|
||||||
|
add_attr(name, "vrrpolicy", out.get('vrrPolicy'), vrr_policy_map)
|
||||||
|
# RGB Range
|
||||||
|
add_attr(name, "rgbrange", out.get('rgbRange'), rgb_range_map)
|
||||||
|
# Overscan
|
||||||
|
add_attr(name, "overscan", out.get('overscan'))
|
||||||
|
# HDR
|
||||||
|
add_attr(name, "hdr", out.get('hdr'), bool_enable_map)
|
||||||
|
|
||||||
|
# Brightness
|
||||||
|
brightness = out.get('brightness')
|
||||||
|
if brightness is not None:
|
||||||
|
add_attr(name, "brightness", int(float(brightness) * 100))
|
||||||
|
|
||||||
|
# Max BPC
|
||||||
|
max_bpc = out.get('maxBpc')
|
||||||
|
if max_bpc == 0:
|
||||||
|
max_bpc = "automatic"
|
||||||
|
add_attr(name, "maxbpc", max_bpc)
|
||||||
|
|
||||||
|
# DDC/CI
|
||||||
|
add_attr(name, "ddcCi", out.get('ddcCiAllowed'), bool_allow_map)
|
||||||
|
|
||||||
|
# Mirroring
|
||||||
|
replication_source_id = out.get('replicationSource', 0)
|
||||||
|
if replication_source_id != 0:
|
||||||
|
source_name = "none"
|
||||||
|
for other_out in outputs:
|
||||||
|
if other_out.get('id') == replication_source_id:
|
||||||
|
source_name = other_out['name']
|
||||||
|
break
|
||||||
|
add_attr(name, "mirror", source_name)
|
||||||
|
else:
|
||||||
|
add_attr(name, "mirror", "none")
|
||||||
|
|
||||||
|
# ICC Profile
|
||||||
|
icc_path = out.get('iccProfilePath')
|
||||||
|
if icc_path:
|
||||||
|
add_attr(name, "iccProfilePath", icc_path)
|
||||||
|
|
||||||
|
# Mode
|
||||||
|
mode_id = out.get('currentModeId')
|
||||||
|
if mode_id:
|
||||||
|
mode_str = get_mode_string(out, mode_id)
|
||||||
|
if mode_str:
|
||||||
|
add_attr(name, "mode", mode_str)
|
||||||
|
|
||||||
|
# Position
|
||||||
|
pos = out.get('pos')
|
||||||
|
if pos:
|
||||||
|
add_attr(name, "position", f"{pos['x']},{pos['y']}")
|
||||||
|
|
||||||
|
# Scale
|
||||||
|
add_attr(name, "scale", out.get('scale'))
|
||||||
|
|
||||||
|
# Rotation
|
||||||
|
add_attr(name, "rotation", out.get('rotation'), rotation_map)
|
||||||
|
|
||||||
|
# Priority and Primary
|
||||||
|
priority = out.get('priority')
|
||||||
|
if priority is not None:
|
||||||
|
if priority == 1:
|
||||||
|
commands.append(f"output.{name}.primary")
|
||||||
|
commands.append(f"output.{name}.priority.{priority}")
|
||||||
|
|
||||||
|
if commands:
|
||||||
|
full_cmd = ["kscreen-doctor"] + commands
|
||||||
|
log(f"Running atomic command: {' '.join(full_cmd)}")
|
||||||
|
try:
|
||||||
|
subprocess.run(full_cmd, check=True)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
log(f"Error executing kscreen-doctor: {e}")
|
||||||
|
raise e
|
||||||
|
|
||||||
|
log("Display configuration restored.")
|
||||||
|
|
||||||
|
class DisplayProfileManagerGUI(QMainWindow):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("KDE Display Profile Manager")
|
||||||
|
self.setMinimumSize(400, 300)
|
||||||
|
|
||||||
|
# Ensure default directory exists
|
||||||
|
DEFAULT_PROFILE_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
self.setup_ui()
|
||||||
|
self.refresh_profiles()
|
||||||
|
|
||||||
|
def setup_ui(self):
|
||||||
|
central_widget = QWidget()
|
||||||
|
self.setCentralWidget(central_widget)
|
||||||
|
layout = QVBoxLayout(central_widget)
|
||||||
|
|
||||||
|
layout.addWidget(QLabel("Available Profiles:"))
|
||||||
|
|
||||||
|
self.profile_list = QListWidget()
|
||||||
|
layout.addWidget(self.profile_list)
|
||||||
|
|
||||||
|
btn_layout = QHBoxLayout()
|
||||||
|
self.load_btn = QPushButton("Load Profile")
|
||||||
|
self.load_btn.clicked.connect(self.on_load_clicked)
|
||||||
|
btn_layout.addWidget(self.load_btn)
|
||||||
|
|
||||||
|
self.save_btn = QPushButton("Save Current")
|
||||||
|
self.save_btn.clicked.connect(self.on_save_clicked)
|
||||||
|
btn_layout.addWidget(self.save_btn)
|
||||||
|
|
||||||
|
self.refresh_btn = QPushButton("Refresh")
|
||||||
|
self.refresh_btn.clicked.connect(self.refresh_profiles)
|
||||||
|
btn_layout.addWidget(self.refresh_btn)
|
||||||
|
|
||||||
|
layout.addLayout(btn_layout)
|
||||||
|
|
||||||
|
def refresh_profiles(self):
|
||||||
|
self.profile_list.clear()
|
||||||
|
if DEFAULT_PROFILE_DIR.exists():
|
||||||
|
profiles = sorted(DEFAULT_PROFILE_DIR.glob("*.json"))
|
||||||
|
for profile in profiles:
|
||||||
|
self.profile_list.addItem(profile.stem)
|
||||||
|
|
||||||
|
def get_default_profile_name(self):
|
||||||
|
existing_names = []
|
||||||
|
if DEFAULT_PROFILE_DIR.exists():
|
||||||
|
existing_names = [p.stem for p in DEFAULT_PROFILE_DIR.glob("*.json")]
|
||||||
|
|
||||||
|
i = 1
|
||||||
|
while f"Profile {i}" in existing_names:
|
||||||
|
i += 1
|
||||||
|
return f"Profile {i}"
|
||||||
|
|
||||||
|
def on_save_clicked(self):
|
||||||
|
default_name = self.get_default_profile_name()
|
||||||
|
name, ok = QInputDialog.getText(self, "Save Profile", "Profile Name:", text=default_name)
|
||||||
|
|
||||||
|
if ok and name:
|
||||||
|
profile_path = DEFAULT_PROFILE_DIR / f"{name}.json"
|
||||||
|
if profile_path.exists():
|
||||||
|
reply = QMessageBox.question(self, "Overwrite?",
|
||||||
|
f"Profile '{name}' already exists. Overwrite?",
|
||||||
|
QMessageBox.Yes | QMessageBox.No)
|
||||||
|
if reply == QMessageBox.No:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
save_profile(str(profile_path))
|
||||||
|
self.refresh_profiles()
|
||||||
|
QMessageBox.information(self, "Success", f"Profile '{name}' saved.")
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Error", f"Failed to save profile: {e}")
|
||||||
|
|
||||||
|
def on_load_clicked(self):
|
||||||
|
selected_item = self.profile_list.currentItem()
|
||||||
|
if not selected_item:
|
||||||
|
QMessageBox.warning(self, "No Selection", "Please select a profile to load.")
|
||||||
|
return
|
||||||
|
|
||||||
|
profile_name = selected_item.text()
|
||||||
|
profile_path = DEFAULT_PROFILE_DIR / f"{profile_name}.json"
|
||||||
|
|
||||||
|
try:
|
||||||
|
load_profile(str(profile_path))
|
||||||
|
QMessageBox.information(self, "Success", f"Profile '{profile_name}' loaded.")
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Error", f"Failed to load profile: {e}")
|
||||||
|
|
||||||
|
def show_gui():
|
||||||
|
if not PYSIDE_AVAILABLE:
|
||||||
|
print("Error: PySide6 is not installed. Please install it to use the GUI.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
window = DisplayProfileManagerGUI()
|
||||||
|
window.show()
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="KDE Display Profile Manager")
|
||||||
|
subparsers = parser.add_subparsers(dest="command", required=False)
|
||||||
|
|
||||||
|
# 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")
|
||||||
|
|
||||||
|
# GUI command
|
||||||
|
subparsers.add_parser("gui", help="Launch the GUI manager")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if args.command == "save":
|
||||||
|
save_profile(args.profile)
|
||||||
|
elif args.command == "load":
|
||||||
|
load_profile(args.profile)
|
||||||
|
elif args.command == "gui" or args.command is None:
|
||||||
|
show_gui()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
VERBOSE=1
|
|
||||||
|
|
||||||
function log {
|
|
||||||
if [ $VERBOSE -eq 1 ]; then
|
|
||||||
echo "$@"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Make sure a config file was provided
|
|
||||||
if [ $# -lt 1 ]; then
|
|
||||||
echo "Usage: $0 <config file>"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
PROFILE=$1
|
|
||||||
|
|
||||||
if [[ ! -f "$PROFILE" ]]; then
|
|
||||||
echo "Profile file not found: $PROFILE" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v jq &>/dev/null; then
|
|
||||||
echo "jq not installed!" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! command -v kscreen-doctor &>/dev/null; then
|
|
||||||
echo "kscreen-doctor not found!" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Extract outputs list
|
|
||||||
enabled_outputs=$(jq -c '.outputs[] | select(.enabled == true)' "$PROFILE")
|
|
||||||
disabled_outputs=$(jq -c '.outputs[] | select(.enabled == false)' "$PROFILE")
|
|
||||||
outputs=$(jq -c '.outputs[]' "$PROFILE")
|
|
||||||
|
|
||||||
# Restore enabled/disabled starting with the enabled monitors
|
|
||||||
# Starting with a disabled monitor might not work if it was
|
|
||||||
# previously the only enabled monitor
|
|
||||||
|
|
||||||
function enable_outputs {
|
|
||||||
local outputs=$1
|
|
||||||
|
|
||||||
# If empty or only whitespace, return early
|
|
||||||
[[ -z "$outputs" ]] && return
|
|
||||||
|
|
||||||
while IFS= read -r out; do
|
|
||||||
id=$(echo "$out" | jq -r '.id')
|
|
||||||
log "[VAR] id:" $id
|
|
||||||
name=$(echo "$out" | jq -r '.name')
|
|
||||||
log "[VAR] name:" $name
|
|
||||||
enabled=$(echo "$out" | jq -r '.enabled')
|
|
||||||
log "[VAR] enabled:" $enabled
|
|
||||||
|
|
||||||
# Enable/disable
|
|
||||||
if [[ "$enabled" == "true" ]]; then
|
|
||||||
CMD="kscreen-doctor output.$name.enable"
|
|
||||||
log "[CMD]" $CMD
|
|
||||||
$CMD
|
|
||||||
else
|
|
||||||
CMD="kscreen-doctor output.$name.disable"
|
|
||||||
log "[CMD]" $CMD
|
|
||||||
$CMD
|
|
||||||
fi
|
|
||||||
done <<< "$outputs"
|
|
||||||
}
|
|
||||||
|
|
||||||
log "Enabling enabled outputs..."
|
|
||||||
enable_outputs "$enabled_outputs"
|
|
||||||
log "Disabling disabled outputs..."
|
|
||||||
enable_outputs "$disabled_outputs"
|
|
||||||
|
|
||||||
function apply_attribute {
|
|
||||||
output_id=$1
|
|
||||||
attribute=$2
|
|
||||||
value=$3
|
|
||||||
value_map=$4
|
|
||||||
|
|
||||||
if [ $value != "null" ]; then
|
|
||||||
|
|
||||||
if [[ -n "$value_map" ]]; then
|
|
||||||
log "[PREVALUE] Value supplied for $output_id $attribute: $value"
|
|
||||||
value=${value_map["$value"]}
|
|
||||||
log "[POSTVALUE] Value output for $output_id $attribute: $value"
|
|
||||||
fi
|
|
||||||
|
|
||||||
CMD="kscreen-doctor output.$output_id.$attribute.$value"
|
|
||||||
log "[CMD]" $CMD
|
|
||||||
$CMD
|
|
||||||
else
|
|
||||||
log "Output $output_id has not attribute $attribute, skipping..."
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
function load_profile_to_outputs {
|
|
||||||
local outputs=$1
|
|
||||||
|
|
||||||
# If empty or only whitespace, return early
|
|
||||||
[[ -z "$outputs" ]] && return
|
|
||||||
|
|
||||||
while IFS= read -r out; do
|
|
||||||
id=$(echo "$out" | jq -r '.id')
|
|
||||||
name=$(echo "$out" | jq -r '.name')
|
|
||||||
posx=$(echo "$out" | jq -r '.pos.x')
|
|
||||||
posy=$(echo "$out" | jq -r '.pos.y')
|
|
||||||
rotation=$(echo "$out" | jq -r '.rotation')
|
|
||||||
scale=$(echo "$out" | jq -r '.scale')
|
|
||||||
mode_id=$(echo "$out" | jq -r '.currentModeId')
|
|
||||||
brightness=$(echo "$out" | jq -r '.brightness')
|
|
||||||
brightness=$(awk "BEGIN {printf \"%d\", $brightness * 100}")
|
|
||||||
ddcCi=$(echo "$out" | jq -r '.ddcCi')
|
|
||||||
iccProfilePath=$(echo "$out" | jq -r '.iccProfilePath')
|
|
||||||
# mode_name=$(echo "$out" | jq -r ".modes[] | select(.id == \"$mode_id\") | .name")
|
|
||||||
# log "[VAR] mode_name:" $mode_name
|
|
||||||
refresh_rate=$(echo "$out" | jq -r ".modes[] | select(.id == \"$mode_id\") | .refreshRate")
|
|
||||||
refresh_rate=$(printf "%.0f" "$refresh_rate")
|
|
||||||
height=$(echo "$out" | jq -r ".modes[] | select(.id == \"$mode_id\") | .size.height")
|
|
||||||
width=$(echo "$out" | jq -r ".modes[] | select(.id == \"$mode_id\") | .size.width")
|
|
||||||
mode="${width}x${height}@${refresh_rate}"
|
|
||||||
replication_source_id=$(echo "$out" | jq -r ".replicationSource")
|
|
||||||
hdr=$(echo "$out" | jq -r ".hdr")
|
|
||||||
maxBpc=$(echo "$out" | jq -r ".maxBpc")
|
|
||||||
overscan=$(echo "$out" | jq -r ".overscan")
|
|
||||||
rgbRange=$(echo "$out" | jq -r ".rgbRange")
|
|
||||||
sdrbrightness=$(echo "$out" | jq -r '."sdr-brightness"')
|
|
||||||
vrrPolicy=$(echo "$out" | jq -r ".vrrPolicy")
|
|
||||||
wcg=$(echo "$out" | jq -r ".wcg")
|
|
||||||
|
|
||||||
|
|
||||||
declare -A bool_enable_map
|
|
||||||
bool_enable_map["true"]="enable"
|
|
||||||
bool_enable_map["false"]="disable"
|
|
||||||
|
|
||||||
declare -A bool_allow_map
|
|
||||||
bool_allow_map["true"]="allow"
|
|
||||||
bool_allow_map["false"]="disallow"
|
|
||||||
|
|
||||||
declare -A rgb_range_map
|
|
||||||
rgb_range_map["0"]="automatic"
|
|
||||||
rgb_range_map["full"]="full"
|
|
||||||
rgb_range_map["limited"]="limited"
|
|
||||||
|
|
||||||
declare -A rotation_map
|
|
||||||
rotation_map["1"]="normal"
|
|
||||||
rotation_map["2"]="left"
|
|
||||||
rotation_map["4"]="inverted"
|
|
||||||
rotation_map["8"]="right"
|
|
||||||
|
|
||||||
apply_attribute $name "wcg" $wcg $bool_enable_map
|
|
||||||
apply_attribute $name "sdr-brightness" $sdrbrightness
|
|
||||||
apply_attribute $name "vrrpolicy" $vrrPolicy
|
|
||||||
apply_attribute $name "rgbrange" $rgbRange $rgb_range_map
|
|
||||||
apply_attribute $name "overscan" $overscan
|
|
||||||
apply_attribute $name "hdr" $hdr $bool_enable_map
|
|
||||||
apply_attribute $name "brightness" $brightness
|
|
||||||
|
|
||||||
if [ "$maxBpc" == 0 ]; then
|
|
||||||
maxBpc="automatic"
|
|
||||||
fi
|
|
||||||
apply_attribute $name "maxbpc" $maxBpc
|
|
||||||
apply_attribute $name "ddcCi" $ddcCi $bool_allow_map
|
|
||||||
|
|
||||||
|
|
||||||
log "[VAR] replication_source_id: $replication_source_id"
|
|
||||||
if [ $replication_source_id != 0 ]; then
|
|
||||||
replication_source_name=$(echo "$outputs.[] | select(.id == \"$replication_source_id\" | .name)" )
|
|
||||||
apply_attribute $name "mirror" $replication_source_name
|
|
||||||
else
|
|
||||||
apply_attribute $name "mirror" "none"
|
|
||||||
fi
|
|
||||||
|
|
||||||
priority=$(echo "$out" | jq -r '.priority')
|
|
||||||
|
|
||||||
if [ "$iccProfilePath" != "" ]; then
|
|
||||||
apply_attribute $name "iccProfilePath" $iccProfilePath
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
apply_attribute $name "mode" $mode
|
|
||||||
|
|
||||||
# Position
|
|
||||||
# CMD="kscreen-doctor output.$name.position.$posx,$posy"
|
|
||||||
# log "[CMD]" $CMD
|
|
||||||
# $CMD
|
|
||||||
fullPosition="$posx,$posy"
|
|
||||||
apply_attribute $name "position" $fullPosition
|
|
||||||
|
|
||||||
apply_attribute $name "scale" $scale
|
|
||||||
|
|
||||||
# Rotation (map from JSON names to kscreen-doctor options)
|
|
||||||
# CMD=""
|
|
||||||
# case "$rotation" in
|
|
||||||
# "1") CMD="kscreen-doctor output.$name.rotation.normal" ;;
|
|
||||||
# "2") CMD="kscreen-doctor output.$name.rotation.left" ;;
|
|
||||||
# "4") CMD="kscreen-doctor output.$name.rotation.inverted" ;;
|
|
||||||
# "8") CMD="kscreen-doctor output.$name.rotation.right" ;;
|
|
||||||
# esac
|
|
||||||
# log "[CMD]" $CMD
|
|
||||||
# $CMD
|
|
||||||
apply_attribute $name "rotation" $rotation $rotation_map
|
|
||||||
|
|
||||||
# Primary / Not Primary
|
|
||||||
if [ $priority -eq 1 ]; then
|
|
||||||
CMD="kscreen-doctor output.$name.primary"
|
|
||||||
log "[CMD]" $CMD
|
|
||||||
$CMD
|
|
||||||
fi
|
|
||||||
|
|
||||||
CMD="kscreen-doctor output.$name.priority.$priority"
|
|
||||||
log "[CMD]" $CMD
|
|
||||||
$CMD
|
|
||||||
|
|
||||||
done <<< "$outputs"
|
|
||||||
}
|
|
||||||
|
|
||||||
load_profile_to_outputs "$outputs"
|
|
||||||
|
|
||||||
echo "Display configuration restored."
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Check for correct usage
|
|
||||||
if [ $# -lt 1 ]; then
|
|
||||||
echo "Usage: $0 <config name>"
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
OUTPUT_CONFIG_NAME=$1
|
|
||||||
kscreen-doctor --json > $OUTPUT_CONFIG_NAME
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
-21
@@ -1,21 +0,0 @@
|
|||||||
import sys
|
|
||||||
from PyQt6.QtWidgets import QApplication
|
|
||||||
from PyQt6.QtGui import QIcon
|
|
||||||
from mainwindow import MainWindow
|
|
||||||
|
|
||||||
def main():
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
app.setApplicationName("Display Profile Manager")
|
|
||||||
app.setOrganizationName("KDE")
|
|
||||||
app.setOrganizationDomain("kde.org")
|
|
||||||
|
|
||||||
# Set application icon (we'll add this later)
|
|
||||||
# app.setWindowIcon(QIcon.fromTheme("video-display"))
|
|
||||||
|
|
||||||
window = MainWindow()
|
|
||||||
window.show()
|
|
||||||
|
|
||||||
sys.exit(app.exec())
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,263 +0,0 @@
|
|||||||
from PyQt6.QtWidgets import (QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
||||||
QPushButton, QListWidget, QListWidgetItem,
|
|
||||||
QLabel, QGroupBox, QLineEdit, QMessageBox,
|
|
||||||
QMenu, QMenuBar)
|
|
||||||
from PyQt6.QtCore import Qt, QSize
|
|
||||||
from PyQt6.QtGui import QAction, QKeySequence, QIcon
|
|
||||||
from save_dialog import SaveProfileDialog
|
|
||||||
from profile_manager import ProfileManager
|
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.profile_manager = ProfileManager()
|
|
||||||
self.init_ui()
|
|
||||||
self.load_profiles()
|
|
||||||
|
|
||||||
def init_ui(self):
|
|
||||||
self.setWindowTitle("Display Profile Manager")
|
|
||||||
self.setMinimumSize(800, 600)
|
|
||||||
|
|
||||||
# Create menu bar
|
|
||||||
self.create_menu_bar()
|
|
||||||
|
|
||||||
# Create central widget
|
|
||||||
central_widget = QWidget()
|
|
||||||
self.setCentralWidget(central_widget)
|
|
||||||
|
|
||||||
# Main layout
|
|
||||||
main_layout = QHBoxLayout(central_widget)
|
|
||||||
|
|
||||||
# Left panel - Profile list
|
|
||||||
left_panel = self.create_left_panel()
|
|
||||||
main_layout.addWidget(left_panel, 2)
|
|
||||||
|
|
||||||
# Right panel - Profile details
|
|
||||||
right_panel = self.create_right_panel()
|
|
||||||
main_layout.addWidget(right_panel, 1)
|
|
||||||
|
|
||||||
def create_menu_bar(self):
|
|
||||||
menubar = self.menuBar()
|
|
||||||
|
|
||||||
# File menu
|
|
||||||
file_menu = menubar.addMenu("&File")
|
|
||||||
|
|
||||||
save_action = QAction("&Save Current Display", self)
|
|
||||||
save_action.setShortcut(QKeySequence("Ctrl+S"))
|
|
||||||
save_action.triggered.connect(self.save_profile)
|
|
||||||
file_menu.addAction(save_action)
|
|
||||||
|
|
||||||
file_menu.addSeparator()
|
|
||||||
|
|
||||||
quit_action = QAction("&Quit", self)
|
|
||||||
quit_action.setShortcut(QKeySequence("Ctrl+Q"))
|
|
||||||
quit_action.triggered.connect(self.close)
|
|
||||||
file_menu.addAction(quit_action)
|
|
||||||
|
|
||||||
# Edit menu
|
|
||||||
edit_menu = menubar.addMenu("&Edit")
|
|
||||||
|
|
||||||
rename_action = QAction("&Rename Profile", self)
|
|
||||||
rename_action.setShortcut(QKeySequence("F2"))
|
|
||||||
rename_action.triggered.connect(self.rename_profile)
|
|
||||||
edit_menu.addAction(rename_action)
|
|
||||||
|
|
||||||
delete_action = QAction("&Delete Profile", self)
|
|
||||||
delete_action.setShortcut(QKeySequence("Delete"))
|
|
||||||
delete_action.triggered.connect(self.delete_profile)
|
|
||||||
edit_menu.addAction(delete_action)
|
|
||||||
|
|
||||||
# Help menu
|
|
||||||
help_menu = menubar.addMenu("&Help")
|
|
||||||
|
|
||||||
about_action = QAction("&About", self)
|
|
||||||
about_action.triggered.connect(self.show_about)
|
|
||||||
help_menu.addAction(about_action)
|
|
||||||
|
|
||||||
def create_left_panel(self):
|
|
||||||
left_widget = QWidget()
|
|
||||||
left_layout = QVBoxLayout(left_widget)
|
|
||||||
|
|
||||||
# Title
|
|
||||||
title_label = QLabel("Saved Profiles")
|
|
||||||
title_label.setStyleSheet("font-size: 14pt; font-weight: bold;")
|
|
||||||
left_layout.addWidget(title_label)
|
|
||||||
|
|
||||||
# Profile list
|
|
||||||
self.profile_list = QListWidget()
|
|
||||||
self.profile_list.itemSelectionChanged.connect(self.on_profile_selected)
|
|
||||||
self.profile_list.itemDoubleClicked.connect(self.load_selected_profile)
|
|
||||||
left_layout.addWidget(self.profile_list)
|
|
||||||
|
|
||||||
# Buttons
|
|
||||||
button_layout = QHBoxLayout()
|
|
||||||
|
|
||||||
self.save_btn = QPushButton("Save Current")
|
|
||||||
self.save_btn.clicked.connect(self.save_profile)
|
|
||||||
button_layout.addWidget(self.save_btn)
|
|
||||||
|
|
||||||
self.load_btn = QPushButton("Load")
|
|
||||||
self.load_btn.clicked.connect(self.load_selected_profile)
|
|
||||||
self.load_btn.setEnabled(False)
|
|
||||||
button_layout.addWidget(self.load_btn)
|
|
||||||
|
|
||||||
self.delete_btn = QPushButton("Delete")
|
|
||||||
self.delete_btn.clicked.connect(self.delete_profile)
|
|
||||||
self.delete_btn.setEnabled(False)
|
|
||||||
button_layout.addWidget(self.delete_btn)
|
|
||||||
|
|
||||||
left_layout.addLayout(button_layout)
|
|
||||||
|
|
||||||
return left_widget
|
|
||||||
|
|
||||||
def create_right_panel(self):
|
|
||||||
right_widget = QWidget()
|
|
||||||
right_layout = QVBoxLayout(right_widget)
|
|
||||||
|
|
||||||
# Profile details group
|
|
||||||
details_group = QGroupBox("Profile Details")
|
|
||||||
details_layout = QVBoxLayout(details_group)
|
|
||||||
|
|
||||||
# Profile name
|
|
||||||
name_layout = QHBoxLayout()
|
|
||||||
name_layout.addWidget(QLabel("Name:"))
|
|
||||||
self.name_edit = QLineEdit()
|
|
||||||
self.name_edit.setReadOnly(True)
|
|
||||||
name_layout.addWidget(self.name_edit)
|
|
||||||
details_layout.addLayout(name_layout)
|
|
||||||
|
|
||||||
# Keyboard shortcut
|
|
||||||
shortcut_layout = QHBoxLayout()
|
|
||||||
shortcut_layout.addWidget(QLabel("Shortcut:"))
|
|
||||||
self.shortcut_label = QLabel("None")
|
|
||||||
shortcut_layout.addWidget(self.shortcut_label)
|
|
||||||
shortcut_layout.addStretch()
|
|
||||||
self.set_shortcut_btn = QPushButton("Set Shortcut")
|
|
||||||
self.set_shortcut_btn.setEnabled(False)
|
|
||||||
self.set_shortcut_btn.clicked.connect(self.set_shortcut)
|
|
||||||
shortcut_layout.addWidget(self.set_shortcut_btn)
|
|
||||||
details_layout.addLayout(shortcut_layout)
|
|
||||||
|
|
||||||
# Profile info
|
|
||||||
self.info_label = QLabel("Select a profile to view details")
|
|
||||||
self.info_label.setWordWrap(True)
|
|
||||||
self.info_label.setAlignment(Qt.AlignmentFlag.AlignTop)
|
|
||||||
details_layout.addWidget(self.info_label)
|
|
||||||
|
|
||||||
details_layout.addStretch()
|
|
||||||
|
|
||||||
right_layout.addWidget(details_group)
|
|
||||||
right_layout.addStretch()
|
|
||||||
|
|
||||||
return right_widget
|
|
||||||
|
|
||||||
def load_profiles(self):
|
|
||||||
"""Load and display all saved profiles"""
|
|
||||||
self.profile_list.clear()
|
|
||||||
profiles = self.profile_manager.list_profiles()
|
|
||||||
|
|
||||||
for profile in profiles:
|
|
||||||
item = QListWidgetItem(profile['name'])
|
|
||||||
item.setData(Qt.ItemDataRole.UserRole, profile)
|
|
||||||
self.profile_list.addItem(item)
|
|
||||||
|
|
||||||
def save_profile(self):
|
|
||||||
"""Show dialog to save current display configuration"""
|
|
||||||
dialog = SaveProfileDialog(self.profile_manager, self)
|
|
||||||
if dialog.exec():
|
|
||||||
profile_name = dialog.get_profile_name()
|
|
||||||
try:
|
|
||||||
self.profile_manager.save_profile(profile_name)
|
|
||||||
self.load_profiles()
|
|
||||||
QMessageBox.information(self, "Success",
|
|
||||||
f"Profile '{profile_name}' saved successfully!")
|
|
||||||
except Exception as e:
|
|
||||||
QMessageBox.critical(self, "Error",
|
|
||||||
f"Failed to save profile: {str(e)}")
|
|
||||||
|
|
||||||
def load_selected_profile(self):
|
|
||||||
"""Load the selected profile"""
|
|
||||||
current_item = self.profile_list.currentItem()
|
|
||||||
if not current_item:
|
|
||||||
return
|
|
||||||
|
|
||||||
profile = current_item.data(Qt.ItemDataRole.UserRole)
|
|
||||||
try:
|
|
||||||
self.profile_manager.load_profile(profile['id'])
|
|
||||||
QMessageBox.information(self, "Success",
|
|
||||||
f"Profile '{profile['name']}' loaded successfully!")
|
|
||||||
except Exception as e:
|
|
||||||
QMessageBox.critical(self, "Error",
|
|
||||||
f"Failed to load profile: {str(e)}")
|
|
||||||
|
|
||||||
def delete_profile(self):
|
|
||||||
"""Delete the selected profile"""
|
|
||||||
current_item = self.profile_list.currentItem()
|
|
||||||
if not current_item:
|
|
||||||
return
|
|
||||||
|
|
||||||
profile = current_item.data(Qt.ItemDataRole.UserRole)
|
|
||||||
|
|
||||||
reply = QMessageBox.question(self, "Confirm Delete",
|
|
||||||
f"Are you sure you want to delete '{profile['name']}'?",
|
|
||||||
QMessageBox.StandardButton.Yes |
|
|
||||||
QMessageBox.StandardButton.No)
|
|
||||||
|
|
||||||
if reply == QMessageBox.StandardButton.Yes:
|
|
||||||
try:
|
|
||||||
self.profile_manager.delete_profile(profile['id'])
|
|
||||||
self.load_profiles()
|
|
||||||
QMessageBox.information(self, "Success",
|
|
||||||
f"Profile '{profile['name']}' deleted successfully!")
|
|
||||||
except Exception as e:
|
|
||||||
QMessageBox.critical(self, "Error",
|
|
||||||
f"Failed to delete profile: {str(e)}")
|
|
||||||
|
|
||||||
def rename_profile(self):
|
|
||||||
"""Rename the selected profile"""
|
|
||||||
current_item = self.profile_list.currentItem()
|
|
||||||
if not current_item:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Make item editable
|
|
||||||
self.profile_list.editItem(current_item)
|
|
||||||
|
|
||||||
def on_profile_selected(self):
|
|
||||||
"""Handle profile selection"""
|
|
||||||
current_item = self.profile_list.currentItem()
|
|
||||||
has_selection = current_item is not None
|
|
||||||
|
|
||||||
self.load_btn.setEnabled(has_selection)
|
|
||||||
self.delete_btn.setEnabled(has_selection)
|
|
||||||
self.set_shortcut_btn.setEnabled(has_selection)
|
|
||||||
|
|
||||||
if has_selection:
|
|
||||||
profile = current_item.data(Qt.ItemDataRole.UserRole)
|
|
||||||
self.name_edit.setText(profile['name'])
|
|
||||||
self.name_edit.setReadOnly(False)
|
|
||||||
|
|
||||||
shortcut = profile.get('shortcut', 'None')
|
|
||||||
self.shortcut_label.setText(shortcut)
|
|
||||||
|
|
||||||
# Display profile info
|
|
||||||
info_text = f"Created: {profile.get('created', 'Unknown')}\n"
|
|
||||||
info_text += f"Last Modified: {profile.get('modified', 'Unknown')}"
|
|
||||||
self.info_label.setText(info_text)
|
|
||||||
else:
|
|
||||||
self.name_edit.clear()
|
|
||||||
self.name_edit.setReadOnly(True)
|
|
||||||
self.shortcut_label.setText("None")
|
|
||||||
self.info_label.setText("Select a profile to view details")
|
|
||||||
|
|
||||||
def set_shortcut(self):
|
|
||||||
"""Set keyboard shortcut for selected profile"""
|
|
||||||
QMessageBox.information(self, "Coming Soon",
|
|
||||||
"Keyboard shortcut configuration will be implemented next!")
|
|
||||||
|
|
||||||
def show_about(self):
|
|
||||||
"""Show about dialog"""
|
|
||||||
QMessageBox.about(self, "About Display Profile Manager",
|
|
||||||
"Display Profile Manager\n\n"
|
|
||||||
"Manage KDE Plasma display configurations\n\n"
|
|
||||||
"Save, load, and manage multiple display profiles\n"
|
|
||||||
"with keyboard shortcuts.")
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
import os
|
|
||||||
import json
|
|
||||||
import subprocess
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
class ProfileManager:
|
|
||||||
def __init__(self):
|
|
||||||
self.config_dir = Path.home() / ".local/share/plasma-display-profiles"
|
|
||||||
self.config_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
self.metadata_file = self.config_dir / "profiles.json"
|
|
||||||
self.script_path = self.find_script()
|
|
||||||
|
|
||||||
# Initialize metadata file if it doesn't exist
|
|
||||||
if not self.metadata_file.exists():
|
|
||||||
self.save_metadata([])
|
|
||||||
|
|
||||||
def find_script(self):
|
|
||||||
"""Find the bash script for display management"""
|
|
||||||
return "scripts/"
|
|
||||||
|
|
||||||
def list_profiles(self):
|
|
||||||
"""Return list of all profiles"""
|
|
||||||
return self.load_metadata()
|
|
||||||
|
|
||||||
def save_profile(self, name):
|
|
||||||
"""Save current display configuration as a new profile"""
|
|
||||||
profiles = self.load_metadata()
|
|
||||||
|
|
||||||
# Generate unique ID
|
|
||||||
profile_id = str(uuid.uuid4())
|
|
||||||
timestamp = datetime.now().isoformat()
|
|
||||||
|
|
||||||
# Create profile entry
|
|
||||||
profile = {
|
|
||||||
'id': profile_id,
|
|
||||||
'name': name,
|
|
||||||
'created': timestamp,
|
|
||||||
'modified': timestamp,
|
|
||||||
'script_file': f"{profile_id}.sh",
|
|
||||||
'shortcut': None
|
|
||||||
}
|
|
||||||
|
|
||||||
# TODO: Call bash script to save display configuration
|
|
||||||
# For now, create a placeholder script file
|
|
||||||
script_file = self.config_dir / profile['script_file']
|
|
||||||
script_file.write_text(f"#!/bin/bash\n# Display profile: {name}\n# Created: {timestamp}\n")
|
|
||||||
script_file.chmod(0o755)
|
|
||||||
|
|
||||||
# If script exists, call it
|
|
||||||
if self.script_path and self.script_path.exists():
|
|
||||||
try:
|
|
||||||
subprocess.run([str(self.script_path), 'save', str(script_file)],
|
|
||||||
check=True, capture_output=True)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
raise Exception(f"Script execution failed: {e.stderr.decode()}")
|
|
||||||
|
|
||||||
profiles.append(profile)
|
|
||||||
self.save_metadata(profiles)
|
|
||||||
|
|
||||||
def load_profile(self, profile_id):
|
|
||||||
"""Load a display profile"""
|
|
||||||
profiles = self.load_metadata()
|
|
||||||
profile = next((p for p in profiles if p['id'] == profile_id), None)
|
|
||||||
|
|
||||||
if not profile:
|
|
||||||
raise Exception(f"Profile not found: {profile_id}")
|
|
||||||
|
|
||||||
script_file = self.config_dir / profile['script_file']
|
|
||||||
|
|
||||||
if not script_file.exists():
|
|
||||||
raise Exception(f"Profile script not found: {script_file}")
|
|
||||||
|
|
||||||
# Execute the profile script
|
|
||||||
try:
|
|
||||||
if self.script_path and self.script_path.exists():
|
|
||||||
subprocess.run([str(self.script_path), 'load', str(script_file)],
|
|
||||||
check=True, capture_output=True)
|
|
||||||
else:
|
|
||||||
# Fallback: execute script directly
|
|
||||||
subprocess.run([str(script_file)], check=True, capture_output=True)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
raise Exception(f"Failed to load profile: {e.stderr.decode()}")
|
|
||||||
|
|
||||||
def delete_profile(self, profile_id):
|
|
||||||
"""Delete a profile"""
|
|
||||||
profiles = self.load_metadata()
|
|
||||||
profile = next((p for p in profiles if p['id'] == profile_id), None)
|
|
||||||
|
|
||||||
if not profile:
|
|
||||||
raise Exception(f"Profile not found: {profile_id}")
|
|
||||||
|
|
||||||
# Remove script file
|
|
||||||
script_file = self.config_dir / profile['script_file']
|
|
||||||
if script_file.exists():
|
|
||||||
script_file.unlink()
|
|
||||||
|
|
||||||
# Remove from metadata
|
|
||||||
profiles = [p for p in profiles if p['id'] != profile_id]
|
|
||||||
self.save_metadata(profiles)
|
|
||||||
|
|
||||||
def rename_profile(self, profile_id, new_name):
|
|
||||||
"""Rename a profile"""
|
|
||||||
profiles = self.load_metadata()
|
|
||||||
profile = next((p for p in profiles if p['id'] == profile_id), None)
|
|
||||||
|
|
||||||
if not profile:
|
|
||||||
raise Exception(f"Profile not found: {profile_id}")
|
|
||||||
|
|
||||||
profile['name'] = new_name
|
|
||||||
profile['modified'] = datetime.now().isoformat()
|
|
||||||
|
|
||||||
self.save_metadata(profiles)
|
|
||||||
@@ -1,77 +0,0 @@
|
|||||||
from PyQt6.QtWidgets import (QDialog, QVBoxLayout, QHBoxLayout, QLabel,
|
|
||||||
QLineEdit, QPushButton, QMessageBox)
|
|
||||||
from PyQt6.QtCore import Qt
|
|
||||||
|
|
||||||
class SaveProfileDialog(QDialog):
|
|
||||||
def __init__(self, profile_manager, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.profile_manager = profile_manager
|
|
||||||
self.init_ui()
|
|
||||||
|
|
||||||
def init_ui(self):
|
|
||||||
self.setWindowTitle("Save Display Profile")
|
|
||||||
self.setMinimumWidth(400)
|
|
||||||
|
|
||||||
layout = QVBoxLayout(self)
|
|
||||||
|
|
||||||
# Instruction label
|
|
||||||
info_label = QLabel("Enter a name for this display profile:")
|
|
||||||
layout.addWidget(info_label)
|
|
||||||
|
|
||||||
# Profile name input
|
|
||||||
self.name_input = QLineEdit()
|
|
||||||
self.name_input.setPlaceholderText(self.get_default_name())
|
|
||||||
self.name_input.textChanged.connect(self.validate_input)
|
|
||||||
layout.addWidget(self.name_input)
|
|
||||||
|
|
||||||
# Hint label
|
|
||||||
hint_label = QLabel("Leave empty to use default name")
|
|
||||||
hint_label.setStyleSheet("color: gray; font-size: 9pt;")
|
|
||||||
layout.addWidget(hint_label)
|
|
||||||
|
|
||||||
# Buttons
|
|
||||||
button_layout = QHBoxLayout()
|
|
||||||
|
|
||||||
self.save_btn = QPushButton("Save")
|
|
||||||
self.save_btn.setDefault(True)
|
|
||||||
self.save_btn.clicked.connect(self.accept)
|
|
||||||
|
|
||||||
cancel_btn = QPushButton("Cancel")
|
|
||||||
cancel_btn.clicked.connect(self.reject)
|
|
||||||
|
|
||||||
button_layout.addStretch()
|
|
||||||
button_layout.addWidget(cancel_btn)
|
|
||||||
button_layout.addWidget(self.save_btn)
|
|
||||||
|
|
||||||
layout.addLayout(button_layout)
|
|
||||||
|
|
||||||
def get_default_name(self):
|
|
||||||
"""Generate default profile name"""
|
|
||||||
profiles = self.profile_manager.list_profiles()
|
|
||||||
|
|
||||||
# Find the next available number
|
|
||||||
counter = 1
|
|
||||||
while True:
|
|
||||||
name = f"My Profile {counter}"
|
|
||||||
if not any(p['name'] == name for p in profiles):
|
|
||||||
return name
|
|
||||||
counter += 1
|
|
||||||
|
|
||||||
def validate_input(self):
|
|
||||||
"""Validate profile name"""
|
|
||||||
name = self.name_input.text().strip()
|
|
||||||
if not name:
|
|
||||||
self.save_btn.setEnabled(True)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Check for duplicates
|
|
||||||
profiles = self.profile_manager.list_profiles()
|
|
||||||
if any(p['name'] == name for p in profiles):
|
|
||||||
self.save_btn.setEnabled(False)
|
|
||||||
else:
|
|
||||||
self.save_btn.setEnabled(True)
|
|
||||||
|
|
||||||
def get_profile_name(self):
|
|
||||||
"""Return the profile name (default or custom)"""
|
|
||||||
name = self.name_input.text().strip()
|
|
||||||
return name if name else self.get_default_name()
|
|
||||||
Reference in New Issue
Block a user