Default settings generation completed!

This commit is contained in:
LemmyCook
2025-09-21 16:31:42 -04:00
parent 177a9743d6
commit d4a46e5361
3 changed files with 330 additions and 23 deletions

View File

@@ -0,0 +1,182 @@
{
"settingsVersion": 3,
"bar": {
"position": "top",
"backgroundOpacity": 1,
"monitors": [],
"density": "default",
"showCapsule": true,
"floating": false,
"marginVertical": 0.25,
"marginHorizontal": 0.25,
"widgets": {
"left": [
{
"id": "SystemMonitor"
},
{
"id": "ActiveWindow"
},
{
"id": "MediaMini"
}
],
"center": [
{
"id": "Workspace"
}
],
"right": [
{
"id": "ScreenRecorderIndicator"
},
{
"id": "Tray"
},
{
"id": "NotificationHistory"
},
{
"id": "WiFi"
},
{
"id": "Bluetooth"
},
{
"id": "Battery"
},
{
"id": "Volume"
},
{
"id": "Brightness"
},
{
"id": "NightLight"
},
{
"id": "Clock"
},
{
"id": "SidePanelToggle"
}
]
}
},
"general": {
"avatarImage": "",
"dimDesktop": true,
"showScreenCorners": false,
"forceBlackScreenCorners": false,
"radiusRatio": 1,
"screenRadiusRatio": 1,
"animationSpeed": 1
},
"location": {
"name": "Tokyo",
"useFahrenheit": false,
"use12hourFormat": false,
"showWeekNumberInCalendar": false
},
"screenRecorder": {
"directory": "",
"frameRate": 60,
"audioCodec": "opus",
"videoCodec": "h264",
"quality": "very_high",
"colorRange": "limited",
"showCursor": true,
"audioSource": "default_output",
"videoSource": "portal"
},
"wallpaper": {
"enabled": true,
"directory": "",
"enableMultiMonitorDirectories": false,
"setWallpaperOnAllMonitors": true,
"fillMode": "crop",
"fillColor": "#000000",
"randomEnabled": false,
"randomIntervalSec": 300,
"transitionDuration": 1500,
"transitionType": "random",
"transitionEdgeSmoothness": 0.05,
"monitors": []
},
"appLauncher": {
"enableClipboardHistory": false,
"position": "center",
"backgroundOpacity": 1,
"pinnedExecs": [],
"useApp2Unit": false,
"sortByMostUsed": true
},
"dock": {
"autoHide": false,
"exclusive": false,
"backgroundOpacity": 1,
"floatingRatio": 1,
"monitors": []
},
"network": {
"wifiEnabled": true,
"bluetoothEnabled": true
},
"notifications": {
"doNotDisturb": false,
"monitors": [],
"lastSeenTs": 0,
"lowUrgencyDuration": 3,
"normalUrgencyDuration": 8,
"criticalUrgencyDuration": 15
},
"audio": {
"volumeStep": 5,
"cavaFrameRate": 60,
"visualizerType": "linear",
"mprisBlacklist": [],
"preferredPlayer": ""
},
"ui": {
"fontDefault": "Roboto",
"fontFixed": "DejaVu Sans Mono",
"fontBillboard": "Inter",
"monitorsScaling": [],
"idleInhibitorEnabled": false
},
"brightness": {
"brightnessStep": 5
},
"colorSchemes": {
"useWallpaperColors": false,
"predefinedScheme": "",
"darkMode": true
},
"matugen": {
"gtk4": false,
"gtk3": false,
"qt6": false,
"qt5": false,
"kitty": false,
"ghostty": false,
"foot": false,
"fuzzel": false,
"vesktop": false,
"pywalfox": false,
"enableUserTemplates": false
},
"nightLight": {
"enabled": false,
"forced": false,
"autoSchedule": true,
"nightTemp": "4000",
"dayTemp": "6500",
"manualSunrise": "06:30",
"manualSunset": "18:30"
},
"hooks": {
"enabled": false,
"wallpaperChange": "",
"darkModeChange": ""
}
}

View File

