mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-13 07:42:46 -04:00
quickshell: drop support for 0.2, require 0.3+
- Remove all compat code - Rewire LegacyNetworkService to use Quickshell.Networking - Add parentWindow to settings child windows
This commit is contained in:
@@ -202,9 +202,6 @@ func runShellInteractive(session bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ! TODO - remove when QS 0.3 is up and we can use the pragma
|
|
||||||
cmd.Env = append(cmd.Env, "QS_APP_ID=com.danklinux.dms")
|
|
||||||
|
|
||||||
if isSessionManaged && hasSystemdRun() {
|
if isSessionManaged && hasSystemdRun() {
|
||||||
cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope")
|
cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -107,10 +107,6 @@ windowrule = pin on, match:class ^(steam)$, match:title ^(notificationtoasts)
|
|||||||
windowrule = float on, match:class ^(firefox)$, match:title ^(Picture-in-Picture)$
|
windowrule = float on, match:class ^(firefox)$, match:title ^(Picture-in-Picture)$
|
||||||
windowrule = float on, match:class ^(zoom)$
|
windowrule = float on, match:class ^(zoom)$
|
||||||
|
|
||||||
# DMS windows floating by default
|
|
||||||
# ! Hyprland doesn't size these windows correctly so disabling by default here
|
|
||||||
# windowrule = float on, match:class ^(org.quickshell)$
|
|
||||||
|
|
||||||
layerrule = no_anim on, match:namespace ^(quickshell)$
|
layerrule = no_anim on, match:namespace ^(quickshell)$
|
||||||
layerrule = no_anim on, match:namespace ^dms:.*
|
layerrule = no_anim on, match:namespace ^dms:.*
|
||||||
|
|
||||||
|
|||||||
@@ -250,12 +250,6 @@ window-rule {
|
|||||||
match app-id="zoom"
|
match app-id="zoom"
|
||||||
open-floating true
|
open-floating true
|
||||||
}
|
}
|
||||||
// Open dms windows as floating by default
|
|
||||||
window-rule {
|
|
||||||
match app-id=r#"org.quickshell$"#
|
|
||||||
match app-id=r#"com.danklinux.dms$"#
|
|
||||||
open-floating true
|
|
||||||
}
|
|
||||||
debug {
|
debug {
|
||||||
honor-xdg-activation-with-invalid-serial
|
honor-xdg-activation-with-invalid-serial
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -208,8 +208,7 @@ func (a *ArchDistribution) getQuickshellMapping(variant deps.PackageVariant) Pac
|
|||||||
if forceQuickshellGit || variant == deps.VariantGit {
|
if forceQuickshellGit || variant == deps.VariantGit {
|
||||||
return PackageMapping{Name: "quickshell-git", Repository: RepoTypeAUR}
|
return PackageMapping{Name: "quickshell-git", Repository: RepoTypeAUR}
|
||||||
}
|
}
|
||||||
// ! TODO - for now we're only forcing quickshell-git on ARCH, as other distros use DL repos which pin a newer quickshell
|
return PackageMapping{Name: "quickshell", Repository: RepoTypeSystem}
|
||||||
return PackageMapping{Name: "quickshell-git", Repository: RepoTypeAUR}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArchDistribution) getHyprlandMapping(_ deps.PackageVariant) PackageMapping {
|
func (a *ArchDistribution) getHyprlandMapping(_ deps.PackageVariant) PackageMapping {
|
||||||
@@ -332,6 +331,12 @@ func (a *ArchDistribution) InstallPackages(ctx context.Context, dependencies []d
|
|||||||
aurPkgs = slices.DeleteFunc(aurPkgs, func(p string) bool { return p == "quickshell-git" })
|
aurPkgs = slices.DeleteFunc(aurPkgs, func(p string) bool { return p == "quickshell-git" })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if slices.Contains(systemPkgs, "quickshell") && a.packageInstalled("quickshell-git") {
|
||||||
|
if err := a.removeQuickshellGit(ctx, sudoPassword, progressChan); err != nil {
|
||||||
|
return fmt.Errorf("failed to remove quickshell-git: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Phase 3: System Packages
|
// Phase 3: System Packages
|
||||||
if len(systemPkgs) > 0 {
|
if len(systemPkgs) > 0 {
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
@@ -449,6 +454,20 @@ func (a *ArchDistribution) categorizePackages(dependencies []deps.Dependency, wm
|
|||||||
return systemPkgs, aurPkgs, manualPkgs, variantMap
|
return systemPkgs, aurPkgs, manualPkgs, variantMap
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *ArchDistribution) removeQuickshellGit(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
|
progressChan <- InstallProgressMsg{
|
||||||
|
Phase: PhaseSystemPackages,
|
||||||
|
Progress: 0.33,
|
||||||
|
Step: "Removing quickshell-git...",
|
||||||
|
IsComplete: false,
|
||||||
|
NeedsSudo: true,
|
||||||
|
CommandInfo: "sudo pacman -Rdd --noconfirm quickshell-git",
|
||||||
|
LogOutput: "Removing quickshell-git so stable quickshell can be installed",
|
||||||
|
}
|
||||||
|
cmd := privesc.ExecCommand(ctx, sudoPassword, "pacman -Rdd --noconfirm quickshell-git")
|
||||||
|
return a.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.33, 0.35)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *ArchDistribution) preinstallQuickshellGit(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (a *ArchDistribution) preinstallQuickshellGit(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
if a.packageInstalled("quickshell-git") {
|
if a.packageInstalled("quickshell-git") {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ func (b *BaseDistribution) detectQuickshell() deps.Dependency {
|
|||||||
}
|
}
|
||||||
|
|
||||||
versionStr := string(output)
|
versionStr := string(output)
|
||||||
versionRegex := regexp.MustCompile(`quickshell (\d+\.\d+\.\d+)`)
|
versionRegex := regexp.MustCompile(`(?i)quickshell (\d+\.\d+\.\d+)`)
|
||||||
matches := versionRegex.FindStringSubmatch(versionStr)
|
matches := versionRegex.FindStringSubmatch(versionStr)
|
||||||
|
|
||||||
if len(matches) < 2 {
|
if len(matches) < 2 {
|
||||||
|
|||||||
@@ -1,40 +1,29 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
// Clear all image cache
|
|
||||||
function clearImageCache() {
|
function clearImageCache() {
|
||||||
Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)]);
|
Quickshell.execDetached(["rm", "-rf", Paths.stringify(Paths.imagecache)]);
|
||||||
Paths.mkdir(Paths.imagecache);
|
Paths.mkdir(Paths.imagecache);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear cache older than specified minutes
|
|
||||||
function clearOldCache(ageInMinutes) {
|
function clearOldCache(ageInMinutes) {
|
||||||
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"]);
|
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", "*.png", "-mmin", `+${ageInMinutes}`, "-delete"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear cache for specific size
|
|
||||||
function clearCacheForSize(size) {
|
function clearCacheForSize(size) {
|
||||||
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"]);
|
Quickshell.execDetached(["find", Paths.stringify(Paths.imagecache), "-name", `*@${size}x${size}.png`, "-delete"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get cache size in MB
|
|
||||||
function getCacheSize(callback) {
|
function getCacheSize(callback) {
|
||||||
var process = Qt.createQmlObject(`
|
Proc.runCommand("cache_size", ["du", "-sm", Paths.stringify(Paths.imagecache)], function (output, exitCode) {
|
||||||
import Quickshell.Io
|
const sizeMB = parseInt(output.split("\t")[0]) || 0;
|
||||||
Process {
|
callback(sizeMB);
|
||||||
command: ["du", "-sm", "${Paths.stringify(Paths.imagecache)}"]
|
});
|
||||||
running: true
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
var sizeMB = parseInt(text.split("\\t")[0]) || 0
|
|
||||||
callback(sizeMB)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, root);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+26
-13
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
@@ -21,7 +22,7 @@ Singleton {
|
|||||||
const isRandomId = !id;
|
const isRandomId = !id;
|
||||||
|
|
||||||
if (!_procDebouncers[procId]) {
|
if (!_procDebouncers[procId]) {
|
||||||
const t = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root);
|
const t = debounceTimerComp.createObject(root);
|
||||||
t.triggered.connect(function () {
|
t.triggered.connect(function () {
|
||||||
_launchProc(procId, isRandomId);
|
_launchProc(procId, isRandomId);
|
||||||
});
|
});
|
||||||
@@ -49,14 +50,10 @@ Singleton {
|
|||||||
const entry = _procDebouncers[id];
|
const entry = _procDebouncers[id];
|
||||||
if (!entry)
|
if (!entry)
|
||||||
return;
|
return;
|
||||||
const proc = Qt.createQmlObject('import Quickshell.Io; Process { running: false }', root);
|
const proc = procComp.createObject(root, {
|
||||||
const out = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc);
|
command: entry.command
|
||||||
const err = Qt.createQmlObject('import Quickshell.Io; StdioCollector {}', proc);
|
});
|
||||||
const timeoutTimer = Qt.createQmlObject('import QtQuick; Timer { repeat: false }', root);
|
const timeoutTimer = debounceTimerComp.createObject(root);
|
||||||
|
|
||||||
proc.stdout = out;
|
|
||||||
proc.stderr = err;
|
|
||||||
proc.command = entry.command;
|
|
||||||
|
|
||||||
let capturedOut = "";
|
let capturedOut = "";
|
||||||
let capturedErr = "";
|
let capturedErr = "";
|
||||||
@@ -77,9 +74,9 @@ Singleton {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
out.streamFinished.connect(function () {
|
proc.stdout.streamFinished.connect(function () {
|
||||||
try {
|
try {
|
||||||
capturedOut = out.text || "";
|
capturedOut = proc.stdout.text || "";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
capturedOut = "";
|
capturedOut = "";
|
||||||
}
|
}
|
||||||
@@ -87,9 +84,9 @@ Singleton {
|
|||||||
maybeComplete();
|
maybeComplete();
|
||||||
});
|
});
|
||||||
|
|
||||||
err.streamFinished.connect(function () {
|
proc.stderr.streamFinished.connect(function () {
|
||||||
try {
|
try {
|
||||||
capturedErr = err.text || "";
|
capturedErr = proc.stderr.text || "";
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
capturedErr = "";
|
capturedErr = "";
|
||||||
}
|
}
|
||||||
@@ -140,4 +137,20 @@ Singleton {
|
|||||||
if (entry.timeoutMs !== noTimeout)
|
if (entry.timeoutMs !== noTimeout)
|
||||||
timeoutTimer.start();
|
timeoutTimer.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: debounceTimerComp
|
||||||
|
Timer {
|
||||||
|
repeat: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: procComp
|
||||||
|
Process {
|
||||||
|
running: false
|
||||||
|
stdout: StdioCollector {}
|
||||||
|
stderr: StdioCollector {}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -523,6 +523,8 @@ Item {
|
|||||||
enabled: PolkitService.polkitAvailable
|
enabled: PolkitService.polkitAvailable
|
||||||
|
|
||||||
function onAuthenticationRequestStarted() {
|
function onAuthenticationRequestStarted() {
|
||||||
|
if (PopoutService.systemUpdatePopout?.shouldBeVisible)
|
||||||
|
return;
|
||||||
polkitAuthModalLoader.active = true;
|
polkitAuthModalLoader.active = true;
|
||||||
if (polkitAuthModalLoader.item)
|
if (polkitAuthModalLoader.item)
|
||||||
polkitAuthModalLoader.item.show();
|
polkitAuthModalLoader.item.show();
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ FloatingWindow {
|
|||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
visible: windowControls.supported && windowControls.canMaximize
|
visible: windowControls.canMaximize
|
||||||
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
iconColor: Theme.surfaceText
|
iconColor: Theme.surfaceText
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ FloatingWindow {
|
|||||||
property bool saveMode: false
|
property bool saveMode: false
|
||||||
property string defaultFileName: ""
|
property string defaultFileName: ""
|
||||||
property var parentModal: null
|
property var parentModal: null
|
||||||
|
parentWindow: parentModal
|
||||||
property bool shouldHaveFocus: visible
|
property bool shouldHaveFocus: visible
|
||||||
property bool allowFocusOverride: false
|
property bool allowFocusOverride: false
|
||||||
property bool shouldBeVisible: visible
|
property bool shouldBeVisible: visible
|
||||||
|
|||||||
@@ -215,7 +215,7 @@ FloatingWindow {
|
|||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
visible: windowControls.supported && windowControls.canMaximize
|
visible: windowControls.canMaximize
|
||||||
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
iconColor: Theme.surfaceText
|
iconColor: Theme.surfaceText
|
||||||
|
|||||||
@@ -0,0 +1,378 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell.Io
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
import qs.Widgets
|
||||||
|
|
||||||
|
FocusScope {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var currentFlow: PolkitService.agent?.flow
|
||||||
|
property string passwordInput: ""
|
||||||
|
property bool isLoading: false
|
||||||
|
property bool awaitingFprintForPassword: false
|
||||||
|
property var windowControls: null
|
||||||
|
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
|
||||||
|
|
||||||
|
property string polkitEtcPamText: ""
|
||||||
|
property string polkitLibPamText: ""
|
||||||
|
property string systemAuthPamText: ""
|
||||||
|
property string commonAuthPamText: ""
|
||||||
|
property string passwordAuthPamText: ""
|
||||||
|
readonly property bool polkitPamHasFprint: {
|
||||||
|
const polkitText = polkitEtcPamText !== "" ? polkitEtcPamText : polkitLibPamText;
|
||||||
|
if (!polkitText)
|
||||||
|
return false;
|
||||||
|
return pamModuleEnabled(polkitText, "pam_fprintd") || (polkitText.includes("system-auth") && pamModuleEnabled(systemAuthPamText, "pam_fprintd")) || (polkitText.includes("common-auth") && pamModuleEnabled(commonAuthPamText, "pam_fprintd")) || (polkitText.includes("password-auth") && pamModuleEnabled(passwordAuthPamText, "pam_fprintd"));
|
||||||
|
}
|
||||||
|
|
||||||
|
signal closeRequested
|
||||||
|
signal authenticationSucceeded
|
||||||
|
|
||||||
|
focus: true
|
||||||
|
|
||||||
|
Keys.onEscapePressed: event => {
|
||||||
|
cancelAuth();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripPamComment(line) {
|
||||||
|
if (!line)
|
||||||
|
return "";
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (!trimmed || trimmed.startsWith("#"))
|
||||||
|
return "";
|
||||||
|
const hashIdx = trimmed.indexOf("#");
|
||||||
|
if (hashIdx >= 0)
|
||||||
|
return trimmed.substring(0, hashIdx).trim();
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pamModuleEnabled(pamText, moduleName) {
|
||||||
|
if (!pamText || !moduleName)
|
||||||
|
return false;
|
||||||
|
const lines = pamText.split(/\r?\n/);
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const line = stripPamComment(lines[i]);
|
||||||
|
if (line && line.includes(moduleName))
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusPasswordField() {
|
||||||
|
passwordField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function reset() {
|
||||||
|
passwordInput = "";
|
||||||
|
isLoading = false;
|
||||||
|
awaitingFprintForPassword = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _commitSubmit() {
|
||||||
|
isLoading = true;
|
||||||
|
awaitingFprintForPassword = false;
|
||||||
|
currentFlow.submit(passwordInput);
|
||||||
|
passwordInput = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function submitAuth() {
|
||||||
|
if (!currentFlow || isLoading)
|
||||||
|
return;
|
||||||
|
if (!currentFlow.isResponseRequired) {
|
||||||
|
awaitingFprintForPassword = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_commitSubmit();
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelAuth() {
|
||||||
|
if (isLoading)
|
||||||
|
return;
|
||||||
|
awaitingFprintForPassword = false;
|
||||||
|
if (currentFlow) {
|
||||||
|
currentFlow.cancelAuthenticationRequest();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
closeRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.currentFlow
|
||||||
|
enabled: root.currentFlow !== null
|
||||||
|
|
||||||
|
function onIsResponseRequiredChanged() {
|
||||||
|
if (!root.currentFlow.isResponseRequired)
|
||||||
|
return;
|
||||||
|
if (root.awaitingFprintForPassword && root.passwordInput !== "") {
|
||||||
|
root._commitSubmit();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root.awaitingFprintForPassword = false;
|
||||||
|
root.isLoading = false;
|
||||||
|
root.passwordInput = "";
|
||||||
|
passwordField.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAuthenticationSucceeded() {
|
||||||
|
root.authenticationSucceeded();
|
||||||
|
root.closeRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAuthenticationFailed() {
|
||||||
|
root.isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onAuthenticationRequestCancelled() {
|
||||||
|
root.closeRequested();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: "/etc/pam.d/polkit-1"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.polkitEtcPamText = text()
|
||||||
|
onLoadFailed: root.polkitEtcPamText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: "/usr/lib/pam.d/polkit-1"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.polkitLibPamText = text()
|
||||||
|
onLoadFailed: root.polkitLibPamText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: "/etc/pam.d/system-auth"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.systemAuthPamText = text()
|
||||||
|
onLoadFailed: root.systemAuthPamText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: "/etc/pam.d/common-auth"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.commonAuthPamText = text()
|
||||||
|
onLoadFailed: root.commonAuthPamText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
FileView {
|
||||||
|
path: "/etc/pam.d/password-auth"
|
||||||
|
printErrors: false
|
||||||
|
onLoaded: root.passwordAuthPamText = text()
|
||||||
|
onLoadFailed: root.passwordAuthPamText = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: headerSection
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
height: Math.max(titleColumn.implicitHeight, windowButtonRow.implicitHeight)
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: root.windowControls !== null
|
||||||
|
onPressed: {
|
||||||
|
if (root.windowControls)
|
||||||
|
root.windowControls.tryStartMove();
|
||||||
|
}
|
||||||
|
onDoubleClicked: {
|
||||||
|
if (root.windowControls)
|
||||||
|
root.windowControls.tryToggleMaximize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: titleColumn
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: windowButtonRow.left
|
||||||
|
anchors.rightMargin: Theme.spacingM
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Authentication Required")
|
||||||
|
font.pixelSize: Theme.fontSizeLarge
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.currentFlow?.message ?? ""
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceTextMedium
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
maximumLineCount: 2
|
||||||
|
elide: Text.ElideRight
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.currentFlow?.supplementaryMessage ?? ""
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: (root.currentFlow?.supplementaryIsError ?? false) ? Theme.error : Theme.surfaceTextMedium
|
||||||
|
width: parent.width
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
maximumLineCount: 2
|
||||||
|
elide: Text.ElideRight
|
||||||
|
opacity: (root.currentFlow?.supplementaryIsError ?? false) ? 1 : 0.8
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: windowButtonRow
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
visible: root.windowControls?.supported === true && root.windowControls?.canMaximize === true
|
||||||
|
iconName: (root.windowControls?.targetWindow?.maximized ?? false) ? "fullscreen_exit" : "fullscreen"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
onClicked: {
|
||||||
|
if (root.windowControls)
|
||||||
|
root.windowControls.tryToggleMaximize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankActionButton {
|
||||||
|
iconName: "close"
|
||||||
|
iconSize: Theme.iconSize - 4
|
||||||
|
iconColor: Theme.surfaceText
|
||||||
|
enabled: !root.isLoading
|
||||||
|
opacity: enabled ? 1 : 0.5
|
||||||
|
onClicked: root.cancelAuth()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
id: bottomSection
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.margins: Theme.spacingM
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: root.currentFlow?.inputPrompt ?? ""
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
visible: text !== ""
|
||||||
|
}
|
||||||
|
|
||||||
|
DankTextField {
|
||||||
|
id: passwordField
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: root.inputFieldHeight
|
||||||
|
backgroundColor: Theme.surfaceHover
|
||||||
|
normalBorderColor: Theme.outlineStrong
|
||||||
|
focusedBorderColor: Theme.primary
|
||||||
|
borderWidth: 1
|
||||||
|
focusedBorderWidth: 2
|
||||||
|
leftIconName: root.polkitPamHasFprint ? "fingerprint" : ""
|
||||||
|
leftIconSize: 20
|
||||||
|
leftIconColor: Theme.primary
|
||||||
|
leftIconFocusedColor: Theme.primary
|
||||||
|
opacity: root.isLoading ? 0.5 : 1
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
textColor: Theme.surfaceText
|
||||||
|
text: root.passwordInput
|
||||||
|
showPasswordToggle: !(root.currentFlow?.responseVisible ?? false)
|
||||||
|
echoMode: (root.currentFlow?.responseVisible ?? false) || passwordVisible ? TextInput.Normal : TextInput.Password
|
||||||
|
placeholderText: ""
|
||||||
|
enabled: !root.isLoading
|
||||||
|
onTextEdited: root.passwordInput = text
|
||||||
|
onAccepted: root.submitAuth()
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Authentication failed, please try again")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: Theme.error
|
||||||
|
width: parent.width
|
||||||
|
visible: root.currentFlow?.failed ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: parent.width
|
||||||
|
height: 36
|
||||||
|
|
||||||
|
Row {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
|
||||||
|
height: 36
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||||
|
border.color: Theme.surfaceVariantAlpha
|
||||||
|
border.width: 1
|
||||||
|
enabled: !root.isLoading
|
||||||
|
opacity: enabled ? 1 : 0.5
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: cancelText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: I18n.tr("Cancel")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: cancelArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
enabled: parent.enabled
|
||||||
|
onClicked: root.cancelAuth()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Math.max(80, authText.contentWidth + Theme.spacingM * 2)
|
||||||
|
height: 36
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: authArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
||||||
|
enabled: !root.isLoading
|
||||||
|
opacity: enabled ? 1 : 0.5
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: authText
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: I18n.tr("Authenticate")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
color: Theme.background
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: authArea
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
enabled: parent.enabled
|
||||||
|
onClicked: root.submitAuth()
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on color {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: Theme.shortDuration
|
||||||
|
easing.type: Theme.standardEasing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,427 +1,68 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
PanelWindow {
|
FloatingWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool disablePopupTransparency: true
|
property bool disablePopupTransparency: true
|
||||||
property string passwordInput: ""
|
|
||||||
property var currentFlow: PolkitService.agent?.flow
|
|
||||||
property bool isLoading: false
|
|
||||||
property bool awaitingFprintForPassword: false
|
|
||||||
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
|
|
||||||
|
|
||||||
property string polkitEtcPamText: ""
|
function show() {
|
||||||
property string polkitLibPamText: ""
|
if (contentLoader.item)
|
||||||
property string systemAuthPamText: ""
|
contentLoader.item.reset();
|
||||||
property string commonAuthPamText: ""
|
|
||||||
property string passwordAuthPamText: ""
|
|
||||||
readonly property bool polkitPamHasFprint: {
|
|
||||||
const polkitText = polkitEtcPamText !== "" ? polkitEtcPamText : polkitLibPamText;
|
|
||||||
if (!polkitText)
|
|
||||||
return false;
|
|
||||||
return pamModuleEnabled(polkitText, "pam_fprintd") || (polkitText.includes("system-auth") && pamModuleEnabled(systemAuthPamText, "pam_fprintd")) || (polkitText.includes("common-auth") && pamModuleEnabled(commonAuthPamText, "pam_fprintd")) || (polkitText.includes("password-auth") && pamModuleEnabled(passwordAuthPamText, "pam_fprintd"));
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripPamComment(line) {
|
|
||||||
if (!line)
|
|
||||||
return "";
|
|
||||||
const trimmed = line.trim();
|
|
||||||
if (!trimmed || trimmed.startsWith("#"))
|
|
||||||
return "";
|
|
||||||
const hashIdx = trimmed.indexOf("#");
|
|
||||||
if (hashIdx >= 0)
|
|
||||||
return trimmed.substring(0, hashIdx).trim();
|
|
||||||
return trimmed;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pamModuleEnabled(pamText, moduleName) {
|
|
||||||
if (!pamText || !moduleName)
|
|
||||||
return false;
|
|
||||||
const lines = pamText.split(/\r?\n/);
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const line = stripPamComment(lines[i]);
|
|
||||||
if (line && line.includes(moduleName))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function focusPasswordField() {
|
|
||||||
passwordField.forceActiveFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function show(targetScreen) {
|
|
||||||
if (targetScreen)
|
|
||||||
screen = targetScreen;
|
|
||||||
passwordInput = "";
|
|
||||||
isLoading = false;
|
|
||||||
awaitingFprintForPassword = false;
|
|
||||||
visible = true;
|
visible = true;
|
||||||
Qt.callLater(focusPasswordField);
|
Qt.callLater(focusContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
visible = false;
|
visible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _commitSubmit() {
|
function focusContent() {
|
||||||
isLoading = true;
|
if (contentLoader.item)
|
||||||
awaitingFprintForPassword = false;
|
contentLoader.item.focusPasswordField();
|
||||||
currentFlow.submit(passwordInput);
|
|
||||||
passwordInput = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function submitAuth() {
|
|
||||||
if (!currentFlow || isLoading)
|
|
||||||
return;
|
|
||||||
if (!currentFlow.isResponseRequired) {
|
|
||||||
awaitingFprintForPassword = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_commitSubmit();
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancelAuth() {
|
|
||||||
if (isLoading)
|
|
||||||
return;
|
|
||||||
awaitingFprintForPassword = false;
|
|
||||||
if (currentFlow) {
|
|
||||||
currentFlow.cancelAuthenticationRequest();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hide();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
objectName: "polkitAuthModal"
|
objectName: "polkitAuthModal"
|
||||||
|
title: I18n.tr("Authentication")
|
||||||
screen: Quickshell.screens[0]
|
minimumSize: Qt.size(460, 220)
|
||||||
color: "transparent"
|
maximumSize: Qt.size(460, 220)
|
||||||
|
color: Theme.surfaceContainer
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:polkit-auth"
|
|
||||||
WlrLayershell.layer: WlrLayershell.Overlay
|
|
||||||
WlrLayershell.exclusiveZone: -1
|
|
||||||
WlrLayershell.keyboardFocus: visible ? WlrKeyboardFocus.Exclusive : WlrKeyboardFocus.None
|
|
||||||
|
|
||||||
anchors {
|
|
||||||
left: true
|
|
||||||
top: true
|
|
||||||
right: true
|
|
||||||
bottom: true
|
|
||||||
}
|
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
Qt.callLater(focusPasswordField);
|
Qt.callLater(focusContent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
passwordInput = "";
|
if (contentLoader.item)
|
||||||
isLoading = false;
|
contentLoader.item.reset();
|
||||||
awaitingFprintForPassword = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: PolkitService.agent
|
target: PolkitService.agent
|
||||||
enabled: PolkitService.polkitAvailable
|
enabled: PolkitService.polkitAvailable
|
||||||
|
|
||||||
function onAuthenticationRequestStarted() {
|
|
||||||
show();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onIsActiveChanged() {
|
function onIsActiveChanged() {
|
||||||
if (!(PolkitService.agent?.isActive ?? false))
|
if (!(PolkitService.agent?.isActive ?? false))
|
||||||
hide();
|
root.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Loader {
|
||||||
target: currentFlow
|
id: contentLoader
|
||||||
enabled: currentFlow !== null
|
|
||||||
|
|
||||||
function onIsResponseRequiredChanged() {
|
|
||||||
if (!currentFlow.isResponseRequired)
|
|
||||||
return;
|
|
||||||
if (awaitingFprintForPassword && passwordInput !== "") {
|
|
||||||
_commitSubmit();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
awaitingFprintForPassword = false;
|
|
||||||
isLoading = false;
|
|
||||||
passwordInput = "";
|
|
||||||
passwordField.forceActiveFocus();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAuthenticationSucceeded() {
|
|
||||||
hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAuthenticationFailed() {
|
|
||||||
isLoading = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function onAuthenticationRequestCancelled() {
|
|
||||||
hide();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
path: "/etc/pam.d/polkit-1"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: root.polkitEtcPamText = text()
|
|
||||||
onLoadFailed: root.polkitEtcPamText = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
path: "/usr/lib/pam.d/polkit-1"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: root.polkitLibPamText = text()
|
|
||||||
onLoadFailed: root.polkitLibPamText = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
path: "/etc/pam.d/system-auth"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: root.systemAuthPamText = text()
|
|
||||||
onLoadFailed: root.systemAuthPamText = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
path: "/etc/pam.d/common-auth"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: root.commonAuthPamText = text()
|
|
||||||
onLoadFailed: root.commonAuthPamText = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
FileView {
|
|
||||||
path: "/etc/pam.d/password-auth"
|
|
||||||
printErrors: false
|
|
||||||
onLoaded: root.passwordAuthPamText = text()
|
|
||||||
onLoadFailed: root.passwordAuthPamText = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dim overlay — clicking outside cancels authentication
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: Qt.rgba(0, 0, 0, 0.45)
|
active: root.visible
|
||||||
|
sourceComponent: PolkitAuthContent {
|
||||||
MouseArea {
|
windowControls: windowControls
|
||||||
anchors.fill: parent
|
onCloseRequested: root.hide()
|
||||||
onClicked: cancelAuth()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Centered dialog box
|
FloatingWindowControls {
|
||||||
Rectangle {
|
id: windowControls
|
||||||
id: dialogBox
|
targetWindow: root
|
||||||
width: 460
|
|
||||||
height: 220
|
|
||||||
anchors.centerIn: parent
|
|
||||||
color: Theme.surfaceContainer
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
|
|
||||||
FocusScope {
|
|
||||||
id: contentFocusScope
|
|
||||||
anchors.fill: parent
|
|
||||||
focus: true
|
|
||||||
|
|
||||||
Keys.onEscapePressed: event => {
|
|
||||||
cancelAuth();
|
|
||||||
event.accepted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
id: headerSection
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.margins: Theme.spacingM
|
|
||||||
height: Math.max(titleColumn.implicitHeight, windowButtonRow.implicitHeight)
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: titleColumn
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: windowButtonRow.left
|
|
||||||
anchors.rightMargin: Theme.spacingM
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Authentication Required")
|
|
||||||
font.pixelSize: Theme.fontSizeLarge
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: currentFlow?.message ?? ""
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceTextMedium
|
|
||||||
width: parent.width
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
maximumLineCount: 2
|
|
||||||
elide: Text.ElideRight
|
|
||||||
visible: text !== ""
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: currentFlow?.supplementaryMessage ?? ""
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: (currentFlow?.supplementaryIsError ?? false) ? Theme.error : Theme.surfaceTextMedium
|
|
||||||
width: parent.width
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
maximumLineCount: 2
|
|
||||||
elide: Text.ElideRight
|
|
||||||
opacity: (currentFlow?.supplementaryIsError ?? false) ? 1 : 0.8
|
|
||||||
visible: text !== ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
|
||||||
id: windowButtonRow
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
spacing: Theme.spacingXS
|
|
||||||
|
|
||||||
DankActionButton {
|
|
||||||
iconName: "close"
|
|
||||||
iconSize: Theme.iconSize - 4
|
|
||||||
iconColor: Theme.surfaceText
|
|
||||||
enabled: !isLoading
|
|
||||||
opacity: enabled ? 1 : 0.5
|
|
||||||
onClicked: cancelAuth()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
id: bottomSection
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.margins: Theme.spacingM
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: currentFlow?.inputPrompt ?? ""
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
width: parent.width
|
|
||||||
visible: text !== ""
|
|
||||||
}
|
|
||||||
|
|
||||||
DankTextField {
|
|
||||||
id: passwordField
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: inputFieldHeight
|
|
||||||
backgroundColor: Theme.surfaceHover
|
|
||||||
normalBorderColor: Theme.outlineStrong
|
|
||||||
focusedBorderColor: Theme.primary
|
|
||||||
borderWidth: 1
|
|
||||||
focusedBorderWidth: 2
|
|
||||||
leftIconName: polkitPamHasFprint ? "fingerprint" : ""
|
|
||||||
leftIconSize: 20
|
|
||||||
leftIconColor: Theme.primary
|
|
||||||
leftIconFocusedColor: Theme.primary
|
|
||||||
opacity: isLoading ? 0.5 : 1
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
textColor: Theme.surfaceText
|
|
||||||
text: passwordInput
|
|
||||||
showPasswordToggle: !(currentFlow?.responseVisible ?? false)
|
|
||||||
echoMode: (currentFlow?.responseVisible ?? false) || passwordVisible ? TextInput.Normal : TextInput.Password
|
|
||||||
placeholderText: ""
|
|
||||||
enabled: !isLoading
|
|
||||||
onTextEdited: passwordInput = text
|
|
||||||
onAccepted: submitAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Authentication failed, please try again")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.error
|
|
||||||
width: parent.width
|
|
||||||
visible: currentFlow?.failed ?? false
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
width: parent.width
|
|
||||||
height: 36
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingM
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Math.max(70, cancelText.contentWidth + Theme.spacingM * 2)
|
|
||||||
height: 36
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: cancelArea.containsMouse ? Theme.surfaceTextHover : "transparent"
|
|
||||||
border.color: Theme.surfaceVariantAlpha
|
|
||||||
border.width: 1
|
|
||||||
enabled: !isLoading
|
|
||||||
opacity: enabled ? 1 : 0.5
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: cancelText
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: I18n.tr("Cancel")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.surfaceText
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: cancelArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
enabled: parent.enabled
|
|
||||||
onClicked: cancelAuth()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: Math.max(80, authText.contentWidth + Theme.spacingM * 2)
|
|
||||||
height: 36
|
|
||||||
radius: Theme.cornerRadius
|
|
||||||
color: authArea.containsMouse ? Qt.darker(Theme.primary, 1.1) : Theme.primary
|
|
||||||
enabled: !isLoading
|
|
||||||
opacity: enabled ? 1 : 0.5
|
|
||||||
|
|
||||||
StyledText {
|
|
||||||
id: authText
|
|
||||||
anchors.centerIn: parent
|
|
||||||
text: I18n.tr("Authenticate")
|
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
|
||||||
color: Theme.background
|
|
||||||
font.weight: Font.Medium
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
id: authArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
enabled: parent.enabled
|
|
||||||
onClicked: submitAuth()
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on color {
|
|
||||||
ColorAnimation {
|
|
||||||
duration: Theme.shortDuration
|
|
||||||
easing.type: Theme.standardEasing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import QtQuick
|
||||||
|
import Quickshell.Wayland
|
||||||
|
import qs.Common
|
||||||
|
import qs.Modals.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
|
DankModal {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var parentPopout: null
|
||||||
|
|
||||||
|
layerNamespace: "dms:polkit-auth-surface"
|
||||||
|
modalWidth: 460
|
||||||
|
modalHeight: 220
|
||||||
|
backgroundColor: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
|
closeOnEscapeKey: true
|
||||||
|
closeOnBackgroundClick: false
|
||||||
|
allowStacking: true
|
||||||
|
keepPopoutsOpen: true
|
||||||
|
|
||||||
|
onOpened: {
|
||||||
|
if (parentPopout)
|
||||||
|
parentPopout.customKeyboardFocus = WlrKeyboardFocus.None;
|
||||||
|
Qt.callLater(() => {
|
||||||
|
if (contentLoader.item) {
|
||||||
|
contentLoader.item.reset();
|
||||||
|
contentLoader.item.focusPasswordField();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDialogClosed: {
|
||||||
|
if (parentPopout)
|
||||||
|
parentPopout.customKeyboardFocus = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: PolkitService.agent
|
||||||
|
enabled: PolkitService.polkitAvailable
|
||||||
|
|
||||||
|
function onIsActiveChanged() {
|
||||||
|
if (!(PolkitService.agent?.isActive ?? false))
|
||||||
|
root.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content: PolkitAuthContent {
|
||||||
|
focus: true
|
||||||
|
onCloseRequested: root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -274,7 +274,7 @@ FloatingWindow {
|
|||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
visible: windowControls.supported
|
visible: windowControls.canMaximize
|
||||||
circular: false
|
circular: false
|
||||||
iconName: processListModal.maximized ? "fullscreen_exit" : "fullscreen"
|
iconName: processListModal.maximized ? "fullscreen_exit" : "fullscreen"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ FloatingWindow {
|
|||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
visible: windowControls.supported
|
visible: windowControls.canMaximize
|
||||||
circular: false
|
circular: false
|
||||||
iconName: settingsModal.maximized ? "fullscreen_exit" : "fullscreen"
|
iconName: settingsModal.maximized ? "fullscreen_exit" : "fullscreen"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
|
|||||||
@@ -381,7 +381,7 @@ FloatingWindow {
|
|||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
visible: windowControls.supported && windowControls.canMaximize
|
visible: windowControls.canMaximize
|
||||||
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
iconColor: Theme.surfaceText
|
iconColor: Theme.surfaceText
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -97,7 +96,7 @@ FloatingWindow {
|
|||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
visible: windowControls.supported && windowControls.canMaximize
|
visible: windowControls.canMaximize
|
||||||
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
iconColor: Theme.surfaceText
|
iconColor: Theme.surfaceText
|
||||||
@@ -213,5 +212,4 @@ FloatingWindow {
|
|||||||
id: windowControls
|
id: windowControls
|
||||||
targetWindow: root
|
targetWindow: root
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,8 +85,7 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (typeof blurWallpaperWindow.updatesEnabled !== "undefined")
|
blurWallpaperWindow.updatesEnabled = Qt.binding(() => !root.source || root.effectActive || root._renderSettling || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
||||||
blurWallpaperWindow.updatesEnabled = Qt.binding(() => !root.source || root.effectActive || root._renderSettling || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
|
||||||
isInitialized = true;
|
isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -152,6 +152,20 @@ PanelWindow {
|
|||||||
onTriggered: barBlur.rebuild()
|
onTriggered: barBlur.rebuild()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: blurRegionComp
|
||||||
|
Region {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: blurSubRegionComp
|
||||||
|
Region {
|
||||||
|
property Item w
|
||||||
|
item: w
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: barBlur
|
id: barBlur
|
||||||
visible: false
|
visible: false
|
||||||
@@ -173,33 +187,32 @@ PanelWindow {
|
|||||||
if (!hasBar && widgets.length === 0)
|
if (!hasBar && widgets.length === 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const cr = Theme.cornerRadius;
|
const region = blurRegionComp.createObject(barWindow);
|
||||||
let qml = 'import QtQuick; import Quickshell; Region {';
|
if (!region) {
|
||||||
|
log.warn("BarBlur: Failed to create blur region");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasBar) {
|
||||||
|
region.x = Qt.binding(() => topBarMouseArea.x + barUnitInset.x + topBarSlide.x);
|
||||||
|
region.y = Qt.binding(() => topBarMouseArea.y + barUnitInset.y + topBarSlide.y);
|
||||||
|
region.width = Qt.binding(() => barUnitInset.width);
|
||||||
|
region.height = Qt.binding(() => barUnitInset.height);
|
||||||
|
region.radius = Qt.binding(() => barBackground.rt);
|
||||||
|
}
|
||||||
|
|
||||||
|
const subRegions = [];
|
||||||
for (let i = 0; i < widgets.length; i++) {
|
for (let i = 0; i < widgets.length; i++) {
|
||||||
qml += ` property Item w${i}; Region { item: w${i}; radius: ${cr} }`;
|
const sub = blurSubRegionComp.createObject(region, {
|
||||||
|
w: widgets[i]
|
||||||
|
});
|
||||||
|
if (sub)
|
||||||
|
subRegions.push(sub);
|
||||||
}
|
}
|
||||||
qml += '}';
|
region.regions = subRegions;
|
||||||
|
|
||||||
try {
|
barWindow.BackgroundEffect.blurRegion = region;
|
||||||
const region = Qt.createQmlObject(qml, barWindow, "BarBlurRegion");
|
barWindow.blurRegion = region;
|
||||||
|
|
||||||
if (hasBar) {
|
|
||||||
region.x = Qt.binding(() => topBarMouseArea.x + barUnitInset.x + topBarSlide.x);
|
|
||||||
region.y = Qt.binding(() => topBarMouseArea.y + barUnitInset.y + topBarSlide.y);
|
|
||||||
region.width = Qt.binding(() => barUnitInset.width);
|
|
||||||
region.height = Qt.binding(() => barUnitInset.height);
|
|
||||||
region.radius = Qt.binding(() => barBackground.rt);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < widgets.length; i++) {
|
|
||||||
region[`w${i}`] = widgets[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
barWindow.BackgroundEffect.blurRegion = region;
|
|
||||||
barWindow.blurRegion = region;
|
|
||||||
} catch (e) {
|
|
||||||
log.warn("BarBlur: Failed to create blur region:", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function teardown() {
|
function teardown() {
|
||||||
@@ -529,27 +542,17 @@ PanelWindow {
|
|||||||
implicitWidth: isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
|
implicitWidth: isVertical ? Theme.px(effectiveBarThickness + effectiveSpacing + ((barConfig?.gothCornersEnabled ?? false) && !hasMaximizedToplevel ? _wingR : 0), _dpr) + _shadowBuffer : 0
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
property var nativeInhibitor: null
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
updateGpuTempConfig();
|
updateGpuTempConfig();
|
||||||
_updateBackgroundAlpha();
|
_updateBackgroundAlpha();
|
||||||
_updateHasMaximizedToplevel();
|
_updateHasMaximizedToplevel();
|
||||||
_updateHasFullscreenToplevel();
|
_updateHasFullscreenToplevel();
|
||||||
_updateShouldHideForWindows();
|
_updateShouldHideForWindows();
|
||||||
|
|
||||||
inhibitorInitTimer.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
IdleInhibitor {
|
||||||
id: inhibitorInitTimer
|
window: barWindow
|
||||||
interval: 300
|
enabled: SessionService.idleInhibited
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (SessionService.nativeInhibitorAvailable) {
|
|
||||||
createNativeInhibitor();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -581,35 +584,6 @@ PanelWindow {
|
|||||||
DgopService.nonNvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nonNvidiaGpuTempEnabled;
|
DgopService.nonNvidiaGpuTempEnabled = hasGpuTempWidget || SessionData.nonNvidiaGpuTempEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createNativeInhibitor() {
|
|
||||||
if (!SessionService.nativeInhibitorAvailable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const qmlString = `
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell.Wayland
|
|
||||||
|
|
||||||
IdleInhibitor {
|
|
||||||
enabled: false
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
nativeInhibitor = Qt.createQmlObject(qmlString, barWindow, "DankBar.NativeInhibitor");
|
|
||||||
nativeInhibitor.window = barWindow;
|
|
||||||
nativeInhibitor.enabled = Qt.binding(() => SessionService.idleInhibited);
|
|
||||||
nativeInhibitor.enabledChanged.connect(function () {
|
|
||||||
if (SessionService.idleInhibited !== nativeInhibitor.enabled) {
|
|
||||||
SessionService.idleInhibited = nativeInhibitor.enabled;
|
|
||||||
SessionService.inhibitorChanged();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
nativeInhibitor = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
function onBarConfigChanged() {
|
function onBarConfigChanged() {
|
||||||
barWindow.updateGpuTempConfig();
|
barWindow.updateGpuTempConfig();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Modals
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
@@ -18,9 +19,23 @@ DankPopout {
|
|||||||
|
|
||||||
property bool _reopenAfterUpgrade: false
|
property bool _reopenAfterUpgrade: false
|
||||||
|
|
||||||
readonly property bool polkitModalOpen: PopoutService.polkitAuthModal?.visible ?? false
|
readonly property bool polkitModalOpen: polkitAuthSurfaceModal.shouldBeVisible
|
||||||
readonly property bool anyModalOpen: polkitModalOpen
|
readonly property bool anyModalOpen: polkitModalOpen
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: PolkitService.agent
|
||||||
|
enabled: PolkitService.polkitAvailable && systemUpdatePopout.shouldBeVisible
|
||||||
|
|
||||||
|
function onAuthenticationRequestStarted() {
|
||||||
|
polkitAuthSurfaceModal.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PolkitAuthSurfaceModal {
|
||||||
|
id: polkitAuthSurfaceModal
|
||||||
|
parentPopout: systemUpdatePopout
|
||||||
|
}
|
||||||
|
|
||||||
backgroundInteractive: !anyModalOpen
|
backgroundInteractive: !anyModalOpen
|
||||||
|
|
||||||
customKeyboardFocus: {
|
customKeyboardFocus: {
|
||||||
@@ -33,16 +48,6 @@ DankPopout {
|
|||||||
return WlrKeyboardFocus.Exclusive;
|
return WlrKeyboardFocus.Exclusive;
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: PolkitService.agent
|
|
||||||
enabled: PolkitService.polkitAvailable && triggerScreen !== null
|
|
||||||
|
|
||||||
function onAuthenticationRequestStarted() {
|
|
||||||
if (PopoutService.polkitAuthModal && triggerScreen)
|
|
||||||
PopoutService.polkitAuthModal.screen = triggerScreen;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SystemUpdateService
|
target: SystemUpdateService
|
||||||
function onIsUpgradingChanged() {
|
function onIsUpgradingChanged() {
|
||||||
|
|||||||
@@ -1397,6 +1397,13 @@ BasePill {
|
|||||||
close();
|
close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: pendingActionCloseTimer
|
||||||
|
interval: 80
|
||||||
|
repeat: false
|
||||||
|
onTriggered: menuRoot.closeWithAction()
|
||||||
|
}
|
||||||
|
|
||||||
function showSubMenu(entry) {
|
function showSubMenu(entry) {
|
||||||
if (!entry || !entry.hasChildren)
|
if (!entry || !entry.hasChildren)
|
||||||
return;
|
return;
|
||||||
@@ -1853,7 +1860,7 @@ BasePill {
|
|||||||
} else if (typeof menuEntry.triggered === "function") {
|
} else if (typeof menuEntry.triggered === "function") {
|
||||||
menuEntry.triggered();
|
menuEntry.triggered();
|
||||||
}
|
}
|
||||||
Qt.createQmlObject('import QtQuick; Timer { interval: 80; running: true; repeat: false; onTriggered: menuRoot.closeWithAction() }', menuRoot);
|
pendingActionCloseTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ PanelWindow {
|
|||||||
|
|
||||||
screen: targetScreen
|
screen: targetScreen
|
||||||
visible: _frameActive
|
visible: _frameActive
|
||||||
|
updatesEnabled: _connectedActive
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:frame"
|
WlrLayershell.namespace: "dms:frame"
|
||||||
WlrLayershell.layer: WlrLayer.Top
|
WlrLayershell.layer: WlrLayer.Top
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import QtQuick
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool active: false
|
property bool active: false
|
||||||
|
property bool _completed: false
|
||||||
|
|
||||||
signal fadeCompleted
|
signal fadeCompleted
|
||||||
signal fadeCancelled
|
signal fadeCancelled
|
||||||
@@ -35,7 +37,8 @@ PanelWindow {
|
|||||||
opacity: 0
|
opacity: 0
|
||||||
|
|
||||||
onOpacityChanged: {
|
onOpacityChanged: {
|
||||||
if (opacity >= 0.99 && root.active) {
|
if (opacity >= 0.99 && root.active && !root._completed) {
|
||||||
|
root._completed = true;
|
||||||
root.fadeCompleted();
|
root.fadeCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,6 +61,7 @@ PanelWindow {
|
|||||||
function startFade() {
|
function startFade() {
|
||||||
if (!SettingsData.fadeToLockEnabled)
|
if (!SettingsData.fadeToLockEnabled)
|
||||||
return;
|
return;
|
||||||
|
_completed = false;
|
||||||
active = true;
|
active = true;
|
||||||
fadeOverlay.opacity = 0.0;
|
fadeOverlay.opacity = 0.0;
|
||||||
fadeSeq.stop();
|
fadeSeq.stop();
|
||||||
@@ -65,12 +69,29 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cancelFade() {
|
function cancelFade() {
|
||||||
|
if (_completed)
|
||||||
|
return;
|
||||||
fadeSeq.stop();
|
fadeSeq.stop();
|
||||||
fadeOverlay.opacity = 0.0;
|
fadeOverlay.opacity = 0.0;
|
||||||
active = false;
|
active = false;
|
||||||
fadeCancelled();
|
fadeCancelled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function dismiss() {
|
||||||
|
fadeSeq.stop();
|
||||||
|
fadeOverlay.opacity = 0.0;
|
||||||
|
active = false;
|
||||||
|
_completed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: IdleService
|
||||||
|
function onIsShellLockedChanged() {
|
||||||
|
if (!IdleService.isShellLocked && root._completed)
|
||||||
|
root.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: root.active
|
enabled: root.active
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Item {
|
|||||||
property bool inputEnabled: false
|
property bool inputEnabled: false
|
||||||
property point lastMousePos: Qt.point(-1, -1)
|
property point lastMousePos: Qt.point(-1, -1)
|
||||||
property bool mouseInitialized: false
|
property bool mouseInitialized: false
|
||||||
property var videoPlayer: null
|
readonly property var videoPlayer: playerLoader.item
|
||||||
|
|
||||||
signal dismissed
|
signal dismissed
|
||||||
|
|
||||||
@@ -27,6 +27,24 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
color: "black"
|
color: "black"
|
||||||
visible: root.active
|
visible: root.active
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: playerLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: false
|
||||||
|
source: "VideoScreensaverPlayer.qml"
|
||||||
|
onLoaded: {
|
||||||
|
item.errorOccurred.connect((error, errorString) => {
|
||||||
|
log.warn("playback error:", errorString);
|
||||||
|
ToastService.showError(I18n.tr("Video Screensaver"), I18n.tr("Playback error: ") + errorString);
|
||||||
|
root.dismiss();
|
||||||
|
});
|
||||||
|
if (root.videoSource) {
|
||||||
|
item.source = root.videoSource;
|
||||||
|
item.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -82,43 +100,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createVideoPlayer() {
|
|
||||||
if (videoPlayer)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
videoPlayer = Qt.createQmlObject(`
|
|
||||||
import QtQuick
|
|
||||||
import QtMultimedia
|
|
||||||
Video {
|
|
||||||
anchors.fill: parent
|
|
||||||
fillMode: VideoOutput.PreserveAspectCrop
|
|
||||||
loops: MediaPlayer.Infinite
|
|
||||||
volume: 0
|
|
||||||
}
|
|
||||||
`, background, "VideoScreensaver.VideoPlayer");
|
|
||||||
|
|
||||||
videoPlayer.errorOccurred.connect((error, errorString) => {
|
|
||||||
log.warn("playback error:", errorString);
|
|
||||||
ToastService.showError(I18n.tr("Video Screensaver"), I18n.tr("Playback error: ") + errorString);
|
|
||||||
root.dismiss();
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
log.warn("Failed to create video player:", e);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function destroyVideoPlayer() {
|
|
||||||
if (videoPlayer) {
|
|
||||||
videoPlayer.stop();
|
|
||||||
videoPlayer.destroy();
|
|
||||||
videoPlayer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
if (!SettingsData.lockScreenVideoEnabled || !SettingsData.lockScreenVideoPath)
|
if (!SettingsData.lockScreenVideoEnabled || !SettingsData.lockScreenVideoPath)
|
||||||
return;
|
return;
|
||||||
@@ -128,8 +109,12 @@ Item {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!createVideoPlayer())
|
playerLoader.active = true;
|
||||||
|
if (playerLoader.status === Loader.Error) {
|
||||||
|
log.warn("Failed to load video player");
|
||||||
|
playerLoader.active = false;
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
videoPicker.result = "";
|
videoPicker.result = "";
|
||||||
videoPicker.folder = "";
|
videoPicker.folder = "";
|
||||||
@@ -144,7 +129,9 @@ Item {
|
|||||||
function dismiss() {
|
function dismiss() {
|
||||||
if (!active)
|
if (!active)
|
||||||
return;
|
return;
|
||||||
destroyVideoPlayer();
|
if (videoPlayer)
|
||||||
|
videoPlayer.stop();
|
||||||
|
playerLoader.active = false;
|
||||||
inputEnabled = false;
|
inputEnabled = false;
|
||||||
active = false;
|
active = false;
|
||||||
videoSource = "";
|
videoSource = "";
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtMultimedia
|
||||||
|
|
||||||
|
Video {
|
||||||
|
fillMode: VideoOutput.PreserveAspectCrop
|
||||||
|
loops: MediaPlayer.Infinite
|
||||||
|
volume: 0
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
import QtQuick.Controls
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -230,44 +231,41 @@ Column {
|
|||||||
if (!inlinePreviewVisible || !textArea.text)
|
if (!inlinePreviewVisible || !textArea.text)
|
||||||
return;
|
return;
|
||||||
const content = textArea.text;
|
const content = textArea.text;
|
||||||
if (content.length > 0) {
|
if (content.length === 0)
|
||||||
const proc = Qt.createQmlObject(`
|
return;
|
||||||
import QtQuick
|
const proc = clipboardCopyProcComp.createObject(root, {
|
||||||
import Quickshell.Io
|
content: content,
|
||||||
Process {
|
running: true
|
||||||
property string content: ""
|
});
|
||||||
command: ["sh", "-c", "printf '%s' \\"$CONTENT\\" | dms clipboard copy"]
|
proc.exited.connect(() => {
|
||||||
environment: { "CONTENT": content }
|
ToastService.showInfo(I18n.tr("Copied to clipboard"));
|
||||||
running: false
|
proc.destroy();
|
||||||
}`, root, "copyProc");
|
});
|
||||||
proc.content = content;
|
|
||||||
proc.running = true;
|
|
||||||
proc.exited.connect(() => {
|
|
||||||
ToastService.showInfo(I18n.tr("Copied to clipboard"));
|
|
||||||
proc.destroy();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function copyHtmlToClipboard() {
|
function copyHtmlToClipboard() {
|
||||||
if (!inlinePreviewVisible || !pluginHighlightedHtml)
|
if (!inlinePreviewVisible || !pluginHighlightedHtml)
|
||||||
return;
|
return;
|
||||||
if (pluginHighlightedHtml.length > 0) {
|
if (pluginHighlightedHtml.length === 0)
|
||||||
const proc = Qt.createQmlObject(`
|
return;
|
||||||
import QtQuick
|
const proc = clipboardCopyProcComp.createObject(root, {
|
||||||
import Quickshell.Io
|
content: pluginHighlightedHtml,
|
||||||
Process {
|
running: true
|
||||||
property string content: ""
|
});
|
||||||
command: ["sh", "-c", "printf '%s' \\"$CONTENT\\" | dms clipboard copy"]
|
proc.exited.connect(() => {
|
||||||
environment: { "CONTENT": content }
|
ToastService.showInfo(I18n.tr("HTML copied to clipboard"));
|
||||||
running: false
|
proc.destroy();
|
||||||
}`, root, "copyProcHtml");
|
});
|
||||||
proc.content = pluginHighlightedHtml;
|
}
|
||||||
proc.running = true;
|
|
||||||
proc.exited.connect(() => {
|
Component {
|
||||||
ToastService.showInfo(I18n.tr("HTML copied to clipboard"));
|
id: clipboardCopyProcComp
|
||||||
proc.destroy();
|
Process {
|
||||||
});
|
property string content: ""
|
||||||
|
command: ["sh", "-c", "printf '%s' \"$CONTENT\" | dms clipboard copy"]
|
||||||
|
environment: ({
|
||||||
|
"CONTENT": content
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -586,10 +586,11 @@ PanelWindow {
|
|||||||
width: alignedWidth
|
width: alignedWidth
|
||||||
height: alignedHeight
|
height: alignedHeight
|
||||||
visible: !win._finalized && !chromeOnlyExit
|
visible: !win._finalized && !chromeOnlyExit
|
||||||
scale: (!win.inlineHeightAnimating && cardHoverHandler.hovered) ? 1.01 : 1.0
|
|
||||||
transformOrigin: Item.Center
|
transformOrigin: Item.Center
|
||||||
|
|
||||||
Behavior on scale {
|
property real chromeScale: (!win.inlineHeightAnimating && cardHoverHandler.hovered) ? 1.01 : 1.0
|
||||||
|
|
||||||
|
Behavior on chromeScale {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
duration: Theme.shortDuration
|
duration: Theme.shortDuration
|
||||||
easing.type: Theme.standardEasing
|
easing.type: Theme.standardEasing
|
||||||
@@ -650,6 +651,8 @@ PanelWindow {
|
|||||||
id: bgShadowLayer
|
id: bgShadowLayer
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: -content.shadowRenderPadding
|
anchors.margins: -content.shadowRenderPadding
|
||||||
|
scale: content.chromeScale
|
||||||
|
transformOrigin: Item.Center
|
||||||
level: content.elevLevel
|
level: content.elevLevel
|
||||||
fallbackOffset: 6
|
fallbackOffset: 6
|
||||||
shadowBlurPx: content.shadowBlurPx
|
shadowBlurPx: content.shadowBlurPx
|
||||||
@@ -684,6 +687,8 @@ PanelWindow {
|
|||||||
visible: win.notificationData && win.notificationData.urgency === NotificationUrgency.Critical
|
visible: win.notificationData && win.notificationData.urgency === NotificationUrgency.Critical
|
||||||
opacity: 1
|
opacity: 1
|
||||||
clip: true
|
clip: true
|
||||||
|
scale: content.chromeScale
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
|
||||||
gradient: Gradient {
|
gradient: Gradient {
|
||||||
orientation: Gradient.Horizontal
|
orientation: Gradient.Horizontal
|
||||||
@@ -713,6 +718,8 @@ PanelWindow {
|
|||||||
border.color: win.connectedFrameMode ? "transparent" : BlurService.borderColor
|
border.color: win.connectedFrameMode ? "transparent" : BlurService.borderColor
|
||||||
border.width: win.connectedFrameMode ? 0 : BlurService.borderWidth
|
border.width: win.connectedFrameMode ? 0 : BlurService.borderWidth
|
||||||
z: 100
|
z: 100
|
||||||
|
scale: content.chromeScale
|
||||||
|
transformOrigin: Item.Center
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ FloatingWindow {
|
|||||||
property int selectedIndex: -1
|
property int selectedIndex: -1
|
||||||
property bool keyboardNavigationActive: false
|
property bool keyboardNavigationActive: false
|
||||||
property var parentModal: null
|
property var parentModal: null
|
||||||
|
parentWindow: parentModal
|
||||||
|
|
||||||
signal widgetAdded(string widgetType)
|
signal widgetAdded(string widgetType)
|
||||||
|
|
||||||
@@ -233,7 +234,7 @@ FloatingWindow {
|
|||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
visible: windowControls.supported
|
visible: windowControls.canMaximize
|
||||||
circular: false
|
circular: false
|
||||||
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ FloatingWindow {
|
|||||||
property bool keyboardNavigationActive: false
|
property bool keyboardNavigationActive: false
|
||||||
property bool isLoading: false
|
property bool isLoading: false
|
||||||
property var parentModal: null
|
property var parentModal: null
|
||||||
|
parentWindow: parentModal
|
||||||
property bool pendingInstallHandled: false
|
property bool pendingInstallHandled: false
|
||||||
property string typeFilter: ""
|
property string typeFilter: ""
|
||||||
|
|
||||||
@@ -295,7 +296,7 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
visible: windowControls.supported
|
visible: windowControls.canMaximize
|
||||||
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
||||||
iconSize: Theme.iconSize - 2
|
iconSize: Theme.iconSize - 2
|
||||||
iconColor: Theme.outline
|
iconColor: Theme.outline
|
||||||
@@ -723,6 +724,7 @@ FloatingWindow {
|
|||||||
id: thirdPartyConfirmModal
|
id: thirdPartyConfirmModal
|
||||||
|
|
||||||
property bool disablePopupTransparency: true
|
property bool disablePopupTransparency: true
|
||||||
|
parentWindow: root
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
visible = true;
|
visible = true;
|
||||||
|
|||||||
@@ -370,14 +370,6 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
|
||||||
text: I18n.tr("Idle monitoring not supported - requires newer Quickshell version")
|
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
|
||||||
color: Theme.error
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
visible: !IdleService.idleMonitorAvailable
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ FloatingWindow {
|
|||||||
property bool keyboardNavigationActive: false
|
property bool keyboardNavigationActive: false
|
||||||
property bool isLoading: false
|
property bool isLoading: false
|
||||||
property var parentModal: null
|
property var parentModal: null
|
||||||
|
parentWindow: parentModal
|
||||||
property bool pendingInstallHandled: false
|
property bool pendingInstallHandled: false
|
||||||
property string pendingApplyThemeId: ""
|
property string pendingApplyThemeId: ""
|
||||||
|
|
||||||
@@ -264,7 +265,7 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
visible: windowControls.supported
|
visible: windowControls.canMaximize
|
||||||
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
||||||
iconSize: Theme.iconSize - 2
|
iconSize: Theme.iconSize - 2
|
||||||
iconColor: Theme.outline
|
iconColor: Theme.outline
|
||||||
|
|||||||
@@ -3064,6 +3064,7 @@ Item {
|
|||||||
|
|
||||||
ThemeBrowser {
|
ThemeBrowser {
|
||||||
id: themeBrowserItem
|
id: themeBrowserItem
|
||||||
|
parentModal: themeColorsTab.parentModal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ FloatingWindow {
|
|||||||
property int selectedIndex: -1
|
property int selectedIndex: -1
|
||||||
property bool keyboardNavigationActive: false
|
property bool keyboardNavigationActive: false
|
||||||
property var parentModal: null
|
property var parentModal: null
|
||||||
|
parentWindow: parentModal
|
||||||
readonly property bool blurActive: Theme.blurForegroundLayers || Theme.transparentBlurLayers
|
readonly property bool blurActive: Theme.blurForegroundLayers || Theme.transparentBlurLayers
|
||||||
readonly property real surfaceAlpha: blurActive ? Math.min(Theme.popupTransparency, Theme.transparentBlurLayers ? 0.36 : 0.78) : 1.0
|
readonly property real surfaceAlpha: blurActive ? Math.min(Theme.popupTransparency, Theme.transparentBlurLayers ? 0.36 : 0.78) : 1.0
|
||||||
readonly property real fieldAlpha: blurActive ? Math.min(Theme.popupTransparency, Theme.transparentBlurLayers ? 0.18 : 0.62) : 1.0
|
readonly property real fieldAlpha: blurActive ? Math.min(Theme.popupTransparency, Theme.transparentBlurLayers ? 0.18 : 0.62) : 1.0
|
||||||
@@ -238,7 +239,7 @@ FloatingWindow {
|
|||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
visible: windowControls.supported
|
visible: windowControls.canMaximize
|
||||||
circular: false
|
circular: false
|
||||||
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
iconName: root.maximized ? "fullscreen_exit" : "fullscreen"
|
||||||
iconSize: Theme.iconSize - 4
|
iconSize: Theme.iconSize - 4
|
||||||
|
|||||||
@@ -222,8 +222,7 @@ Variants {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (typeof wallpaperWindow.updatesEnabled !== "undefined")
|
wallpaperWindow.updatesEnabled = Qt.binding(() => !root.source || root.effectActive || root._renderSettling || root.overviewBlurActive || root._overviewBlurSettling || root.pendingWallpaper !== "" || root._deferredSource !== "" || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
||||||
wallpaperWindow.updatesEnabled = Qt.binding(() => !root.source || root.effectActive || root._renderSettling || root.overviewBlurActive || root._overviewBlurSettling || root.pendingWallpaper !== "" || root._deferredSource !== "" || currentWallpaper.status === Image.Loading || nextWallpaper.status === Image.Loading);
|
|
||||||
|
|
||||||
if (!source) {
|
if (!source) {
|
||||||
root._renderSettling = false;
|
root._renderSettling = false;
|
||||||
|
|||||||
@@ -22,17 +22,30 @@ Singleton {
|
|||||||
property string currentSoundTheme: ""
|
property string currentSoundTheme: ""
|
||||||
property var soundFilePaths: ({})
|
property var soundFilePaths: ({})
|
||||||
|
|
||||||
property var volumeChangeSound: null
|
readonly property var volumeChangeSound: soundsLoader.item?.volumeChangeSound ?? null
|
||||||
property var powerPlugSound: null
|
readonly property var powerPlugSound: soundsLoader.item?.powerPlugSound ?? null
|
||||||
property var powerUnplugSound: null
|
readonly property var powerUnplugSound: soundsLoader.item?.powerUnplugSound ?? null
|
||||||
property var normalNotificationSound: null
|
readonly property var normalNotificationSound: soundsLoader.item?.normalNotificationSound ?? null
|
||||||
property var criticalNotificationSound: null
|
readonly property var criticalNotificationSound: soundsLoader.item?.criticalNotificationSound ?? null
|
||||||
property var loginSound: null
|
readonly property var loginSound: soundsLoader.item?.loginSound ?? null
|
||||||
|
readonly property var mediaDevices: soundsLoader.item?.mediaDevices ?? null
|
||||||
property real notificationsVolume: 1.0
|
property real notificationsVolume: 1.0
|
||||||
property bool notificationsAudioMuted: false
|
property bool notificationsAudioMuted: false
|
||||||
|
|
||||||
property var mediaDevices: null
|
Loader {
|
||||||
property var mediaDevicesConnections: null
|
id: soundsLoader
|
||||||
|
active: root.soundsAvailable
|
||||||
|
source: "AudioSoundPlayers.qml"
|
||||||
|
onLoaded: {
|
||||||
|
item.volume = Qt.binding(() => root.notificationsVolume);
|
||||||
|
item.volumeChangeSource = Qt.binding(() => root.getSoundPath("audio-volume-change"));
|
||||||
|
item.powerPlugSource = Qt.binding(() => root.getSoundPath("power-plug"));
|
||||||
|
item.powerUnplugSource = Qt.binding(() => root.getSoundPath("power-unplug"));
|
||||||
|
item.normalNotificationSource = Qt.binding(() => root.getSoundPath("message"));
|
||||||
|
item.criticalNotificationSource = Qt.binding(() => root.getSoundPath("message-new-instant"));
|
||||||
|
item.loginSource = Qt.binding(() => root.getSoundPath("desktop-login"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
property var deviceAliases: ({})
|
property var deviceAliases: ({})
|
||||||
property string wireplumberConfigPath: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/wireplumber/wireplumber.conf.d/51-dms-audio-aliases.conf"
|
property string wireplumberConfigPath: Paths.strip(StandardPaths.writableLocation(StandardPaths.ConfigLocation)) + "/wireplumber/wireplumber.conf.d/51-dms-audio-aliases.conf"
|
||||||
@@ -452,10 +465,6 @@ EOFCONFIG
|
|||||||
function discoverSoundFiles(themeName) {
|
function discoverSoundFiles(themeName) {
|
||||||
if (!themeName) {
|
if (!themeName) {
|
||||||
soundFilePaths = {};
|
soundFilePaths = {};
|
||||||
if (soundsAvailable) {
|
|
||||||
destroySoundPlayers();
|
|
||||||
createSoundPlayers();
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -514,11 +523,6 @@ EOFCONFIG
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
soundFilePaths = paths;
|
soundFilePaths = paths;
|
||||||
|
|
||||||
if (soundsAvailable) {
|
|
||||||
destroySoundPlayers();
|
|
||||||
createSoundPlayers();
|
|
||||||
}
|
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -559,159 +563,6 @@ EOFCONFIG
|
|||||||
discoverSoundFiles(currentSoundTheme);
|
discoverSoundFiles(currentSoundTheme);
|
||||||
} else {
|
} else {
|
||||||
soundFilePaths = {};
|
soundFilePaths = {};
|
||||||
if (soundsAvailable) {
|
|
||||||
destroySoundPlayers();
|
|
||||||
createSoundPlayers();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupMediaDevices() {
|
|
||||||
if (!soundsAvailable || mediaDevices) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
mediaDevices = Qt.createQmlObject(`
|
|
||||||
import QtQuick
|
|
||||||
import QtMultimedia
|
|
||||||
MediaDevices {
|
|
||||||
id: devices
|
|
||||||
Component.onCompleted: {
|
|
||||||
log.debug("MediaDevices initialized, default output:", defaultAudioOutput?.description)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, root, "AudioService.MediaDevices");
|
|
||||||
|
|
||||||
if (mediaDevices) {
|
|
||||||
mediaDevicesConnections = Qt.createQmlObject(`
|
|
||||||
import QtQuick
|
|
||||||
Connections {
|
|
||||||
target: root.mediaDevices
|
|
||||||
function onDefaultAudioOutputChanged() {
|
|
||||||
log.debug("Default audio output changed, recreating sound players")
|
|
||||||
root.destroySoundPlayers()
|
|
||||||
root.createSoundPlayers()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, root, "AudioService.MediaDevicesConnections");
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log.debug("MediaDevices not available, using default audio output");
|
|
||||||
mediaDevices = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function destroySoundPlayers() {
|
|
||||||
if (volumeChangeSound) {
|
|
||||||
volumeChangeSound.destroy();
|
|
||||||
volumeChangeSound = null;
|
|
||||||
}
|
|
||||||
if (powerPlugSound) {
|
|
||||||
powerPlugSound.destroy();
|
|
||||||
powerPlugSound = null;
|
|
||||||
}
|
|
||||||
if (powerUnplugSound) {
|
|
||||||
powerUnplugSound.destroy();
|
|
||||||
powerUnplugSound = null;
|
|
||||||
}
|
|
||||||
if (normalNotificationSound) {
|
|
||||||
normalNotificationSound.destroy();
|
|
||||||
normalNotificationSound = null;
|
|
||||||
}
|
|
||||||
if (criticalNotificationSound) {
|
|
||||||
criticalNotificationSound.destroy();
|
|
||||||
criticalNotificationSound = null;
|
|
||||||
}
|
|
||||||
if (loginSound) {
|
|
||||||
loginSound.destroy();
|
|
||||||
loginSound = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createSoundPlayers() {
|
|
||||||
if (!soundsAvailable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setupMediaDevices();
|
|
||||||
|
|
||||||
try {
|
|
||||||
const deviceProperty = mediaDevices ? `device: root.mediaDevices.defaultAudioOutput\n ` : "";
|
|
||||||
|
|
||||||
const volumeChangePath = getSoundPath("audio-volume-change");
|
|
||||||
volumeChangeSound = Qt.createQmlObject(`
|
|
||||||
import QtQuick
|
|
||||||
import QtMultimedia
|
|
||||||
MediaPlayer {
|
|
||||||
source: "${volumeChangePath}"
|
|
||||||
audioOutput: AudioOutput {
|
|
||||||
${deviceProperty}volume: notificationsVolume
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, root, "AudioService.VolumeChangeSound");
|
|
||||||
|
|
||||||
const powerPlugPath = getSoundPath("power-plug");
|
|
||||||
powerPlugSound = Qt.createQmlObject(`
|
|
||||||
import QtQuick
|
|
||||||
import QtMultimedia
|
|
||||||
MediaPlayer {
|
|
||||||
source: "${powerPlugPath}"
|
|
||||||
audioOutput: AudioOutput {
|
|
||||||
${deviceProperty}volume: notificationsVolume
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, root, "AudioService.PowerPlugSound");
|
|
||||||
|
|
||||||
const powerUnplugPath = getSoundPath("power-unplug");
|
|
||||||
powerUnplugSound = Qt.createQmlObject(`
|
|
||||||
import QtQuick
|
|
||||||
import QtMultimedia
|
|
||||||
MediaPlayer {
|
|
||||||
source: "${powerUnplugPath}"
|
|
||||||
audioOutput: AudioOutput {
|
|
||||||
${deviceProperty}volume: notificationsVolume
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, root, "AudioService.PowerUnplugSound");
|
|
||||||
|
|
||||||
const messagePath = getSoundPath("message");
|
|
||||||
normalNotificationSound = Qt.createQmlObject(`
|
|
||||||
import QtQuick
|
|
||||||
import QtMultimedia
|
|
||||||
MediaPlayer {
|
|
||||||
source: "${messagePath}"
|
|
||||||
audioOutput: AudioOutput {
|
|
||||||
${deviceProperty}volume: notificationsVolume
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, root, "AudioService.NormalNotificationSound");
|
|
||||||
|
|
||||||
const messageNewInstantPath = getSoundPath("message-new-instant");
|
|
||||||
criticalNotificationSound = Qt.createQmlObject(`
|
|
||||||
import QtQuick
|
|
||||||
import QtMultimedia
|
|
||||||
MediaPlayer {
|
|
||||||
source: "${messageNewInstantPath}"
|
|
||||||
audioOutput: AudioOutput {
|
|
||||||
${deviceProperty}volume: notificationsVolume
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, root, "AudioService.CriticalNotificationSound");
|
|
||||||
|
|
||||||
const loginPath = getSoundPath("desktop-login");
|
|
||||||
loginSound = Qt.createQmlObject(`
|
|
||||||
import QtQuick
|
|
||||||
import QtMultimedia
|
|
||||||
MediaPlayer {
|
|
||||||
source: "${loginPath}"
|
|
||||||
audioOutput: AudioOutput {
|
|
||||||
${deviceProperty}volume: notificationsVolume
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`, root, "AudioService.LoginSound");
|
|
||||||
} catch (e) {
|
|
||||||
log.warn("Error creating sound players:", e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,16 +806,6 @@ EOFCONFIG
|
|||||||
objects: Pipewire.nodes.values.filter(node => node.audio && !node.isStream)
|
objects: Pipewire.nodes.values.filter(node => node.audio && !node.isStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: Pipewire
|
|
||||||
function onDefaultAudioSinkChanged() {
|
|
||||||
if (soundsAvailable) {
|
|
||||||
Qt.callLater(root.destroySoundPlayers);
|
|
||||||
Qt.callLater(root.createSoundPlayers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setVolume(percentage) {
|
function setVolume(percentage) {
|
||||||
if (!root.sink?.audio)
|
if (!root.sink?.audio)
|
||||||
return "No audio sink available";
|
return "No audio sink available";
|
||||||
@@ -1127,10 +968,8 @@ EOFCONFIG
|
|||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
rebuildTypedNodeLists();
|
rebuildTypedNodeLists();
|
||||||
|
|
||||||
if (soundsAvailable) {
|
if (soundsAvailable)
|
||||||
checkGsettings();
|
checkGsettings();
|
||||||
Qt.callLater(createSoundPlayers);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadDeviceAliases();
|
loadDeviceAliases();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtMultimedia
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property real volume: 1.0
|
||||||
|
property url volumeChangeSource
|
||||||
|
property url powerPlugSource
|
||||||
|
property url powerUnplugSource
|
||||||
|
property url normalNotificationSource
|
||||||
|
property url criticalNotificationSource
|
||||||
|
property url loginSource
|
||||||
|
|
||||||
|
readonly property alias mediaDevices: devices
|
||||||
|
readonly property alias volumeChangeSound: volumeChangePlayer
|
||||||
|
readonly property alias powerPlugSound: powerPlugPlayer
|
||||||
|
readonly property alias powerUnplugSound: powerUnplugPlayer
|
||||||
|
readonly property alias normalNotificationSound: normalNotificationPlayer
|
||||||
|
readonly property alias criticalNotificationSound: criticalNotificationPlayer
|
||||||
|
readonly property alias loginSound: loginPlayer
|
||||||
|
|
||||||
|
MediaDevices {
|
||||||
|
id: devices
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaPlayer {
|
||||||
|
id: volumeChangePlayer
|
||||||
|
source: root.volumeChangeSource
|
||||||
|
audioOutput: AudioOutput {
|
||||||
|
device: devices.defaultAudioOutput
|
||||||
|
volume: root.volume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaPlayer {
|
||||||
|
id: powerPlugPlayer
|
||||||
|
source: root.powerPlugSource
|
||||||
|
audioOutput: AudioOutput {
|
||||||
|
device: devices.defaultAudioOutput
|
||||||
|
volume: root.volume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaPlayer {
|
||||||
|
id: powerUnplugPlayer
|
||||||
|
source: root.powerUnplugSource
|
||||||
|
audioOutput: AudioOutput {
|
||||||
|
device: devices.defaultAudioOutput
|
||||||
|
volume: root.volume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaPlayer {
|
||||||
|
id: normalNotificationPlayer
|
||||||
|
source: root.normalNotificationSource
|
||||||
|
audioOutput: AudioOutput {
|
||||||
|
device: devices.defaultAudioOutput
|
||||||
|
volume: root.volume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaPlayer {
|
||||||
|
id: criticalNotificationPlayer
|
||||||
|
source: root.criticalNotificationSource
|
||||||
|
audioOutput: AudioOutput {
|
||||||
|
device: devices.defaultAudioOutput
|
||||||
|
volume: root.volume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaPlayer {
|
||||||
|
id: loginPlayer
|
||||||
|
source: root.loginSource
|
||||||
|
audioOutput: AudioOutput {
|
||||||
|
device: devices.defaultAudioOutput
|
||||||
|
volume: root.volume
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,7 +4,6 @@ pragma ComponentBehavior: Bound
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland // ! Import is needed despite what qmlls says
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
@@ -12,9 +11,8 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("BlurService")
|
readonly property var log: Log.scoped("BlurService")
|
||||||
|
|
||||||
property bool quickshellSupported: false
|
|
||||||
property bool compositorSupported: false
|
property bool compositorSupported: false
|
||||||
property bool available: quickshellSupported && compositorSupported
|
readonly property bool available: compositorSupported
|
||||||
readonly property bool enabled: available && (SettingsData.blurEnabled ?? false)
|
readonly property bool enabled: available && (SettingsData.blurEnabled ?? false)
|
||||||
|
|
||||||
readonly property color borderColor: {
|
readonly property color borderColor: {
|
||||||
@@ -42,41 +40,6 @@ Singleton {
|
|||||||
return Theme.withAlpha(baseColor, hoverAlpha ?? 0.15);
|
return Theme.withAlpha(baseColor, hoverAlpha ?? 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createBlurRegion(targetWindow) {
|
|
||||||
if (!available)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const region = Qt.createQmlObject(`
|
|
||||||
import Quickshell
|
|
||||||
Region {}
|
|
||||||
`, targetWindow, "BlurRegion");
|
|
||||||
targetWindow.BackgroundEffect.blurRegion = region;
|
|
||||||
return region;
|
|
||||||
} catch (e) {
|
|
||||||
log.warn("Failed to create blur region:", e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reapplyBlurRegion(targetWindow, region) {
|
|
||||||
if (!region || !available)
|
|
||||||
return;
|
|
||||||
try {
|
|
||||||
targetWindow.BackgroundEffect.blurRegion = region;
|
|
||||||
region.changed();
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function destroyBlurRegion(targetWindow, region) {
|
|
||||||
if (!region)
|
|
||||||
return;
|
|
||||||
try {
|
|
||||||
targetWindow.BackgroundEffect.blurRegion = null;
|
|
||||||
} catch (e) {}
|
|
||||||
region.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: blurProbe
|
id: blurProbe
|
||||||
running: false
|
running: false
|
||||||
@@ -98,18 +61,5 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: blurProbe.running = true
|
||||||
try {
|
|
||||||
const test = Qt.createQmlObject(`
|
|
||||||
import Quickshell
|
|
||||||
Region { radius: 0 }
|
|
||||||
`, root, "BlurAvailabilityTest");
|
|
||||||
test.destroy();
|
|
||||||
quickshellSupported = true;
|
|
||||||
log.info("Quickshell blur support available");
|
|
||||||
blurProbe.running = true;
|
|
||||||
} catch (e) {
|
|
||||||
log.info("BackgroundEffect not available - blur disabled. Requires a newer version of Quickshell.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,25 +11,8 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("IdleService")
|
readonly property var log: Log.scoped("IdleService")
|
||||||
|
|
||||||
readonly property bool idleMonitorAvailable: {
|
|
||||||
try {
|
|
||||||
return typeof IdleMonitor !== "undefined";
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property bool idleInhibitorAvailable: {
|
|
||||||
try {
|
|
||||||
return typeof IdleInhibitor !== "undefined";
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool enabled: true
|
property bool enabled: true
|
||||||
property bool respectInhibitors: true
|
property bool respectInhibitors: true
|
||||||
property bool _enableGate: true
|
|
||||||
|
|
||||||
readonly property bool externalInhibitActive: DMSService.screensaverInhibited
|
readonly property bool externalInhibitActive: DMSService.screensaverInhibited
|
||||||
|
|
||||||
@@ -43,17 +26,28 @@ Singleton {
|
|||||||
|
|
||||||
readonly property bool mediaPlaying: MprisController.activePlayer !== null && MprisController.activePlayer.isPlaying
|
readonly property bool mediaPlaying: MprisController.activePlayer !== null && MprisController.activePlayer.isPlaying
|
||||||
|
|
||||||
|
onEnabledChanged: _applyMonitorEnableds()
|
||||||
|
onPostLockMonitorActiveChanged: _applyMonitorEnableds()
|
||||||
onMonitorTimeoutChanged: _rearmIdleMonitors()
|
onMonitorTimeoutChanged: _rearmIdleMonitors()
|
||||||
onLockTimeoutChanged: _rearmIdleMonitors()
|
onLockTimeoutChanged: _rearmIdleMonitors()
|
||||||
onSuspendTimeoutChanged: _rearmIdleMonitors()
|
onSuspendTimeoutChanged: _rearmIdleMonitors()
|
||||||
onPostLockMonitorTimeoutChanged: _rearmIdleMonitors()
|
onPostLockMonitorTimeoutChanged: _rearmIdleMonitors()
|
||||||
onIsShellLockedChanged: _rearmIdleMonitors()
|
onIsShellLockedChanged: _rearmIdleMonitors()
|
||||||
|
|
||||||
|
function _applyMonitorEnableds() {
|
||||||
|
const base = enabled;
|
||||||
|
monitorOffMonitor.enabled = base && monitorTimeout > 0 && !postLockMonitorActive;
|
||||||
|
postLockMonitorOffMonitor.enabled = base && postLockMonitorActive;
|
||||||
|
lockMonitor.enabled = base && lockTimeout > 0;
|
||||||
|
suspendMonitor.enabled = base && suspendTimeout > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function _rearmIdleMonitors() {
|
function _rearmIdleMonitors() {
|
||||||
_enableGate = false;
|
monitorOffMonitor.enabled = false;
|
||||||
Qt.callLater(() => {
|
postLockMonitorOffMonitor.enabled = false;
|
||||||
_enableGate = true;
|
lockMonitor.enabled = false;
|
||||||
});
|
suspendMonitor.enabled = false;
|
||||||
|
Qt.callLater(_applyMonitorEnableds);
|
||||||
}
|
}
|
||||||
|
|
||||||
signal lockRequested
|
signal lockRequested
|
||||||
@@ -65,10 +59,6 @@ Singleton {
|
|||||||
signal requestMonitorOn
|
signal requestMonitorOn
|
||||||
signal requestSuspend
|
signal requestSuspend
|
||||||
|
|
||||||
property var monitorOffMonitor: null
|
|
||||||
property var postLockMonitorOffMonitor: null
|
|
||||||
property var lockMonitor: null
|
|
||||||
property var suspendMonitor: null
|
|
||||||
property var lockComponent: null
|
property var lockComponent: null
|
||||||
property bool monitorsOff: false
|
property bool monitorsOff: false
|
||||||
property bool isShellLocked: false
|
property bool isShellLocked: false
|
||||||
@@ -82,84 +72,69 @@ Singleton {
|
|||||||
CompositorService.powerOffMonitors();
|
CompositorService.powerOffMonitors();
|
||||||
}
|
}
|
||||||
|
|
||||||
function createIdleMonitors() {
|
IdleMonitor {
|
||||||
if (!idleMonitorAvailable) {
|
id: monitorOffMonitor
|
||||||
log.info("IdleMonitor not available, skipping creation");
|
timeout: root.monitorTimeout > 0 ? root.monitorTimeout : 86400
|
||||||
return;
|
respectInhibitors: root.respectInhibitors
|
||||||
}
|
enabled: false
|
||||||
|
onIsIdleChanged: {
|
||||||
try {
|
if (isIdle) {
|
||||||
const qmlString = `
|
if (SettingsData.fadeToDpmsEnabled) {
|
||||||
import QtQuick
|
root.fadeToDpmsRequested();
|
||||||
import Quickshell.Wayland
|
|
||||||
|
|
||||||
IdleMonitor {
|
|
||||||
enabled: false
|
|
||||||
respectInhibitors: true
|
|
||||||
timeout: 0
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
monitorOffMonitor = Qt.createQmlObject(qmlString, root, "IdleService.MonitorOffMonitor");
|
|
||||||
monitorOffMonitor.timeout = Qt.binding(() => root.monitorTimeout > 0 ? root.monitorTimeout : 86400);
|
|
||||||
monitorOffMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
|
||||||
monitorOffMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.monitorTimeout > 0 && !root.postLockMonitorActive);
|
|
||||||
monitorOffMonitor.isIdleChanged.connect(function () {
|
|
||||||
if (monitorOffMonitor.isIdle) {
|
|
||||||
if (SettingsData.fadeToDpmsEnabled) {
|
|
||||||
root.fadeToDpmsRequested();
|
|
||||||
} else {
|
|
||||||
root.requestMonitorOff();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
if (SettingsData.fadeToDpmsEnabled) {
|
|
||||||
root.cancelFadeToDpms();
|
|
||||||
}
|
|
||||||
root.requestMonitorOn();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
postLockMonitorOffMonitor = Qt.createQmlObject(qmlString, root, "IdleService.PostLockMonitorOffMonitor");
|
|
||||||
postLockMonitorOffMonitor.timeout = Qt.binding(() => root.postLockMonitorTimeout > 0 ? root.postLockMonitorTimeout : 86400);
|
|
||||||
postLockMonitorOffMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
|
||||||
postLockMonitorOffMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.postLockMonitorActive);
|
|
||||||
postLockMonitorOffMonitor.isIdleChanged.connect(function () {
|
|
||||||
if (postLockMonitorOffMonitor.isIdle) {
|
|
||||||
root.requestMonitorOff();
|
root.requestMonitorOff();
|
||||||
} else {
|
|
||||||
root.requestMonitorOn();
|
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
if (SettingsData.fadeToDpmsEnabled) {
|
||||||
|
root.cancelFadeToDpms();
|
||||||
|
}
|
||||||
|
root.requestMonitorOn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lockMonitor = Qt.createQmlObject(qmlString, root, "IdleService.LockMonitor");
|
IdleMonitor {
|
||||||
lockMonitor.timeout = Qt.binding(() => root.lockTimeout > 0 ? root.lockTimeout : 86400);
|
id: postLockMonitorOffMonitor
|
||||||
lockMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
timeout: root.postLockMonitorTimeout > 0 ? root.postLockMonitorTimeout : 86400
|
||||||
lockMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.lockTimeout > 0);
|
respectInhibitors: root.respectInhibitors
|
||||||
lockMonitor.isIdleChanged.connect(function () {
|
enabled: false
|
||||||
if (lockMonitor.isIdle) {
|
onIsIdleChanged: {
|
||||||
if (SettingsData.fadeToLockEnabled) {
|
if (isIdle) {
|
||||||
root.fadeToLockRequested();
|
root.requestMonitorOff();
|
||||||
} else {
|
} else {
|
||||||
root.lockRequested();
|
root.requestMonitorOn();
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
if (SettingsData.fadeToLockEnabled) {
|
}
|
||||||
root.cancelFadeToLock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
suspendMonitor = Qt.createQmlObject(qmlString, root, "IdleService.SuspendMonitor");
|
IdleMonitor {
|
||||||
suspendMonitor.timeout = Qt.binding(() => root.suspendTimeout > 0 ? root.suspendTimeout : 86400);
|
id: lockMonitor
|
||||||
suspendMonitor.respectInhibitors = Qt.binding(() => root.respectInhibitors);
|
timeout: root.lockTimeout > 0 ? root.lockTimeout : 86400
|
||||||
suspendMonitor.enabled = Qt.binding(() => root._enableGate && root.enabled && root.idleMonitorAvailable && root.suspendTimeout > 0);
|
respectInhibitors: root.respectInhibitors
|
||||||
suspendMonitor.isIdleChanged.connect(function () {
|
enabled: false
|
||||||
if (suspendMonitor.isIdle) {
|
onIsIdleChanged: {
|
||||||
root.requestSuspend();
|
if (isIdle) {
|
||||||
|
if (SettingsData.fadeToLockEnabled) {
|
||||||
|
root.fadeToLockRequested();
|
||||||
|
} else {
|
||||||
|
root.lockRequested();
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
} catch (e) {
|
if (SettingsData.fadeToLockEnabled) {
|
||||||
log.warn("Error creating IdleMonitors:", e);
|
root.cancelFadeToLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IdleMonitor {
|
||||||
|
id: suspendMonitor
|
||||||
|
timeout: root.suspendTimeout > 0 ? root.suspendTimeout : 86400
|
||||||
|
respectInhibitors: root.respectInhibitors
|
||||||
|
enabled: false
|
||||||
|
onIsIdleChanged: {
|
||||||
|
if (isIdle)
|
||||||
|
root.requestSuspend();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,13 +169,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!idleMonitorAvailable) {
|
_applyMonitorEnableds();
|
||||||
log.warn("IdleMonitor not available - power management disabled. This requires a newer version of Quickshell.");
|
|
||||||
} else {
|
|
||||||
log.info("Initialized with idle monitoring support");
|
|
||||||
createIdleMonitors();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (externalInhibitActive) {
|
if (externalInhibitActive) {
|
||||||
const apps = DMSService.screensaverInhibitors.map(i => i.appName).join(", ");
|
const apps = DMSService.screensaverInhibitors.map(i => i.appName).join(", ");
|
||||||
SessionService.idleInhibited = true;
|
SessionService.idleInhibited = true;
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import QtCore
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Wayland
|
|
||||||
// ! Even though qmlls says this is unused, it is wrong
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import "../Common/KeybindActions.js" as Actions
|
import "../Common/KeybindActions.js" as Actions
|
||||||
@@ -15,21 +13,7 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("KeybindsService")
|
readonly property var log: Log.scoped("KeybindsService")
|
||||||
|
|
||||||
Component.onCompleted: {
|
property bool available: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl
|
||||||
if (!shortcutInhibitorAvailable) {
|
|
||||||
log.warn("ShortcutInhibitor is not available in this environment, keybinds editor disabled.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property bool shortcutInhibitorAvailable: {
|
|
||||||
try {
|
|
||||||
return typeof ShortcutInhibitor !== "undefined";
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool available: (CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl) && shortcutInhibitorAvailable
|
|
||||||
property string currentProvider: {
|
property string currentProvider: {
|
||||||
if (CompositorService.isNiri)
|
if (CompositorService.isNiri)
|
||||||
return "niri";
|
return "niri";
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
|||||||
|
import QtQuick
|
||||||
|
// qmllint disable unused-imports
|
||||||
|
import QtMultimedia
|
||||||
|
|
||||||
|
// qmllint enable unused-imports
|
||||||
|
|
||||||
|
Item {}
|
||||||
@@ -8,30 +8,15 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("MultimediaService")
|
readonly property var log: Log.scoped("MultimediaService")
|
||||||
|
|
||||||
property bool available: false
|
readonly property bool available: probeLoader.status === Loader.Ready
|
||||||
|
|
||||||
function detectAvailability() {
|
Loader {
|
||||||
try {
|
id: probeLoader
|
||||||
const testObj = Qt.createQmlObject(`
|
source: "MultimediaProbe.qml"
|
||||||
import QtQuick
|
active: true
|
||||||
import QtMultimedia
|
onStatusChanged: {
|
||||||
import qs.Services
|
if (status === Loader.Error)
|
||||||
Item {}
|
log.warn("QtMultimedia not available");
|
||||||
`, root, "MultimediaService.TestComponent");
|
|
||||||
if (testObj) {
|
|
||||||
testObj.destroy();
|
|
||||||
}
|
|
||||||
available = true;
|
|
||||||
return true;
|
|
||||||
} catch (e) {
|
|
||||||
available = false;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (!detectAvailability()) {
|
|
||||||
log.warn("QtMultimedia not available");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import QtCore
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Qt.labs.folderlistmodel
|
import Qt.labs.folderlistmodel
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
@@ -154,35 +155,43 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function loadPluginManifestFile(manifestPathNoScheme, sourceTag, mtimeEpochMs) {
|
function loadPluginManifestFile(manifestPathNoScheme, sourceTag, mtimeEpochMs) {
|
||||||
const manifestId = "m_" + Math.random().toString(36).slice(2);
|
const loader = manifestFvComp.createObject(root, {
|
||||||
const qml = `
|
absPath: manifestPathNoScheme,
|
||||||
import QtQuick
|
path: manifestPathNoScheme,
|
||||||
import Quickshell.Io
|
sourceTag: sourceTag,
|
||||||
FileView {
|
mtimeEpochMs: mtimeEpochMs
|
||||||
id: fv
|
});
|
||||||
property string absPath: ""
|
}
|
||||||
onLoaded: {
|
|
||||||
try {
|
|
||||||
let raw = text()
|
|
||||||
if (raw.charCodeAt(0) === 0xFEFF) raw = raw.slice(1)
|
|
||||||
const manifest = JSON.parse(raw)
|
|
||||||
root._onManifestParsed(absPath, manifest, "${sourceTag}", ${mtimeEpochMs})
|
|
||||||
} catch (e) {
|
|
||||||
log.error("bad manifest", absPath, e.message)
|
|
||||||
knownManifests[absPath] = { mtime: ${mtimeEpochMs}, source: "${sourceTag}", bad: true }
|
|
||||||
}
|
|
||||||
fv.destroy()
|
|
||||||
}
|
|
||||||
onLoadFailed: (err) => {
|
|
||||||
log.warn("manifest load failed", absPath, err)
|
|
||||||
fv.destroy()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const loader = Qt.createQmlObject(qml, root, "mf_" + manifestId);
|
Component {
|
||||||
loader.absPath = manifestPathNoScheme;
|
id: manifestFvComp
|
||||||
loader.path = manifestPathNoScheme;
|
FileView {
|
||||||
|
id: fv
|
||||||
|
property string absPath: ""
|
||||||
|
property string sourceTag: ""
|
||||||
|
property double mtimeEpochMs: 0
|
||||||
|
onLoaded: {
|
||||||
|
try {
|
||||||
|
let raw = text();
|
||||||
|
if (raw.charCodeAt(0) === 0xFEFF)
|
||||||
|
raw = raw.slice(1);
|
||||||
|
const manifest = JSON.parse(raw);
|
||||||
|
root._onManifestParsed(absPath, manifest, sourceTag, mtimeEpochMs);
|
||||||
|
} catch (e) {
|
||||||
|
root.log.error("bad manifest", absPath, e.message);
|
||||||
|
root.knownManifests[absPath] = {
|
||||||
|
mtime: mtimeEpochMs,
|
||||||
|
source: sourceTag,
|
||||||
|
bad: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
fv.destroy();
|
||||||
|
}
|
||||||
|
onLoadFailed: err => {
|
||||||
|
root.log.warn("manifest load failed", absPath, err);
|
||||||
|
fv.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _onManifestParsed(absPath, manifest, sourceTag, mtimeEpochMs) {
|
function _onManifestParsed(absPath, manifest, sourceTag, mtimeEpochMs) {
|
||||||
@@ -670,10 +679,10 @@ Singleton {
|
|||||||
_stateLoaded[pluginId] = true;
|
_stateLoaded[pluginId] = true;
|
||||||
_ensureStateDir();
|
_ensureStateDir();
|
||||||
const path = getPluginStatePath(pluginId);
|
const path = getPluginStatePath(pluginId);
|
||||||
const escapedPath = path.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
||||||
try {
|
try {
|
||||||
const qml = 'import QtQuick; import Quickshell.Io; FileView { path: "' + escapedPath + '"; blockLoading: true; blockWrites: true; atomicWrites: true }';
|
const fv = stateLoadFvComp.createObject(root, {
|
||||||
const fv = Qt.createQmlObject(qml, root, "sf_" + pluginId);
|
path: path
|
||||||
|
});
|
||||||
const raw = fv.text();
|
const raw = fv.text();
|
||||||
if (raw && raw.trim()) {
|
if (raw && raw.trim()) {
|
||||||
_stateCache[pluginId] = JSON.parse(raw);
|
_stateCache[pluginId] = JSON.parse(raw);
|
||||||
@@ -694,10 +703,10 @@ Singleton {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const path = getPluginStatePath(pluginId);
|
const path = getPluginStatePath(pluginId);
|
||||||
const escapedPath = path.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
||||||
try {
|
try {
|
||||||
const qml = 'import QtQuick; import Quickshell.Io; FileView { path: "' + escapedPath + '"; blockWrites: true; atomicWrites: true }';
|
const fv = stateSaveFvComp.createObject(root, {
|
||||||
const fv = Qt.createQmlObject(qml, root, "sw_" + pluginId);
|
path: path
|
||||||
|
});
|
||||||
_stateWriters[pluginId] = fv;
|
_stateWriters[pluginId] = fv;
|
||||||
fv.loaded.connect(function () {
|
fv.loaded.connect(function () {
|
||||||
fv.setText(content);
|
fv.setText(content);
|
||||||
@@ -710,6 +719,23 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: stateLoadFvComp
|
||||||
|
FileView {
|
||||||
|
blockLoading: true
|
||||||
|
blockWrites: true
|
||||||
|
atomicWrites: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: stateSaveFvComp
|
||||||
|
FileView {
|
||||||
|
blockWrites: true
|
||||||
|
atomicWrites: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function _flushDirtyStates() {
|
function _flushDirtyStates() {
|
||||||
const dirty = _stateDirtyPlugins;
|
const dirty = _stateDirtyPlugins;
|
||||||
_stateDirtyPlugins = {};
|
_stateDirtyPlugins = {};
|
||||||
@@ -748,22 +774,8 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createPluginDirectory() {
|
function createPluginDirectory() {
|
||||||
const mkdirProcess = Qt.createComponent("data:text/plain,import Quickshell.Io; Process { }");
|
Quickshell.execDetached(["mkdir", "-p", pluginDirectory]);
|
||||||
if (mkdirProcess.status === Component.Ready) {
|
return true;
|
||||||
const process = mkdirProcess.createObject(root);
|
|
||||||
process.command = ["mkdir", "-p", pluginDirectory];
|
|
||||||
process.exited.connect(function (exitCode) {
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
log.error("Failed to create plugin directory, exit code:", exitCode);
|
|
||||||
}
|
|
||||||
process.destroy();
|
|
||||||
});
|
|
||||||
process.running = true;
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
log.error("Failed to create mkdir process");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Launcher plugin helper functions
|
// Launcher plugin helper functions
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Services.Polkit
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -10,33 +11,15 @@ Singleton {
|
|||||||
|
|
||||||
readonly property bool disablePolkitIntegration: Quickshell.env("DMS_DISABLE_POLKIT") === "1"
|
readonly property bool disablePolkitIntegration: Quickshell.env("DMS_DISABLE_POLKIT") === "1"
|
||||||
|
|
||||||
property bool polkitAvailable: false
|
readonly property bool polkitAvailable: !disablePolkitIntegration
|
||||||
property var agent: null
|
readonly property alias agent: polkitAgentInstance
|
||||||
|
|
||||||
function createPolkitAgent() {
|
PolkitAgent {
|
||||||
try {
|
id: polkitAgentInstance
|
||||||
const qmlString = `
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell.Services.Polkit
|
|
||||||
import qs.Services
|
|
||||||
|
|
||||||
PolkitAgent {
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
agent = Qt.createQmlObject(qmlString, root, "PolkitService.Agent")
|
|
||||||
polkitAvailable = true
|
|
||||||
log.info("Initialized successfully")
|
|
||||||
} catch (e) {
|
|
||||||
polkitAvailable = false
|
|
||||||
log.warn("Polkit not available - authentication prompts disabled. This requires a newer version of Quickshell.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (disablePolkitIntegration) {
|
if (!disablePolkitIntegration)
|
||||||
return
|
log.info("Initialized successfully");
|
||||||
}
|
|
||||||
createPolkitAgent()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import Quickshell
|
|||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell.Hyprland
|
import Quickshell.Hyprland
|
||||||
import Quickshell.I3
|
import Quickshell.I3
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
@@ -22,14 +21,6 @@ Singleton {
|
|||||||
property string inhibitReason: "Keep system awake"
|
property string inhibitReason: "Keep system awake"
|
||||||
property string nvidiaCommand: ""
|
property string nvidiaCommand: ""
|
||||||
|
|
||||||
readonly property bool nativeInhibitorAvailable: {
|
|
||||||
try {
|
|
||||||
return typeof IdleInhibitor !== "undefined";
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
property bool loginctlAvailable: false
|
property bool loginctlAvailable: false
|
||||||
property bool wtypeAvailable: false
|
property bool wtypeAvailable: false
|
||||||
property string sessionId: ""
|
property string sessionId: ""
|
||||||
@@ -66,8 +57,6 @@ Singleton {
|
|||||||
detectHibernateProcess.running = true;
|
detectHibernateProcess.running = true;
|
||||||
detectPrimeRunProcess.running = true;
|
detectPrimeRunProcess.running = true;
|
||||||
detectWtypeProcess.running = true;
|
detectWtypeProcess.running = true;
|
||||||
cleanupOrphanedInhibitors();
|
|
||||||
log.info("Native inhibitor available:", nativeInhibitorAvailable);
|
|
||||||
if (!SettingsData.loginctlLockIntegration) {
|
if (!SettingsData.loginctlLockIntegration) {
|
||||||
log.debug("loginctl lock integration disabled by user");
|
log.debug("loginctl lock integration disabled by user");
|
||||||
return;
|
return;
|
||||||
@@ -396,19 +385,15 @@ Singleton {
|
|||||||
signal inhibitorChanged
|
signal inhibitorChanged
|
||||||
|
|
||||||
function enableIdleInhibit() {
|
function enableIdleInhibit() {
|
||||||
if (idleInhibited) {
|
if (idleInhibited)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
log.debug("Enabling idle inhibit (native:", nativeInhibitorAvailable, ")");
|
|
||||||
idleInhibited = true;
|
idleInhibited = true;
|
||||||
inhibitorChanged();
|
inhibitorChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
function disableIdleInhibit() {
|
function disableIdleInhibit() {
|
||||||
if (!idleInhibited) {
|
if (!idleInhibited)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
log.debug("Disabling idle inhibit (native:", nativeInhibitorAvailable, ")");
|
|
||||||
idleInhibited = false;
|
idleInhibited = false;
|
||||||
inhibitorChanged();
|
inhibitorChanged();
|
||||||
}
|
}
|
||||||
@@ -423,64 +408,6 @@ Singleton {
|
|||||||
|
|
||||||
function setInhibitReason(reason) {
|
function setInhibitReason(reason) {
|
||||||
inhibitReason = reason;
|
inhibitReason = reason;
|
||||||
|
|
||||||
if (idleInhibited && !nativeInhibitorAvailable) {
|
|
||||||
const wasActive = idleInhibited;
|
|
||||||
idleInhibited = false;
|
|
||||||
|
|
||||||
Qt.callLater(() => {
|
|
||||||
if (wasActive) {
|
|
||||||
idleInhibited = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: idleInhibitProcess
|
|
||||||
|
|
||||||
command: {
|
|
||||||
if (!idleInhibited || nativeInhibitorAvailable) {
|
|
||||||
return ["true"];
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Starting systemd/elogind inhibit process");
|
|
||||||
return [isElogind ? "elogind-inhibit" : "systemd-inhibit", "--what=idle", "--who=quickshell", `--why=${inhibitReason}`, "--mode=block", "sleep", "infinity"];
|
|
||||||
}
|
|
||||||
|
|
||||||
running: idleInhibited && !nativeInhibitorAvailable
|
|
||||||
|
|
||||||
onRunningChanged: {
|
|
||||||
log.debug("Inhibit process running:", running, "(native:", nativeInhibitorAvailable, ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: function (exitCode) {
|
|
||||||
if (idleInhibited && exitCode !== 0 && !nativeInhibitorAvailable) {
|
|
||||||
log.warn("Inhibitor process crashed with exit code:", exitCode);
|
|
||||||
idleInhibited = false;
|
|
||||||
ToastService.showWarning("Idle inhibitor failed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Kill orphaned idle inhibitor processes left behind by previous quickshell sessions.
|
|
||||||
// When quickshell crashes or is force-killed, the child systemd-inhibit process gets
|
|
||||||
// reparented to PID 1 and continues to block idle indefinitely.
|
|
||||||
function cleanupOrphanedInhibitors() {
|
|
||||||
if (nativeInhibitorAvailable) return;
|
|
||||||
orphanCleanupProcess.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: orphanCleanupProcess
|
|
||||||
running: false
|
|
||||||
command: ["pkill", "-f", "systemd-inhibit --what=idle --who=quickshell.*sleep infinity"]
|
|
||||||
|
|
||||||
onExited: function (exitCode) {
|
|
||||||
if (exitCode === 0) {
|
|
||||||
log.info("Cleaned up orphaned idle inhibitor process(es) from a previous session");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
|
|||||||
@@ -71,8 +71,13 @@ Item {
|
|||||||
PathCubic {}
|
PathCubic {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: pathMoveComp
|
||||||
|
PathMove {}
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
shapePath.pathElements.push(Qt.createQmlObject('import QtQuick; import QtQuick.Shapes; PathMove {}', shapePath));
|
shapePath.pathElements.push(pathMoveComp.createObject(shapePath));
|
||||||
|
|
||||||
for (let i = 0; i < segments; i++) {
|
for (let i = 0; i < segments; i++) {
|
||||||
const seg = cubicSegment.createObject(shapePath);
|
const seg = cubicSegment.createObject(shapePath);
|
||||||
|
|||||||
@@ -182,14 +182,19 @@ Item {
|
|||||||
setBarContext(pos, bottomGap);
|
setBarContext(pos, bottomGap);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Briefly forces backgroundWindow.updatesEnabled true while the surface
|
// Holds backgroundWindow.updatesEnabled true while the surface body is
|
||||||
// body changes, so the contentHoleRect mask carve-out commits to the
|
// changing so the contentHoleRect mask carve-out tracks the popup body —
|
||||||
// compositor — otherwise the input region stays stuck at the popup's
|
// otherwise clicks in newly-grown areas hit the bg window and dismiss.
|
||||||
// initial size and clicks in any newly-grown area dismiss the popup.
|
// Debounced off ~250ms after the last change so a stable popup doesn't
|
||||||
// Cleared by the frameSwapped Connections below as soon as the dirty
|
// keep the bg window in active-update mode.
|
||||||
// frame ships, so the bg window goes back to skipping buffer updates.
|
|
||||||
property bool _bgCommitWindow: false
|
property bool _bgCommitWindow: false
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: bgCommitSettleTimer
|
||||||
|
interval: 250
|
||||||
|
onTriggered: root._bgCommitWindow = false
|
||||||
|
}
|
||||||
|
|
||||||
function _setSurfaceGeometry(bodyX, bodyY, bodyW, bodyH) {
|
function _setSurfaceGeometry(bodyX, bodyY, bodyW, bodyH) {
|
||||||
const newX = Theme.snap(bodyX, dpr);
|
const newX = Theme.snap(bodyX, dpr);
|
||||||
const newY = Theme.snap(bodyY, dpr);
|
const newY = Theme.snap(bodyY, dpr);
|
||||||
@@ -206,15 +211,7 @@ Item {
|
|||||||
_surfaceH = _surfaceBodyH + shadowBuffer * 2;
|
_surfaceH = _surfaceBodyH + shadowBuffer * 2;
|
||||||
if (changed && backgroundWindow.visible) {
|
if (changed && backgroundWindow.visible) {
|
||||||
_bgCommitWindow = true;
|
_bgCommitWindow = true;
|
||||||
}
|
bgCommitSettleTimer.restart();
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: backgroundWindow
|
|
||||||
ignoreUnknownSignals: true
|
|
||||||
function onFrameSwapped() {
|
|
||||||
if (root._bgCommitWindow)
|
|
||||||
root._bgCommitWindow = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,31 +5,28 @@ Item {
|
|||||||
|
|
||||||
readonly property real edgeSize: 8
|
readonly property real edgeSize: 8
|
||||||
required property var targetWindow
|
required property var targetWindow
|
||||||
property bool supported: typeof targetWindow.startSystemMove === "function"
|
|
||||||
readonly property bool canMaximize: targetWindow.minimumSize.width !== targetWindow.maximumSize.width || targetWindow.minimumSize.height !== targetWindow.maximumSize.height
|
readonly property bool canMaximize: targetWindow.minimumSize.width !== targetWindow.maximumSize.width || targetWindow.minimumSize.height !== targetWindow.maximumSize.height
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
function tryStartMove() {
|
function tryStartMove() {
|
||||||
if (!supported)
|
|
||||||
return;
|
|
||||||
targetWindow.startSystemMove();
|
targetWindow.startSystemMove();
|
||||||
}
|
}
|
||||||
|
|
||||||
function tryStartResize(edges) {
|
function tryStartResize(edges) {
|
||||||
if (!supported || !canMaximize)
|
if (!canMaximize)
|
||||||
return;
|
return;
|
||||||
targetWindow.startSystemResize(edges);
|
targetWindow.startSystemResize(edges);
|
||||||
}
|
}
|
||||||
|
|
||||||
function tryToggleMaximize() {
|
function tryToggleMaximize() {
|
||||||
if (!supported || !canMaximize)
|
if (!canMaximize)
|
||||||
return;
|
return;
|
||||||
targetWindow.maximized = !targetWindow.maximized;
|
targetWindow.maximized = !targetWindow.maximized;
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
visible: root.supported && root.canMaximize
|
visible: root.canMaximize
|
||||||
height: root.edgeSize
|
height: root.edgeSize
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -41,7 +38,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
visible: root.supported && root.canMaximize
|
visible: root.canMaximize
|
||||||
width: root.edgeSize
|
width: root.edgeSize
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
@@ -53,7 +50,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
visible: root.supported && root.canMaximize
|
visible: root.canMaximize
|
||||||
width: root.edgeSize
|
width: root.edgeSize
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
@@ -65,7 +62,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
visible: root.supported && root.canMaximize
|
visible: root.canMaximize
|
||||||
width: root.edgeSize
|
width: root.edgeSize
|
||||||
height: root.edgeSize
|
height: root.edgeSize
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -75,7 +72,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
visible: root.supported && root.canMaximize
|
visible: root.canMaximize
|
||||||
width: root.edgeSize
|
width: root.edgeSize
|
||||||
height: root.edgeSize
|
height: root.edgeSize
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -85,7 +82,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
visible: root.supported && root.canMaximize
|
visible: root.canMaximize
|
||||||
height: root.edgeSize
|
height: root.edgeSize
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -97,7 +94,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
visible: root.supported && root.canMaximize
|
visible: root.canMaximize
|
||||||
width: root.edgeSize
|
width: root.edgeSize
|
||||||
height: root.edgeSize
|
height: root.edgeSize
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -107,7 +104,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
visible: root.supported && root.canMaximize
|
visible: root.canMaximize
|
||||||
width: root.edgeSize
|
width: root.edgeSize
|
||||||
height: root.edgeSize
|
height: root.edgeSize
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|||||||
@@ -41,17 +41,8 @@ Item {
|
|||||||
property string _actionType: ""
|
property string _actionType: ""
|
||||||
property bool addingNewKey: false
|
property bool addingNewKey: false
|
||||||
property bool useCustomCompositor: false
|
property bool useCustomCompositor: false
|
||||||
property var _shortcutInhibitor: null
|
|
||||||
property bool _altShiftGhost: false
|
property bool _altShiftGhost: false
|
||||||
|
|
||||||
readonly property bool _shortcutInhibitorAvailable: {
|
|
||||||
try {
|
|
||||||
return typeof ShortcutInhibitor !== "undefined";
|
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
readonly property var keys: bindData.keys || []
|
readonly property var keys: bindData.keys || []
|
||||||
readonly property bool hasOverride: {
|
readonly property bool hasOverride: {
|
||||||
for (var i = 0; i < keys.length; i++) {
|
for (var i = 0; i < keys.length; i++) {
|
||||||
@@ -251,41 +242,17 @@ Item {
|
|||||||
addingNewKey = false;
|
addingNewKey = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _createShortcutInhibitor() {
|
ShortcutInhibitor {
|
||||||
if (!_shortcutInhibitorAvailable || _shortcutInhibitor)
|
window: root.panelWindow
|
||||||
return;
|
enabled: root.recording
|
||||||
const qmlString = `
|
|
||||||
import QtQuick
|
|
||||||
import Quickshell.Wayland
|
|
||||||
|
|
||||||
ShortcutInhibitor {
|
|
||||||
enabled: false
|
|
||||||
window: null
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
_shortcutInhibitor = Qt.createQmlObject(qmlString, root, "KeybindItem.ShortcutInhibitor");
|
|
||||||
_shortcutInhibitor.enabled = Qt.binding(() => root.recording);
|
|
||||||
_shortcutInhibitor.window = Qt.binding(() => root.panelWindow);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _destroyShortcutInhibitor() {
|
|
||||||
if (_shortcutInhibitor) {
|
|
||||||
_shortcutInhibitor.enabled = false;
|
|
||||||
_shortcutInhibitor.destroy();
|
|
||||||
_shortcutInhibitor = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function startRecording() {
|
function startRecording() {
|
||||||
_destroyShortcutInhibitor();
|
|
||||||
_createShortcutInhibitor();
|
|
||||||
recording = true;
|
recording = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopRecording() {
|
function stopRecording() {
|
||||||
recording = false;
|
recording = false;
|
||||||
_destroyShortcutInhibitor();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|
||||||
@@ -8,7 +10,6 @@ Item {
|
|||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
required property var targetWindow
|
required property var targetWindow
|
||||||
property var blurItem: null
|
|
||||||
property bool blurEnabled: Theme.connectedSurfaceBlurEnabled
|
property bool blurEnabled: Theme.connectedSurfaceBlurEnabled
|
||||||
property real blurX: 0
|
property real blurX: 0
|
||||||
property real blurY: 0
|
property real blurY: 0
|
||||||
@@ -16,54 +17,38 @@ Item {
|
|||||||
property real blurHeight: 0
|
property real blurHeight: 0
|
||||||
property real blurRadius: 0
|
property real blurRadius: 0
|
||||||
|
|
||||||
property var _region: null
|
readonly property bool _active: blurEnabled && BlurService.enabled && !!targetWindow
|
||||||
|
|
||||||
|
Region {
|
||||||
|
id: blurRegion
|
||||||
|
x: root.blurX
|
||||||
|
y: root.blurY
|
||||||
|
width: root.blurWidth
|
||||||
|
height: root.blurHeight
|
||||||
|
radius: root.blurRadius
|
||||||
|
}
|
||||||
|
|
||||||
function _apply() {
|
function _apply() {
|
||||||
if (!blurEnabled || !BlurService.enabled || !targetWindow) {
|
if (!targetWindow)
|
||||||
_cleanup();
|
|
||||||
return;
|
return;
|
||||||
}
|
targetWindow.BackgroundEffect.blurRegion = _active ? blurRegion : null;
|
||||||
|
|
||||||
if (!_region)
|
|
||||||
_region = BlurService.createBlurRegion(targetWindow);
|
|
||||||
|
|
||||||
if (!_region)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_region.item = Qt.binding(() => root.blurItem);
|
|
||||||
_region.x = Qt.binding(() => root.blurX);
|
|
||||||
_region.y = Qt.binding(() => root.blurY);
|
|
||||||
_region.width = Qt.binding(() => root.blurWidth);
|
|
||||||
_region.height = Qt.binding(() => root.blurHeight);
|
|
||||||
_region.radius = Qt.binding(() => root.blurRadius);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function _cleanup() {
|
on_ActiveChanged: _apply()
|
||||||
if (!_region)
|
onTargetWindowChanged: _apply()
|
||||||
return;
|
|
||||||
BlurService.destroyBlurRegion(targetWindow, _region);
|
|
||||||
_region = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
onBlurEnabledChanged: _apply()
|
|
||||||
|
|
||||||
Connections {
|
|
||||||
target: BlurService
|
|
||||||
function onEnabledChanged() {
|
|
||||||
root._apply();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.targetWindow ?? null
|
target: root.targetWindow ?? null
|
||||||
|
ignoreUnknownSignals: true
|
||||||
function onVisibleChanged() {
|
function onVisibleChanged() {
|
||||||
if (root.targetWindow && root.targetWindow.visible) {
|
if (root.targetWindow && root.targetWindow.visible)
|
||||||
root._region = null;
|
|
||||||
root._apply();
|
root._apply();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: _apply()
|
Component.onCompleted: _apply()
|
||||||
Component.onDestruction: _cleanup()
|
Component.onDestruction: {
|
||||||
|
if (targetWindow)
|
||||||
|
targetWindow.BackgroundEffect.blurRegion = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
//@ pragma Env QT_WAYLAND_DISABLE_WINDOWDECORATION=1
|
//@ pragma Env QT_WAYLAND_DISABLE_WINDOWDECORATION=1
|
||||||
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Material
|
//@ pragma Env QT_QUICK_CONTROLS_STYLE=Material
|
||||||
//@ pragma UseQApplication
|
//@ pragma UseQApplication
|
||||||
// ! TODO - replace pragma AppId when next QS releases, remove from GO launch injection.
|
//@ pragma AppId com.danklinux.dms
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
|||||||
Reference in New Issue
Block a user