quickshell: should be everything I want now

This commit is contained in:
2025-10-12 23:23:36 +02:00
parent abadf04aa2
commit 22105c20d4
84 changed files with 4375 additions and 1312 deletions

View File

@@ -4,6 +4,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import Quickshell.Services.Pipewire
import qs.Utils
Singleton {
id: root
@@ -60,6 +61,7 @@ Singleton {
function onMutedChanged() {
root._muted = (sink?.audio.muted ?? true)
Logger.log("AudioService", "OnMuteChanged:", root._muted)
}
}
@@ -76,6 +78,7 @@ Singleton {
function onMutedChanged() {
root._inputMuted = (source?.audio.muted ?? true)
Logger.log("AudioService", "OnInputMuteChanged:", root._inputMuted)
}
}
@@ -93,7 +96,7 @@ Singleton {
sink.audio.muted = false
sink.audio.volume = Math.max(0, Math.min(1.0, newVolume))
} else {
console.warn("No sink available")
Logger.warn("AudioService", "No sink available")
}
}
@@ -101,7 +104,7 @@ Singleton {
if (sink?.ready && sink?.audio) {
sink.audio.muted = muted
} else {
console.warn("No sink available")
Logger.warn("AudioService", "No sink available")
}
}
@@ -111,7 +114,7 @@ Singleton {
source.audio.muted = false
source.audio.volume = Math.max(0, Math.min(1.0, newVolume))
} else {
console.warn("No source available")
Logger.warn("AudioService", "No source available")
}
}
@@ -119,7 +122,7 @@ Singleton {
if (source?.ready && source?.audio) {
source.audio.muted = muted
} else {
console.warn("No source available")
Logger.warn("AudioService", "No source available")
}
}

View File