@@ -5,10 +5,16 @@ import Quickshell
import Quickshell.Io
import qs.Commons
import qs.Services
import "../Helpers/QtObj2JS.js" as QtObj2JS
Singleton {
id: root
// Used to access via Settings.data.xxx.yyy
readonly property alias data: adapter
property bool isLoaded: false
property bool directoriesCreated: false
// Define our app directories
// Default config directory: ~/.config/noctalia
// Default cache directory: ~/.cache/noctalia
@@ -18,20 +24,14 @@ Singleton {
property string cacheDirImages: cacheDir + "images/"
property string cacheDirImagesWallpapers: cacheDir + "images/wallpapers/"
property string cacheDirImagesNotifications: cacheDir + "images/notifications/"
property string settingsFile: Quickshell.env("NOCTALIA_SETTINGS_FILE") || (configDir + "settings.json")
property string defaultLocation: "Tokyo"
property string defaultWallpaper: Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png"
property string defaultAvatar: Quickshell.env("HOME") + "/.face"
property string defaultVideosDirectory: Quickshell.env("HOME") + "/Videos"
property string defaultLocation: "Tokyo"
property string defaultWallpapersDirectory: Quickshell.env("HOME") + "/Pictures/Wallpapers"
property string defaultWallpaper: Quickshell.shellDir + "/Assets/Wallpaper/noctalia.png"
// Used to access via Settings.data.xxx.yyy
readonly property alias data: adapter
property bool isLoaded: false
property bool directoriesCreated: false
// Signal emitted when settings are loaded after startupcale changes
signal settingsLoaded
@@ -50,11 +50,18 @@ Singleton {
// Mark directories as created and trigger file loading
directoriesCreated = true
generateDefaultSettings();
settingsFileView.adapter = adapter;
// This should only be activated once when the settings structure has changed
// Then it should be commented out again, regular users don't need to generate
// default settings on every start
//generateDefaultSettings()
// Patch-in the local default, resolved to user's home
adapter.general.avatarImage = defaultAvatar
adapter.screenRecorder.directory = defaultVideosDirectory
adapter.wallpaper.directory = defaultWallpapersDirectory
// Set the adapter to the settingsFileView to trigger the real settings load
settingsFileView.adapter = adapter
}
// Don't write settings to disk immediately
@@ -162,7 +169,7 @@ Singleton {
// general
property JsonObject general: JsonObject {
property string avatarImage: defaultAvatar
property string avatarImage: ""
property bool dimDesktop: true
property bool showScreenCorners: false
property bool forceBlackScreenCorners: false
@@ -181,7 +188,7 @@ Singleton {
// screen recorder
property JsonObject screenRecorder: JsonObject {
property string directory: defaultVideosDirectory
property string directory: ""
property int frameRate: 60
property string audioCodec: "opus"
property string videoCodec: "h264"
@@ -195,7 +202,7 @@ Singleton {
// wallpaper
property JsonObject wallpaper: JsonObject {
property bool enabled: true
property string directory: defaultWallpapersDirectory
property string directory: ""
property bool enableMultiMonitorDirectories: false
property bool setWallpaperOnAllMonitors: true
property string fillMode: "crop"
@@ -271,7 +278,7 @@ Singleton {
property JsonObject colorSchemes: JsonObject {
property bool useWallpaperColors: false
property string predefinedScheme: ""
property string predefinedScheme: "Noctalia (default)"
property bool darkMode: true
}
@@ -310,18 +317,21 @@ Singleton {
}
}
// -----------------------------------------------------
// Generate default settings at the root of the repo
function generateDefaultSettings() {
try {
Logger.log("Settings", "Generating default settings file...")
Logger.log("Settings", "Generating settings-default.json")
var defaultPath = configDir + "settings-default.json"
var jsonData = JSON.stringify(adapter, null, 2)
var tempFile = "/tmp/noctalia-default-settings.json"
// Prepare a clean JSON
var plainAdapter = QtObj2JS.qtObjectToPlainObject(adapter)
var jsonData = JSON.stringify(plainAdapter, null, 2)
// Write to temp file first, then copy to final location
Quickshell.execDetached(["sh", "-c", `echo '${jsonData}' > "${tempFile}" && cp "${tempFile}" "${defaultPath}" && rm "${tempFile}"`])
var defaultPath = Quickshell.shellDir + "/Assets/settings-default.json"
Logger.log("Settings", "Generated settings-default.json with default values")
// Encode transfer it has base64 to avoid any escaping issue
var base64Data = Qt.btoa(jsonData)
Quickshell.execDetached(["sh", "-c", `echo "${base64Data}" | base64 -d > "${defaultPath}"`])
} catch (error) {
Logger.error("Settings", "Failed to generate default settings file: " + error)
}
@@ -467,6 +477,7 @@ Singleton {
const widgetAfter = JSON.stringify(widget)
return (widgetAfter !== widgetBefore)
}
// -----------------------------------------------------
// Kickoff essential services
function kickOffServices() {

114
Helpers/QtObj2JS.js Normal file
View File

@@ -0,0 +1,114 @@
// -----------------------------------------------------
// Helper function to convert Qt objects to plain JavaScript objects
// Only used when generating settings-default.json
function qtObjectToPlainObject(obj) {
if (obj === null || obj === undefined) {
return obj;
}
// Handle primitive types
if (typeof obj !== "object") {
return obj;
}
// Handle native JavaScript arrays
if (Array.isArray(obj)) {
return obj.map((item) => qtObjectToPlainObject(item));
}
// Detect QML arrays FIRST (before color detection)
// QML arrays have a numeric length property and indexed properties
if (typeof obj.length === "number" && obj.length >= 0) {
// Check if it has indexed properties - be more flexible about detection
var hasIndexedProps = true;
var hasNumericKeys = false;
// Check if we have at least some numeric properties
for (var i = 0; i < obj.length; i++) {
if (obj.hasOwnProperty(i) || obj[i] !== undefined) {
hasNumericKeys = true;
break;
}
}
// If we have length > 0 and some numeric keys, treat as array
if (obj.length > 0 && hasNumericKeys) {
var arr = [];
for (var i = 0; i < obj.length; i++) {
// Use direct property access, handle undefined gracefully
var item = obj[i];
if (item !== undefined) {
arr.push(qtObjectToPlainObject(item));
}
}
return arr; // Return here to avoid processing as object
}
// Handle empty arrays (length = 0)
if (obj.length === 0) {
return [];
}
}
// Detect and convert QML color objects to hex strings
if (
typeof obj.r === "number" &&
typeof obj.g === "number" &&
typeof obj.b === "number" &&
typeof obj.a === "number" &&
typeof obj.valid === "boolean"
) {
// This looks like a QML color object
try {
// Try to get the string representation (should be hex like "#000000")
if (typeof obj.toString === "function") {
return obj.toString();
} else {
// Fallback: convert RGBA to hex manually
var r = Math.round(obj.r * 255);
var g = Math.round(obj.g * 255);
var b = Math.round(obj.b * 255);
var hex =
"#" +
r.toString(16).padStart(2, "0") +
g.toString(16).padStart(2, "0") +
b.toString(16).padStart(2, "0");
return hex;
}
} catch (e) {
// If conversion fails, fall through to regular object handling
}
}
// Handle regular objects
var plainObj = {};
// Get all property names, but filter out Qt-specific ones
var propertyNames = Object.getOwnPropertyNames(obj);
for (var i = 0; i < propertyNames.length; i++) {
var propName = propertyNames[i];
// Skip Qt-specific properties, functions, and array-like properties
if (
propName === "objectName" ||
propName === "objectNameChanged" ||
propName === "length" || // Skip length property
/^\d+$/.test(propName) || // Skip numeric keys (0, 1, 2, etc.)
propName.endsWith("Changed") ||
typeof obj[propName] === "function"
) {
continue;
}
try {
var value = obj[propName];
plainObj[propName] = qtObjectToPlainObject(value);
} catch (e) {
// Skip properties that can't be accessed
continue;
}
}
return plainObj;
}