Compare commits
4 Commits
bc15980ba5
..
gui
| Author | SHA1 | Date | |
|---|---|---|---|
| 768d878981 | |||
| afccfbbc05 | |||
| b25cb6b278 | |||
| e92be07078 |
+101
-46
@@ -73,6 +73,28 @@ 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
|
||||
|
||||
@@ -87,50 +109,98 @@ function load_profile_to_outputs {
|
||||
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 -neq 0 ]; then
|
||||
if [ $replication_source_id != 0 ]; then
|
||||
replication_source_name=$(echo "$outputs.[] | select(.id == \"$replication_source_id\" | .name)" )
|
||||
log "[VAR] replication_source_name: $replication_source_name"
|
||||
CMD="kscreen-doctor output.$"
|
||||
done
|
||||
log "[VAR] mode: $mode"
|
||||
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
|
||||
|
||||
# Mode (Resolution + refresh)
|
||||
# CMD="kscreen-doctor output.$name.mode.$mode"
|
||||
CMD="kscreen-doctor output.$name.mode.$mode"
|
||||
log "[CMD]" $CMD
|
||||
$CMD
|
||||
|
||||
apply_attribute $name "mode" $mode
|
||||
|
||||
# Position
|
||||
CMD="kscreen-doctor output.$name.position.$posx,$posy"
|
||||
log "[CMD]" $CMD
|
||||
$CMD
|
||||
# CMD="kscreen-doctor output.$name.position.$posx,$posy"
|
||||
# log "[CMD]" $CMD
|
||||
# $CMD
|
||||
fullPosition="$posx,$posy"
|
||||
apply_attribute $name "position" $fullPosition
|
||||
|
||||
# Scale
|
||||
CMD="kscreen-doctor output.$name.scale.$scale"
|
||||
log "[CMD]" $CMD
|
||||
$CMD
|
||||
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
|
||||
# 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
|
||||
@@ -139,28 +209,13 @@ function load_profile_to_outputs {
|
||||
$CMD
|
||||
fi
|
||||
|
||||
CMD="kscreen-doctor output.$name.priority.$priority"
|
||||
log "[CMD]" $CMD
|
||||
$CMD
|
||||
|
||||
done <<< "$outputs"
|
||||
}
|
||||
|
||||
load_profile_to_outputs "$outputs"
|
||||
|
||||
clone_groups=$(jq -c '.clones[]?' "$PROFILE")
|
||||
|
||||
function restore_clone_groups {
|
||||
local clone_groups=$1
|
||||
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
|
||||
CMD="kscreen-doctor output.$o.clone.$primary"
|
||||
log "[CMD]" $CMD
|
||||
$CMD
|
||||
|
||||
done
|
||||
done <<< "$clone_groups"
|
||||
}
|
||||
|
||||
restore_clone_groups "$clone_groups"
|
||||
|
||||
echo "Display configuration restored."
|
||||
@@ -1,30 +1,29 @@
|
||||
# All Kscreen Doctor attributes
|
||||
|
||||
- [x] primary
|
||||
- [ ] priority
|
||||
- [x] priority
|
||||
- [x] enable
|
||||
- [x] disable
|
||||
- [x] mode
|
||||
- [x] position
|
||||
- [x] scale
|
||||
- [x] orientation / rotation
|
||||
- [ ] overscan (0-100)
|
||||
- [ ] vrrpolicy (never / always / automatic)
|
||||
- [ ] rgbrange (automatic / full / limited)
|
||||
- [ ] hdr (enable / disable / toggle)
|
||||
- [ ] sdr-brightness (50-10000)
|
||||
- [ ] wcg (enable / disable / toggle)
|
||||
- [ ] iccprofile (path)
|
||||
- [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)
|
||||
- [ ] brightness (0-100)
|
||||
- [x] brightness (0-100)
|
||||
- [ ] colorPowerTradeoff (preferEfficiency / preferAccuracy)
|
||||
- [ ] dimming (0-100)
|
||||
- [ ] mirror ( none / output )
|
||||
- [ ] ddcCi (allow / disallow)
|
||||
- [ ] maxbpc (automatic / 6-16)
|
||||
- [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
@@ -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()
|
||||
@@ -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.")
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user