@@ -3,6 +3,7 @@ pragma Singleton
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Utils
Singleton {
id: root
@@ -44,6 +45,10 @@ Singleton {
reloadableId: "brightness"
Component.onCompleted: {
Logger.log("Brightness", "Service started")
}
onMonitorsChanged: {
ddcMonitors = []
ddcProc.running = true
@@ -80,7 +85,7 @@ Singleton {
var ddcModel = ddcModelMatch ? ddcModelMatch.length > 0 : false
var model = modelMatch ? modelMatch[1] : "Unknown"
var bus = busMatch ? busMatch[1] : "Unknown"
console.log("Detected DDC Monitor:", model, "on bus", bus, "is DDC:", !ddcModel)
Logger.log("Brigthness", "Detected DDC Monitor:", model, "on bus", bus, "is DDC:", !ddcModel)
return {
"model": model,
"busNum": bus,
@@ -188,7 +193,7 @@ Singleton {
var val = parseInt(dataText)
if (!isNaN(val)) {
monitor.brightness = val / 101
console.log("Apple display brightness:", monitor.brightness)
Logger.log("Brightness", "Apple display brightness:", monitor.brightness)
}
} else if (monitor.isDdc) {
var parts = dataText.split(" ")
@@ -197,7 +202,7 @@ Singleton {
var max = parseInt(parts[4])
if (!isNaN(current) && !isNaN(max) && max > 0) {
monitor.brightness = current / max
console.log("DDC brightness:", monitor.brightness)
Logger.log("Brightness", "DDC brightness:", current + "/" + max + " =", monitor.brightness)
}
}
} else {
@@ -213,8 +218,8 @@ Singleton {
if (!isNaN(current) && !isNaN(max) && max > 0) {
monitor.maxBrightness = max
monitor.brightness = current / max
console.log("Internal brightness:", monitor.brightness)
console.log("Using backlight device:", monitor.backlightDevice)
Logger.log("Brightness", "Internal brightness:", current + "/" + max + " =", monitor.brightness)
Logger.log("Brightness", "Using backlight device:", monitor.backlightDevice)
}
}
}

View File

@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Services
import qs.Utils
pragma Singleton
Singleton {
@@ -136,10 +137,10 @@ Singleton {
if (isInhibited)
isInhibited = false;
console.log("Inhibitor process exited with code:", exitCode, "status:", exitStatus);
Logger.log("Caffeine", "Inhibitor process exited with code:", exitCode, "status:", exitStatus);
}
onStarted: function() {
console.log("Inhibitor process started with strategy:", root.strategy);
Logger.log("Caffeine", "Inhibitor process started with PID:", inhibitorProcess.processId);
}
}

View File

@@ -0,0 +1,35 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Constants
Item {
IpcHandler {
function setPrimary(color: color) {
SettingsService.primaryColor = color;
}
target: "colors"
}
IpcHandler {
function toggleCalendar() {
calendarPanel.toggle();
}
function toggleControlCenter() {
controlCenterPanel.toggle();
}
target: "panels"
}
IpcHandler {
function toggleBarLyrics() {
SettingsService.showLyricsBar = !SettingsService.showLyricsBar;
}
target: "lyrics"
}
}

View File

@@ -1,6 +1,7 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Utils
pragma Singleton
Singleton {
@@ -9,28 +10,93 @@ Singleton {
property real fetchInterval: 30 // in s
property real fetchTimeout: 10 // in s
property string ipURL: "https://api.uyanide.com/ip"
property string geoURL: "curl https://api.ipinfo.io/lite/"
property string geoURL: "https://api.ipinfo.io/lite/"
property string geoURLToken: ""
function fetchIP() {
if (fetchIPProcess.running) {
console.warn("Fetch IP process is still running, skipping fetchIP");
return ;
}
fetchIPProcess.running = true;
const xhr = new XMLHttpRequest();
xhr.timeout = fetchTimeout * 1000;
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
if (response && response.ip) {
let newIP = response.ip;
Logger.log("IpService", "Fetched IP: " + newIP);
if (newIP !== ip) {
ip = newIP;
fetchGeoInfo(); // Fetch geo info only if IP has changed
}
} else {
ip = "N/A";
countryCode = "N/A";
Logger.error("IpService", "IP response does not contain 'ip' field");
}
} catch (e) {
ip = "N/A";
countryCode = "N/A";
Logger.error("IpService", "Failed to parse IP response: " + e);
}
} else {
ip = "N/A";
countryCode = "N/A";
Logger.error("IpService", "Failed to fetch IP, status: " + xhr.status);
}
}
};
xhr.ontimeout = function() {
ip = "N/A";
countryCode = "N/A";
Logger.error("IpService", "Fetch IP request timed out");
};
xhr.open("GET", ipURL);
xhr.send();
}
function fetchGeoInfo() {
if (fetchGeoProcess.running) {
console.warn("Fetch geo process is still running, skipping fetchGeoInfo");
return ;
}
if (!ip || ip === "N/A") {
countryCode = "N/A";
return ;
}
fetchGeoProcess.command = ["sh", "-c", `curl -L -m ${fetchTimeout.toString()} ${geoURL}${ip}${geoURLToken ? "?token=" + geoURLToken : ""}`];
fetchGeoProcess.running = true;
const xhr = new XMLHttpRequest();
xhr.timeout = fetchTimeout * 1000;
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
try {
const response = JSON.parse(xhr.responseText);
if (response && response.country) {
let newCountryCode = response.country_code;
Logger.log("IpService", "Fetched country code: " + newCountryCode);
if (newCountryCode !== countryCode) {
countryCode = newCountryCode;
SendNotification.show("New IP", `IP: ${ip}\nCountry: ${newCountryCode}`);
}
} else {
countryCode = "N/A";
Logger.error("IpService", "Geo response does not contain 'country' field");
}
} catch (e) {
countryCode = "N/A";
Logger.error("IpService", "Failed to parse geo response: " + e);
}
} else {
countryCode = "N/A";
Logger.error("IpService", "Failed to fetch geo info, status: " + xhr.status);
}
}
};
xhr.ontimeout = function() {
countryCode = "N/A";
Logger.error("IpService", "Fetch geo info request timed out");
};
let url = geoURL + ip;
if (geoURLToken)
url += "?token=" + geoURLToken;
xhr.open("GET", url);
xhr.send();
}
function refresh() {
@@ -46,11 +112,11 @@ Singleton {
FileView {
id: tokenFile
path: Qt.resolvedUrl("../Assets/Ip/token.txt")
path: Qt.resolvedUrl("../Assets/Config/GeoInfoToken.txt")
onLoaded: {
geoURLToken = tokenFile.text();
if (!geoURLToken)
console.warn("No token found for geoIP service, assuming none is required");
Logger.warn("IpService", "No token found for geoIP service, assuming none is required");
fetchIP();
fetchTimer.start();
@@ -68,64 +134,4 @@ Singleton {
}
}
Process {
id: fetchIPProcess
command: ["sh", "-c", `curl -L -m ${fetchTimeout.toString()} ${ipURL}`]
running: false
stdout: SplitParser {
splitMarker: ""
onRead: (data) => {
let newIP = "";
try {
const response = JSON.parse(data);
if (response && response.ip) {
newIP = response.ip;
console.log("Fetched IP: " + newIP);
}
} catch (e) {
console.error("Failed to parse IP response: " + e);
}
if (newIP && newIP !== ip) {
ip = newIP;
fetchGeoInfo();
} else if (!newIP) {
ip = "N/A";
countryCode = "N/A";
}
}
}
}
Process {
id: fetchGeoProcess
command: []
running: false
stdout: SplitParser {
splitMarker: ""
onRead: (data) => {
let newCountryCode = "";
try {
const response = JSON.parse(data);
if (response && response.country) {
newCountryCode = response.country_code;
console.log("Fetched country code: " + newCountryCode);
SendNotification.show("New IP", `IP: ${ip}\nCountry: ${newCountryCode}`);
}
} catch (e) {
console.error("Failed to parse geo response: " + e);
}
if (newCountryCode && newCountryCode !== countryCode)
countryCode = newCountryCode;
else if (!newCountryCode)
countryCode = "N/A";
}
}
}
}

View File

@@ -0,0 +1,323 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Constants
import qs.Utils
pragma Singleton
// Weather logic and caching with stable UI properties
Singleton {
//console.log(JSON.stringify(weatherData))
id: root
property string locationName: "Munich"
property string locationFile: Qt.resolvedUrl("../Assets/Config/Location.json")
property int weatherUpdateFrequency: 30 * 60 // 30 minutes expressed in seconds
property bool isFetchingWeather: false
readonly property alias data: adapter // Used to access via LocationService.data.xxx from outside, best to use "adapter" inside the service.
// Stable UI properties - only updated when location is fully resolved
property bool coordinatesReady: false
property string stableLatitude: ""
property string stableLongitude: ""
property string stableName: ""
// Helper property for UI components (outside JsonAdapter to avoid binding loops)
readonly property string displayCoordinates: {
if (!root.coordinatesReady || root.stableLatitude === "" || root.stableLongitude === "")
return "";
const lat = parseFloat(root.stableLatitude).toFixed(4);
const lon = parseFloat(root.stableLongitude).toFixed(4);
return `${lat}, ${lon}`;
}
// --------------------------------
function init() {
// does nothing but ensure the singleton is created
// do not remove
Logger.log("Location", "Service started");
}
// --------------------------------
function resetWeather() {
Logger.log("Location", "Resetting weather data");
// Mark as changing to prevent UI updates
root.coordinatesReady = false;
// Reset stable properties
root.stableLatitude = "";
root.stableLongitude = "";
root.stableName = "";
// Reset core data
adapter.latitude = "";
adapter.longitude = "";
adapter.name = "";
adapter.weatherLastFetch = 0;
adapter.weather = null;
// Try to fetch immediately
updateWeather();
}
// --------------------------------
function updateWeather() {
if (isFetchingWeather) {
Logger.warn("Location", "Weather is still fetching");
return ;
}
if ((adapter.weatherLastFetch === "") || (adapter.weather === null) || (adapter.latitude === "") || (adapter.longitude === "") || (adapter.name !== root.locationName) || (Time.timestamp >= adapter.weatherLastFetch + weatherUpdateFrequency))
getFreshWeather();
}
// --------------------------------
function getFreshWeather() {
isFetchingWeather = true;
// Check if location name has changed
const locationChanged = data.name !== root.locationName;
if (locationChanged) {
root.coordinatesReady = false;
Logger.log("Location", "Location changed from", adapter.name, "to", root.locationName);
}
if ((adapter.latitude === "") || (adapter.longitude === "") || locationChanged)
_geocodeLocation(root.locationName, function(latitude, longitude, name, country) {
Logger.log("Location", "Geocoded", root.locationName, "to:", latitude, "/", longitude);
// Save location name
adapter.name = root.locationName;
// Save GPS coordinates
adapter.latitude = latitude.toString();
adapter.longitude = longitude.toString();
root.stableName = `${name}, ${country}`;
_fetchWeather(latitude, longitude, errorCallback);
}, errorCallback);
else
_fetchWeather(adapter.latitude, adapter.longitude, errorCallback);
}
// --------------------------------
function _geocodeLocation(locationName, callback, errorCallback) {
Logger.log("Location", "Geocoding location name");
var geoUrl = "https://assets.noctalia.dev/geocode.php?city=" + encodeURIComponent(locationName) + "&language=en&format=json";
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
try {
var geoData = JSON.parse(xhr.responseText);
if (geoData.lat != null)
callback(geoData.lat, geoData.lng, geoData.name, geoData.country);
else
errorCallback("Location", "could not resolve location name");
} catch (e) {
errorCallback("Location", "Failed to parse geocoding data: " + e);
}
} else {
errorCallback("Location", "Geocoding error: " + xhr.status);
}
}
};
xhr.open("GET", geoUrl);
xhr.send();
}
// --------------------------------
function _fetchWeather(latitude, longitude, errorCallback) {
Logger.log("Location", "Fetching weather from api.open-meteo.com");
var url = "https://api.open-meteo.com/v1/forecast?latitude=" + latitude + "&longitude=" + longitude + "&current_weather=true&current=relativehumidity_2m,surface_pressure&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=auto";
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 200) {
try {
var weatherData = JSON.parse(xhr.responseText);
// Save core data
data.weather = weatherData;
data.weatherLastFetch = Time.timestamp;
// Update stable display values only when complete and successful
root.stableLatitude = data.latitude = weatherData.latitude.toString();
root.stableLongitude = data.longitude = weatherData.longitude.toString();
root.coordinatesReady = true;
isFetchingWeather = false;
Logger.log("Location", "Cached weather to disk - stable coordinates updated");
} catch (e) {
errorCallback("Location", "Failed to parse weather data");
}
} else {
errorCallback("Location", "Weather fetch error: " + xhr.status);
}
}
};
xhr.open("GET", url);
xhr.send();
}
// --------------------------------
function errorCallback(module, message) {
Logger.error(module, message);
isFetchingWeather = false;
}
// --------------------------------
function weatherSymbolFromCode(code) {
if (code === 0)
return "weather-sun";
if (code === 1 || code === 2)
return "weather-cloud-sun";
if (code === 3)
return "weather-cloud";
if (code >= 45 && code <= 48)
return "weather-cloud-haze";
if (code >= 51 && code <= 67)
return "weather-cloud-rain";
if (code >= 71 && code <= 77)
return "weather-cloud-snow";
if (code >= 71 && code <= 77)
return "weather-cloud-snow";
if (code >= 85 && code <= 86)
return "weather-cloud-snow";
if (code >= 95 && code <= 99)
return "weather-cloud-lightning";
return "weather-cloud";
}
function weatherColorFromCode(code) {
// Clear sky - bright yellow
if (code === 0)
return Colors.yellow;
// Mainly clear/Partly cloudy - soft peach/rosewater tones
if (code === 1 || code === 2)
return Colors.peach;
// Overcast - neutral sky blue
if (code === 3)
return Colors.sky;
// Fog - soft lavender/muted tone
if (code >= 45 && code <= 48)
return Colors.lavender;
// Drizzle - light blue/sapphire
if (code >= 51 && code <= 67)
return Colors.sapphire;
// Snow - cool teal
if (code >= 71 && code <= 77)
return Colors.teal;
// Rain showers - deeper blue
if (code >= 80 && code <= 82)
return Colors.blue;
// Snow showers - teal
if (code >= 85 && code <= 86)
return Colors.teal;
// Thunderstorm - dramatic mauve/pink
if (code >= 95 && code <= 99)
return Colors.mauve;
// Default - sky blue
return Colors.sky;
}
// --------------------------------
function weatherDescriptionFromCode(code) {
if (code === 0)
return "Clear sky";
if (code === 1)
return "Mainly clear";
if (code === 2)
return "Partly cloudy";
if (code === 3)
return "Overcast";
if (code === 45 || code === 48)
return "Fog";
if (code >= 51 && code <= 67)
return "Drizzle";
if (code >= 71 && code <= 77)
return "Snow";
if (code >= 80 && code <= 82)
return "Rain showers";
if (code >= 95 && code <= 99)
return "Thunderstorm";
return "Unknown";
}
// --------------------------------
function celsiusToFahrenheit(celsius) {
return 32 + celsius * 1.8;
}
FileView {
id: locationFileView
path: locationFile
printErrors: false
onAdapterUpdated: saveTimer.start()
onLoaded: {
Logger.log("Location", "Loaded cached data");
// Initialize stable properties on load
if (adapter.latitude !== "" && adapter.longitude !== "" && adapter.weatherLastFetch > 0) {
root.stableLatitude = adapter.latitude;
root.stableLongitude = adapter.longitude;
root.stableName = adapter.name;
root.coordinatesReady = true;
Logger.log("Location", "Coordinates ready");
}
updateWeather();
}
onLoadFailed: function(error) {
updateWeather();
}
JsonAdapter {
id: adapter
// Core data properties
property string latitude: ""
property string longitude: ""
property string name: ""
property int weatherLastFetch: 0
property var weather: null
}
}
// Every 20s check if we need to fetch new weather
Timer {
id: updateTimer
interval: 20 * 1000
running: true
repeat: true
onTriggered: {
updateWeather();
}
}
Timer {
id: saveTimer
running: false
interval: 1000
onTriggered: locationFileView.writeAdapter()
}
}

