12 Commits

17 changed files with 682 additions and 6660 deletions
+2
View File
@@ -0,0 +1,2 @@
profiles/
*.json
-15
View File
@@ -1,15 +0,0 @@
#!/bin/bash
# Make sure a config file was provided
if [ $# -lt 1 ]; then
echo "Usage: $0 <config file>"
exit
fi
# Attempt to parse the config file
INPUT_FILE=$1
echo "Parsing config info from $INPUT_FILE..."
TEST=$(jq '.outputs[]' $INPUT_FILE)
echo $TEST[0]
+176 -52
View File
@@ -1,5 +1,12 @@
#!/usr/bin/env bash
VERBOSE=1
function log {
if [ $VERBOSE -eq 1 ]; then
echo "$@"
fi
}
set -e
# Make sure a config file was provided
@@ -25,73 +32,190 @@ if ! command -v kscreen-doctor &>/dev/null; then
exit 1
fi
#######################
# Parse JSON + restore
#######################
# 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")
# 1. Restore enabled/disabled + basic properties
while IFS= read -r out; do
id=$(echo "$out" | jq -r '.id')
enabled=$(echo "$out" | jq -r '.enabled')
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=$(echo "$out" | jq -r '.currentModeId')
priority=$(echo "$out" | jq -r '.priority')
# Restore enabled/disabled starting with the enabled monitors
# Starting with a disabled monitor might not work if it was
# previously the only enabled monitor
# Enable/disable
if [[ "$enabled" == "true" ]]; then
kscreen-doctor "output.$id.enable"
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
kscreen-doctor "output.$id.disable"
continue
log "Output $output_id has not attribute $attribute, skipping..."
fi
}
# Mode (Resolution + refresh)
kscreen-doctor "output.$id.mode.$mode"
function load_profile_to_outputs {
local outputs=$1
# Position
kscreen-doctor "output.$id.position.$posx,$posy"
# If empty or only whitespace, return early
[[ -z "$outputs" ]] && return
# Scale
kscreen-doctor "output.$id.scale.$scale"
# Rotation (map from JSON names to kscreen-doctor options)
case "$rotation" in
"1") kscreen-doctor "output.$id.rotation.normal" ;;
"2") kscreen-doctor "output.$id.rotation.left" ;;
"4") kscreen-doctor "output.$id.rotation.inverted" ;;
"8") kscreen-doctor "output.$id.rotation.right" ;;
esac
# Primary / Not Primary
echo $priority
if [ $priority -eq 1 ]; then
kscreen-doctor "output.$id.primary"
fi
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")
done <<< "$outputs"
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
#########################
# 2. Restore clone groups
#########################
apply_attribute $name "mode" $mode
clone_groups=$(jq -c '.clones[]?' "$PROFILE")
# Position
# CMD="kscreen-doctor output.$name.position.$posx,$posy"
# log "[CMD]" $CMD
# $CMD
fullPosition="$posx,$posy"
apply_attribute $name "position" $fullPosition
while IFS= read -r clone; do
primary=$(echo "$clone" | jq -r '.[0]')
others=$(echo "$clone" | jq -r '.[]' | tail -n +2)
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
for o in $others; do
kscreen-doctor "output.$o.clone.$primary"
done
done <<< "$clone_groups"
# 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."
-108
View File
@@ -1,108 +0,0 @@
#!/usr/bin/env bash
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
#######################
# Parse JSON + ordering
#######################
# Extract output entries based on positions
origin_out=$(jq -c '.outputs[] | select(.pos.x == 0 and .pos.y == 0 and .enabled == true)' "$PROFILE")
non_origin_outs=$(jq -c '.outputs[] | select(.pos.x != 0 or .pos.y != 0 or .enable == false)' "$PROFILE")
#######################
# Function: Apply output
#######################
apply_output() {
local out="$1"
id=$(echo "$out" | jq -r '.id')
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=$(echo "$out" | jq -r '.currentModeId')
priority=$(echo "$out" | jq -r '.priority')
enabled=$(echo "$out" | jq -r '.enabled')
# Enable/disable
if [[ "$enabled" == "true" ]]; then
kscreen-doctor "output.$id.enable"
else
kscreen-doctor "output.$id.disable"
return
fi
# Mode
kscreen-doctor "output.$id.mode.$mode"
# Position
kscreen-doctor "output.$id.position.$posx,$posy"
# Scale
kscreen-doctor "output.$id.scale.$scale"
# Rotation
case "$rotation" in
"1") kscreen-doctor "output.$id.rotation.normal" ;;
"2") kscreen-doctor "output.$id.rotation.left" ;;
"4") kscreen-doctor "output.$id.rotation.inverted" ;;
"8") kscreen-doctor "output.$id.rotation.right" ;;
esac
# Primary
if [ "$priority" -eq 1 ]; then
kscreen-doctor "output.$id.primary"
fi
}
if [[ -n "$origin_out" ]]; then
apply_output "$origin_out"
fi
while IFS= read -r out; do
apply_output "$out"
done <<< "$non_origin_outs"
#########################
# 3. Restore clone groups
#########################
clone_groups=$(jq -c '.clones[]?' "$PROFILE")
while IFS= read -r clone; do
primary=$(echo "$clone" | jq -r '.[0]')
others=$(echo "$clone" | jq -r '.[]' | tail -n +2)
for o in $others; do
kscreen-doctor "output.$o.clone.$primary"
done
done <<< "$clone_groups"
echo "Display configuration restored."
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
-1297
View File
File diff suppressed because it is too large Load Diff
+29
View File
@@ -0,0 +1,29 @@
# All Kscreen Doctor attributes
- [x] primary
- [x] priority
- [x] enable
- [x] disable
- [x] mode
- [x] position
- [x] scale
- [x] orientation / rotation
- [x] overscan (0-100)
- [x] vrrpolicy (never / always / automatic)
- [x] rgbrange (automatic / full / limited)
- [x] hdr (enable / disable / toggle)
- [x] sdr-brightness (50-10000)
- [x] wcg (enable / disable / toggle)
- [x] iccprofile (path)
- [ ] sdrGamut (0-100)
- [ ] maxBrightnessOverride (disable / int)
- [ ] maxAverageBrightnessOverride (disable / int)
- [ ] minBrightnessOverride (disable / int)
- [ ] colorProfileSource (sRBG / ICC / EDID)
- [x] brightness (0-100)
- [ ] colorPowerTradeoff (preferEfficiency / preferAccuracy)
- [ ] dimming (0-100)
- [x] mirror ( none / output )
- [x] ddcCi (allow / disallow)
- [x] maxbpc (automatic / 6-16)
- [ ] edrPolicy (never / always)
- [ ] sharpness (0 - 100)
Binary file not shown.
Binary file not shown.
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
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()
+263
View File
@@ -0,0 +1,263 @@
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.")
+114
View File
@@ -0,0 +1,114 @@
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)
+77
View File
@@ -0,0 +1,77 @@
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()