View File

@@ -0,0 +1,144 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Services
import qs.Utils
pragma Singleton
Singleton {
property int linesCount: 3
property int linesAhead: linesCount / 2
property int currentIndex: linesCount - linesAhead - 1
property string offsetFile: Qt.resolvedUrl("../Assets/Config/LyricsOffset.txt")
property int offset: 0 // in ms
property int offsetStep: 500 // in ms
property int referenceCount: 0
// with linesCount=3 and linesAhead=1, lyrics will be like:
// line 1
// line 2 <- current line
// line 3
property var lyrics: Array(linesCount).fill(" ")
function startSyncing() {
referenceCount++;
Logger.log("LyricsService", "Reference count:", referenceCount);
if (referenceCount === 1) {
Logger.log("LyricsService", "Starting lyrics syncing");
// fill lyrics with empty lines
lyrics = Array(linesCount).fill(" ");
listenProcess.exec(["sh", "-c", `sl-wrap listen -l ${linesCount} -a ${linesAhead} -f ${offsetFile.slice(7)}`]);
}
}
function stopSyncing() {
referenceCount--;
Logger.log("LyricsService", "Reference count:", referenceCount);
if (referenceCount <= 0) {
Logger.log("LyricsService", "Stopping lyrics syncing");
// Execute again to stop
// kinda ugly but works, but meanwhile:
// listenProcess.signal(9)
// listenProcess.signal(15)
// listenProcess.running = false
// counts on exec() to terminate previous exec()
// all don't work
listenProcess.exec(["sh", "-c", `sl-wrap trackid`]);
}
}
function writeOffset() {
offsetFileView.setText(String(offset));
}
function increaseOffset() {
offset += offsetStep;
}
function decreaseOffset() {
offset -= offsetStep;
}
function resetOffset() {
offset = 0;
}
function clearCache() {
action.command = ["sh", "-c", "spotify-lyrics clear $(spotify-lyrics trackid)"];
action.startDetached();
}
function showLyricsText() {
action.command = ["sh", "-c", "ghostty -e sh -c 'spotify-lyrics fetch | less'"];
action.startDetached();
}
onOffsetChanged: {
if (SettingsService.showLyricsBar)
SendNotification.show("Lyrics Offset Changed", `Current offset: ${offset} ms`, 1000);
writeOffset();
}
Process {
id: listenProcess
running: false
stdout: SplitParser {
splitMarker: ""
onRead: (data) => {
lyrics = data.split("\n").slice(0, linesCount);
if (lyrics.length < linesCount) {
// fill with empty lines if not enough
for (let i = lyrics.length; i < linesCount; i++) {
lyrics[i] = " ";
}
}
}
}
}
Process {
id: action
running: false
}
FileView {
id: offsetFileView
path: offsetFile
watchChanges: false
onLoaded: {
try {
const fileContents = text();
if (fileContents.length > 0) {
const val = parseInt(fileContents);
if (!isNaN(val)) {
offset = val;
Logger.log("LyricsService", "Loaded offset:", offset);
} else {
offset = 0;
writeOffset();
}
} else {
offset = 0;
writeOffset();
}
} catch (e) {
Logger.log("LyricsService", "Error reading offset file:", e);
}
}
onLoadFailed: {
Logger.log("LyricsService", "Error loading offset file:", errorString);
}
onSaveFailed: {
Logger.log("LyricsService", "Error saving offset file:", errorString);
}
onSaved: {
Logger.log("LyricsService", "Offset file saved.");
}
}
}

View File

@@ -2,6 +2,7 @@ import QtQuick
import Quickshell
import Quickshell.Services.Mpris
import qs.Modules.Misc
import qs.Utils
pragma Singleton
Singleton {
@@ -11,6 +12,7 @@ Singleton {
property var currentPlayer: null
property real currentPosition: 0
property bool isPlaying: currentPlayer ? currentPlayer.isPlaying : false
property int selectedPlayerIndex: -1
property string trackTitle: currentPlayer ? (currentPlayer.trackTitle || "Unknown Track") : ""
property string trackArtist: currentPlayer ? (currentPlayer.trackArtist || "Unknown Artist") : ""
property string trackAlbum: currentPlayer ? (currentPlayer.trackAlbum || "Unknown Album") : ""
@@ -59,11 +61,25 @@ Singleton {
// Updates currentPlayer and currentPosition
function updateCurrentPlayer() {
// Use selected player if index is valid
if (selectedPlayerIndex >= 0) {
let availablePlayers = getAvailablePlayers();
if (selectedPlayerIndex < availablePlayers.length) {
currentPlayer = availablePlayers[selectedPlayerIndex];
currentPosition = currentPlayer.position;
Logger.log("MusicManager", "Current player set by index:", currentPlayer ? currentPlayer.identity : "None");
return ;
} else {
selectedPlayerIndex = -1; // Reset if index is out of range
}
}
// Otherwise, find active player
let newPlayer = findActivePlayer();
if (newPlayer !== currentPlayer) {
currentPlayer = newPlayer;
currentPosition = currentPlayer ? currentPlayer.position : 0;
}
Logger.log("MusicManager", "Current player updated:", currentPlayer ? currentPlayer.identity : "None");
}
// Player control functions

View File

@@ -1,6 +1,7 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Utils
pragma Singleton
pragma ComponentBehavior: Bound
@@ -66,7 +67,7 @@ Singleton {
});
root.workspaces = workspacesList;
} catch (e) {
console.error("Failed to parse workspaces:", e, line);
Logger.error("Niri", "Failed to parse workspaces:", e, line);
}
}
}
@@ -102,7 +103,7 @@ Singleton {
}
root.windows = windowsMap;
} catch (e) {
console.error("Error parsing windows event:", e);
Logger.error("Niri", "Error parsing windows event:", e);
}
} else if (event.WorkspaceActivated) {
workspaceProcess.running = true;
@@ -120,13 +121,13 @@ Singleton {
root.focusedWindowId = -1;
}
} catch (e) {
console.error("Error parsing window focus event:", e);
Logger.error("Niri", "Error parsing window focus event:", e);
}
} else if (event.OverviewOpenedOrClosed) {
try {
root.inOverview = event.OverviewOpenedOrClosed.is_open === true;
} catch (e) {
console.error("Error parsing overview state:", e);
Logger.error("Niri", "Error parsing overview state:", e);
}
} else if (event.WindowOpenedOrChanged) {
try {
@@ -161,7 +162,7 @@ Singleton {
}
} catch (e) {
console.error("Error parsing window opened/changed event:", e);
Logger.error("Niri", "Error parsing window opened/changed event:", e);
}
} else if (event.windowClosed) {
try {
@@ -170,11 +171,11 @@ Singleton {
delete root.windows[closedId];
}
} catch (e) {
console.error("Error parsing window closed event:", e);
Logger.error("Niri", "Error parsing window closed event:", e);
}
}
} catch (e) {
console.error("Error parsing event stream:", e, data);
Logger.error("Niri", "Error parsing event stream:", e, data);
}
}
}

View File

@@ -0,0 +1,88 @@
import QtQuick
import Quickshell
import Quickshell.Services.UPower
import qs.Services
import qs.Utils
pragma Singleton
Singleton {
id: root
readonly property var powerProfiles: PowerProfiles
readonly property bool available: powerProfiles && powerProfiles.hasPerformanceProfile
property int profile: powerProfiles ? powerProfiles.profile : PowerProfile.Balanced
function getName(p) {
if (!available)
return "Unknown";
const prof = (p !== undefined) ? p : profile;
switch (prof) {
case PowerProfile.Performance:
return "Performance";
case PowerProfile.Balanced:
return "Balanced";
case PowerProfile.PowerSaver:
return "Power saver";
default:
return "Unknown";
}
}
function getIcon(p) {
if (!available)
return "balanced";
const prof = (p !== undefined) ? p : profile;
switch (prof) {
case PowerProfile.Performance:
return "performance";
case PowerProfile.Balanced:
return "balanced";
case PowerProfile.PowerSaver:
return "powersaver";
default:
return "balanced";
}
}
function setProfile(p) {
if (!available)
return ;
try {
powerProfiles.profile = p;
} catch (e) {
Logger.error("PowerProfileService", "Failed to set profile:", e);
}
}
function cycleProfile() {
if (!available)
return ;
const current = powerProfiles.profile;
if (current === PowerProfile.Performance)
setProfile(PowerProfile.PowerSaver);
else if (current === PowerProfile.Balanced)
setProfile(PowerProfile.Performance);
else if (current === PowerProfile.PowerSaver)
setProfile(PowerProfile.Balanced);
}
Connections {
function onProfileChanged() {
root.profile = powerProfiles.profile;
// Only show toast if we have a valid profile name (not "Unknown")
const profileName = root.getName();
if (profileName !== "Unknown")
ToastService.showNotice(I18n.tr("toast.power-profile.changed"), I18n.tr("toast.power-profile.profile-name", {
"profile": profileName
}));
}
target: powerProfiles
}
}

View File

@@ -6,7 +6,7 @@ pragma Singleton
Singleton {
id: root
function show(title, message, icon = "", urgency = "normal", timeout = 5000) {
function show(title, message, timeout = 5000, icon = "", urgency = "normal") {
if (icon)
action.command = ["notify-send", "-u", urgency, "-i", icon, "-t", timeout.toString(), title, message];
else

View File

@@ -0,0 +1,35 @@
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Constants
import qs.Services
pragma Singleton
Singleton {
property alias primaryColor: adapter.primaryColor
property alias showLyricsBar: adapter.showLyricsBar
property string settingsFilePath: Qt.resolvedUrl("../Assets/Config/Settings.json")
FileView {
id: settingsFile
path: settingsFilePath
watchChanges: true
onFileChanged: reload()
JsonAdapter {
id: adapter
property string primaryColor: "#89b4fa"
property bool showLyricsBar: false
}
}
Connections {
target: adapter
onPrimaryColorChanged: settingsFile.writeAdapter()
onShowLyricsBarChanged: settingsFile.writeAdapter()
}
}

View File

@@ -2,6 +2,7 @@ import Qt.labs.folderlistmodel
import QtQuick
import Quickshell
import Quickshell.Io
import qs.Utils
pragma Singleton
Singleton {
@@ -222,7 +223,7 @@ Singleton {
}
root.cpuTemp = Math.round(sum / root.intelTempValues.length);
} else {
console.warn("No temperature sensors found for coretemp");
Logger.warn("SystemStatService", "No temperature sensors found for coretemp");
root.cpuTemp = 0;
}
return ;
@@ -328,7 +329,7 @@ Singleton {
function checkNext() {
if (currentIndex >= 16) {
// Check up to hwmon10
console.warn("No supported temperature sensor found");
Logger.warn("SystemStatService", "No supported temperature sensor found");
return ;
}
cpuTempNameReader.path = `/sys/class/hwmon/hwmon${currentIndex}/name`;
@@ -341,7 +342,7 @@ Singleton {
if (root.supportedTempCpuSensorNames.includes(name)) {
root.cpuTempSensorName = name;
root.cpuTempHwmonPath = `/sys/class/hwmon/hwmon${currentIndex}`;
console.log(`Found ${root.cpuTempSensorName} CPU thermal sensor at ${root.cpuTempHwmonPath}`);
Logger.log("SystemStatService", `Found ${root.cpuTempSensorName} CPU thermal sensor at ${root.cpuTempHwmonPath}`);
} else {
currentIndex++;
Qt.callLater(() => {
@@ -370,7 +371,6 @@ Singleton {
if (root.cpuTempSensorName === "coretemp") {
// For Intel, collect all temperature values
const temp = parseInt(data) / 1000;
//console.log(temp, cpuTempReader.path)
root.intelTempValues.push(temp);
Qt.callLater(() => {
// Qt.callLater is mandatory

View File

@@ -5,6 +5,7 @@ import QtQuick
import Quickshell
import Quickshell.Io
import qs.Services
import qs.Utils
Singleton {
id: root
@@ -40,7 +41,7 @@ Singleton {
try {
Quickshell.execDetached(["niri", "msg", "action", "focus-workspace", workspaceId.toString()]);
} catch (e) {
console.error("Error switching Niri workspace:", e);
Logger.error("WorkspaceManager", "Error switching Niri workspace:", e);
}
}