mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-02 02:22:06 -04:00
Compare commits
4 Commits
0b88055742
...
3194fc3fbe
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3194fc3fbe | ||
|
|
3318864ece | ||
|
|
e224417593 | ||
|
|
3f7f6c5d2c |
@@ -53,7 +53,6 @@ var (
|
||||
clipCopyPasteOnce bool
|
||||
clipCopyType string
|
||||
clipCopyDownload bool
|
||||
clipCopyCacheFile string
|
||||
clipJSONOutput bool
|
||||
)
|
||||
|
||||
@@ -192,8 +191,6 @@ func init() {
|
||||
clipCopyCmd.Flags().BoolVarP(&clipCopyPasteOnce, "paste-once", "o", false, "Exit after first paste")
|
||||
clipCopyCmd.Flags().StringVarP(&clipCopyType, "type", "t", "text/plain;charset=utf-8", "MIME type")
|
||||
clipCopyCmd.Flags().BoolVarP(&clipCopyDownload, "download", "d", false, "Download URL as image and copy as file")
|
||||
clipCopyCmd.Flags().StringVar(&clipCopyCacheFile, "cache-file", "", "")
|
||||
clipCopyCmd.Flags().MarkHidden("cache-file")
|
||||
|
||||
clipWatchCmd.Flags().BoolVar(&clipJSONOutput, "json", false, "Output as JSON")
|
||||
clipHistoryCmd.Flags().BoolVar(&clipJSONOutput, "json", false, "Output as JSON")
|
||||
@@ -224,13 +221,6 @@ func init() {
|
||||
}
|
||||
|
||||
func runClipCopy(cmd *cobra.Command, args []string) {
|
||||
if clipCopyCacheFile != "" {
|
||||
if err := clipboard.ServeCacheFile(clipCopyCacheFile, clipCopyType, clipCopyPasteOnce); err != nil {
|
||||
log.Fatalf("serve cache file: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var data []byte
|
||||
copyFromStdin := false
|
||||
|
||||
|
||||
@@ -820,10 +820,14 @@ func checkOptionalDependencies() []checkResult {
|
||||
results = append(results, checkImageFormatPlugins()...)
|
||||
|
||||
terminals := []string{"ghostty", "kitty", "alacritty", "foot", "wezterm"}
|
||||
if idx := slices.IndexFunc(terminals, utils.CommandExists); idx >= 0 {
|
||||
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, terminals[idx], "", optionalFeaturesURL})
|
||||
terminals = slices.DeleteFunc(terminals, func(t string) bool {
|
||||
return !utils.CommandExists(t)
|
||||
})
|
||||
|
||||
if len(terminals) > 0 {
|
||||
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusOK, strings.Join(terminals, ", "), "", optionalFeaturesURL})
|
||||
} else {
|
||||
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, or alacritty", optionalFeaturesURL})
|
||||
results = append(results, checkResult{catOptionalFeatures, "Terminal", statusWarn, "None found", "Install ghostty, kitty, foot or alacritty", optionalFeaturesURL})
|
||||
}
|
||||
|
||||
networkResult, err := network.DetectNetworkStack()
|
||||
|
||||
@@ -5,6 +5,7 @@ package main
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
)
|
||||
|
||||
@@ -30,7 +31,9 @@ func init() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
if os.Geteuid() == 0 {
|
||||
clipboard.MaybeServeAndExit()
|
||||
|
||||
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
|
||||
log.Fatal("This program should not be run as root. Exiting.")
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ package main
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
)
|
||||
|
||||
@@ -27,7 +28,9 @@ func init() {
|
||||
}
|
||||
|
||||
func main() {
|
||||
if os.Geteuid() == 0 {
|
||||
clipboard.MaybeServeAndExit()
|
||||
|
||||
if os.Geteuid() == 0 && !isReadOnlyCommand(os.Args) {
|
||||
log.Fatal("This program should not be run as root. Exiting.")
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,22 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// isReadOnlyCommand returns true if the CLI args indicate a command that is
|
||||
// safe to run as root (e.g. shell completion, help).
|
||||
func isReadOnlyCommand(args []string) bool {
|
||||
for _, arg := range args[1:] {
|
||||
if strings.HasPrefix(arg, "-") {
|
||||
continue
|
||||
}
|
||||
switch arg {
|
||||
case "completion", "help", "__complete":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isArchPackageInstalled(packageName string) bool {
|
||||
cmd := exec.Command("pacman", "-Q", packageName)
|
||||
err := cmd.Run()
|
||||
|
||||
@@ -12,35 +12,95 @@ import (
|
||||
wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||
)
|
||||
|
||||
const envServe = "_DMS_CLIPBOARD_SERVE"
|
||||
const envMime = "_DMS_CLIPBOARD_MIME"
|
||||
const envPasteOnce = "_DMS_CLIPBOARD_PASTE_ONCE"
|
||||
const envCacheFile = "_DMS_CLIPBOARD_CACHE"
|
||||
|
||||
// MaybeServeAndExit intercepts before cobra when re-exec'd as a clipboard
|
||||
// child. Reads source data into memory, deletes any cache file, then serves.
|
||||
func MaybeServeAndExit() {
|
||||
if os.Getenv(envServe) == "" {
|
||||
return
|
||||
}
|
||||
|
||||
mimeType := os.Getenv(envMime)
|
||||
pasteOnce := os.Getenv(envPasteOnce) == "1"
|
||||
cachePath := os.Getenv(envCacheFile)
|
||||
|
||||
var data []byte
|
||||
var err error
|
||||
|
||||
switch {
|
||||
case cachePath != "":
|
||||
data, err = os.ReadFile(cachePath)
|
||||
os.Remove(cachePath)
|
||||
default:
|
||||
data, err = io.ReadAll(os.Stdin)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "clipboard: read source: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := serveClipboard(data, mimeType, pasteOnce); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "clipboard: serve: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
func Copy(data []byte, mimeType string) error {
|
||||
return copyForkCached(data, mimeType, false)
|
||||
}
|
||||
|
||||
func CopyOpts(data []byte, mimeType string, foreground, pasteOnce bool) error {
|
||||
if foreground {
|
||||
return copyServeWithWriter(func(writer io.Writer) error {
|
||||
total := 0
|
||||
for total < len(data) {
|
||||
n, err := writer.Write(data[total:])
|
||||
total += n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if total != len(data) {
|
||||
return io.ErrShortWrite
|
||||
}
|
||||
return nil
|
||||
}, mimeType, pasteOnce)
|
||||
return serveClipboard(data, mimeType, pasteOnce)
|
||||
}
|
||||
return copyForkCached(data, mimeType, pasteOnce)
|
||||
}
|
||||
|
||||
func CopyReader(data io.Reader, mimeType string, foreground, pasteOnce bool) error {
|
||||
if !foreground {
|
||||
return copyFork(data, mimeType, pasteOnce)
|
||||
if foreground {
|
||||
buf, err := io.ReadAll(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("read source: %w", err)
|
||||
}
|
||||
return serveClipboard(buf, mimeType, pasteOnce)
|
||||
}
|
||||
return copyServeReader(data, mimeType, pasteOnce)
|
||||
return copyFork(data, mimeType, pasteOnce)
|
||||
}
|
||||
|
||||
func newForkCmd(mimeType string, pasteOnce bool, extra ...string) *exec.Cmd {
|
||||
cmd := exec.Command(os.Args[0])
|
||||
cmd.Stderr = nil
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||
cmd.Env = append(os.Environ(),
|
||||
envServe+"=1",
|
||||
envMime+"="+mimeType,
|
||||
)
|
||||
if pasteOnce {
|
||||
cmd.Env = append(cmd.Env, envPasteOnce+"=1")
|
||||
}
|
||||
cmd.Env = append(cmd.Env, extra...)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func waitReady(cmd *exec.Cmd) error {
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("stdout pipe: %w", err)
|
||||
}
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("start: %w", err)
|
||||
}
|
||||
var buf [1]byte
|
||||
if _, err := stdout.Read(buf[:]); err != nil {
|
||||
return fmt.Errorf("waiting for clipboard ready: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyForkCached(data []byte, mimeType string, pasteOnce bool) error {
|
||||
@@ -60,65 +120,34 @@ func copyForkCached(data []byte, mimeType string, pasteOnce bool) error {
|
||||
return fmt.Errorf("close cache file: %w", err)
|
||||
}
|
||||
|
||||
args := []string{os.Args[0], "cl", "copy", "--foreground", "--cache-file", cachePath}
|
||||
if pasteOnce {
|
||||
args = append(args, "--paste-once")
|
||||
}
|
||||
args = append(args, "--type", mimeType)
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd := newForkCmd(mimeType, pasteOnce, envCacheFile+"="+cachePath)
|
||||
cmd.Stdin = nil
|
||||
cmd.Stderr = nil
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||
cmd.Env = append(os.Environ(), "DMS_CLIP_FORKED=1")
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
if err := waitReady(cmd); err != nil {
|
||||
os.Remove(cachePath)
|
||||
return fmt.Errorf("stdout pipe: %w", err)
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
os.Remove(cachePath)
|
||||
return fmt.Errorf("start: %w", err)
|
||||
}
|
||||
|
||||
var buf [1]byte
|
||||
if _, err := stdout.Read(buf[:]); err != nil {
|
||||
return fmt.Errorf("waiting for clipboard ready: %w", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
|
||||
args := []string{os.Args[0], "cl", "copy", "--foreground"}
|
||||
if pasteOnce {
|
||||
args = append(args, "--paste-once")
|
||||
}
|
||||
args = append(args, "--type", mimeType)
|
||||
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Stderr = nil
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||
cmd.Env = append(os.Environ(), "DMS_CLIP_FORKED=1")
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("stdout pipe: %w", err)
|
||||
}
|
||||
cmd := newForkCmd(mimeType, pasteOnce)
|
||||
|
||||
switch src := data.(type) {
|
||||
case *os.File:
|
||||
cmd.Stdin = src
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("start: %w", err)
|
||||
}
|
||||
return waitReady(cmd)
|
||||
|
||||
default:
|
||||
stdin, err := cmd.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("stdin pipe: %w", err)
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("stdout pipe: %w", err)
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return fmt.Errorf("start: %w", err)
|
||||
}
|
||||
@@ -129,63 +158,22 @@ func copyFork(data io.Reader, mimeType string, pasteOnce bool) error {
|
||||
if err := stdin.Close(); err != nil {
|
||||
return fmt.Errorf("close stdin: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var buf [1]byte
|
||||
if _, err := stdout.Read(buf[:]); err != nil {
|
||||
return fmt.Errorf("waiting for clipboard ready: %w", err)
|
||||
var buf [1]byte
|
||||
if _, err := stdout.Read(buf[:]); err != nil {
|
||||
return fmt.Errorf("waiting for clipboard ready: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func signalReady() {
|
||||
if os.Getenv("DMS_CLIP_FORKED") == "" {
|
||||
if os.Getenv(envServe) == "" {
|
||||
return
|
||||
}
|
||||
os.Stdout.Write([]byte{1})
|
||||
}
|
||||
|
||||
func ServeCacheFile(path, mimeType string, pasteOnce bool) error {
|
||||
defer os.Remove(path)
|
||||
return copyServeWithWriter(func(writer io.Writer) error {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open cache file: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
_, err = io.Copy(writer, f)
|
||||
return err
|
||||
}, mimeType, pasteOnce)
|
||||
}
|
||||
|
||||
func copyServeReader(data io.Reader, mimeType string, pasteOnce bool) error {
|
||||
cachedData, err := createClipboardCacheFile()
|
||||
if err != nil {
|
||||
return fmt.Errorf("create clipboard cache file: %w", err)
|
||||
}
|
||||
defer os.Remove(cachedData.Name())
|
||||
|
||||
if _, err := io.Copy(cachedData, data); err != nil {
|
||||
return fmt.Errorf("cache clipboard data: %w", err)
|
||||
}
|
||||
if err := cachedData.Close(); err != nil {
|
||||
return fmt.Errorf("close temp cache file: %w", err)
|
||||
}
|
||||
|
||||
return copyServeWithWriter(func(writer io.Writer) error {
|
||||
cachedFile, err := os.Open(cachedData.Name())
|
||||
if err != nil {
|
||||
return fmt.Errorf("open temp cache file: %w", err)
|
||||
}
|
||||
defer cachedFile.Close()
|
||||
|
||||
if _, err := io.Copy(writer, cachedFile); err != nil {
|
||||
return fmt.Errorf("write clipboard data: %w", err)
|
||||
}
|
||||
return nil
|
||||
}, mimeType, pasteOnce)
|
||||
}
|
||||
|
||||
func createClipboardCacheFile() (*os.File, error) {
|
||||
preferredDirs := []string{}
|
||||
|
||||
@@ -206,7 +194,7 @@ func createClipboardCacheFile() (*os.File, error) {
|
||||
return os.CreateTemp("", "dms-clipboard-*")
|
||||
}
|
||||
|
||||
func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOnce bool) error {
|
||||
func serveClipboard(data []byte, mimeType string, pasteOnce bool) error {
|
||||
display, err := wlclient.Connect("")
|
||||
if err != nil {
|
||||
return fmt.Errorf("wayland connect: %w", err)
|
||||
@@ -248,12 +236,10 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
||||
if bindErr != nil {
|
||||
return fmt.Errorf("registry bind: %w", bindErr)
|
||||
}
|
||||
|
||||
if dataControlMgr == nil {
|
||||
return fmt.Errorf("compositor does not support ext_data_control_manager_v1")
|
||||
}
|
||||
defer dataControlMgr.Destroy()
|
||||
|
||||
if seat == nil {
|
||||
return fmt.Errorf("no seat available")
|
||||
}
|
||||
@@ -292,18 +278,12 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
||||
|
||||
cancelled := make(chan struct{})
|
||||
pasted := make(chan struct{}, 1)
|
||||
sendErr := make(chan error, 1)
|
||||
|
||||
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
||||
defer syscall.Close(e.Fd)
|
||||
_ = syscall.SetNonblock(e.Fd, false)
|
||||
file := os.NewFile(uintptr(e.Fd), "pipe")
|
||||
defer file.Close()
|
||||
if err := writeTo(file); err != nil {
|
||||
select {
|
||||
case sendErr <- err:
|
||||
default:
|
||||
}
|
||||
}
|
||||
_, _ = file.Write(data)
|
||||
select {
|
||||
case pasted <- struct{}{}:
|
||||
default:
|
||||
@@ -325,8 +305,6 @@ func copyServeWithWriter(writeTo func(io.Writer) error, mimeType string, pasteOn
|
||||
select {
|
||||
case <-cancelled:
|
||||
return nil
|
||||
case err := <-sendErr:
|
||||
return err
|
||||
case <-pasted:
|
||||
if pasteOnce {
|
||||
return nil
|
||||
@@ -580,12 +558,10 @@ func copyMultiServe(offers []Offer, pasteOnce bool) error {
|
||||
if bindErr != nil {
|
||||
return fmt.Errorf("registry bind: %w", bindErr)
|
||||
}
|
||||
|
||||
if dataControlMgr == nil {
|
||||
return fmt.Errorf("compositor does not support ext_data_control_manager_v1")
|
||||
}
|
||||
defer dataControlMgr.Destroy()
|
||||
|
||||
if seat == nil {
|
||||
return fmt.Errorf("no seat available")
|
||||
}
|
||||
@@ -613,12 +589,12 @@ func copyMultiServe(offers []Offer, pasteOnce bool) error {
|
||||
pasted := make(chan struct{}, 1)
|
||||
|
||||
source.SetSendHandler(func(e ext_data_control.ExtDataControlSourceV1SendEvent) {
|
||||
defer syscall.Close(e.Fd)
|
||||
_ = syscall.SetNonblock(e.Fd, false)
|
||||
file := os.NewFile(uintptr(e.Fd), "pipe")
|
||||
defer file.Close()
|
||||
|
||||
if data, ok := offerMap[e.MimeType]; ok {
|
||||
file.Write(data)
|
||||
_, _ = file.Write(data)
|
||||
}
|
||||
|
||||
select {
|
||||
|
||||
@@ -434,6 +434,7 @@ Singleton {
|
||||
property bool soundNewNotification: true
|
||||
property bool soundVolumeChanged: true
|
||||
property bool soundPluggedIn: true
|
||||
property bool soundLogin: false
|
||||
|
||||
property int acMonitorTimeout: 0
|
||||
property int acLockTimeout: 0
|
||||
|
||||
@@ -242,6 +242,7 @@ var SPEC = {
|
||||
|
||||
soundsEnabled: { def: true },
|
||||
useSystemSoundTheme: { def: false },
|
||||
soundLogin: { def: false },
|
||||
soundNewNotification: { def: true },
|
||||
soundVolumeChanged: { def: true },
|
||||
soundPluggedIn: { def: true },
|
||||
|
||||
@@ -221,10 +221,22 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: loginSoundTimer
|
||||
// Half a second delay before playing login sound, otherwise the sound may be cut off
|
||||
// 50 is the minimum that seems to work, but 500 is safer
|
||||
interval: 500
|
||||
repeat: false
|
||||
onTriggered: {
|
||||
AudioService.playLoginSoundIfApplicable();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
dockRecreateDebounce.start();
|
||||
// Force PolkitService singleton to initialize
|
||||
PolkitService.polkitAvailable;
|
||||
loginSoundTimer.start();
|
||||
}
|
||||
|
||||
Loader {
|
||||
|
||||
@@ -91,6 +91,16 @@ Item {
|
||||
visible: AudioService.gsettingsAvailable
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
tab: "sounds"
|
||||
tags: ["sound", "login", "startup", "boot"]
|
||||
settingKey: "soundLogin"
|
||||
text: I18n.tr("Login")
|
||||
description: I18n.tr("Play sound after logging in")
|
||||
checked: SettingsData.soundLogin
|
||||
onToggled: checked => SettingsData.set("soundLogin", checked)
|
||||
}
|
||||
|
||||
SettingsToggleRow {
|
||||
tab: "sounds"
|
||||
tags: ["sound", "notification", "new"]
|
||||
|
||||
@@ -26,6 +26,7 @@ Singleton {
|
||||
property var powerUnplugSound: null
|
||||
property var normalNotificationSound: null
|
||||
property var criticalNotificationSound: null
|
||||
property var loginSound: null
|
||||
property real notificationsVolume: 1.0
|
||||
property bool notificationsAudioMuted: false
|
||||
|
||||
@@ -67,6 +68,16 @@ Singleton {
|
||||
}
|
||||
}
|
||||
|
||||
// Used in playLoginSoundIfApplicable()
|
||||
Process {
|
||||
id: loginSoundChecker
|
||||
onExited: (exitCode) => {
|
||||
if (exitCode === 0) {
|
||||
playLoginSound();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getAvailableSinks() {
|
||||
const hidden = SessionData.hiddenOutputDeviceNames ?? [];
|
||||
return Pipewire.nodes.values.filter(node => node.audio && node.isSink && !node.isStream && !hidden.includes(node.name));
|
||||
@@ -395,7 +406,7 @@ EOFCONFIG
|
||||
const themesToSearch = themeName !== "freedesktop" ? `${themeName} freedesktop` : themeName;
|
||||
|
||||
const script = `
|
||||
for event_key in audio-volume-change power-plug power-unplug message message-new-instant; do
|
||||
for event_key in audio-volume-change power-plug power-unplug message message-new-instant desktop-login; do
|
||||
found=0
|
||||
|
||||
case "$event_key" in
|
||||
@@ -457,7 +468,8 @@ EOFCONFIG
|
||||
"power-plug": "../assets/sounds/plasma/power-plug.wav",
|
||||
"power-unplug": "../assets/sounds/plasma/power-unplug.wav",
|
||||
"message": "../assets/sounds/freedesktop/message.wav",
|
||||
"message-new-instant": "../assets/sounds/freedesktop/message-new-instant.wav"
|
||||
"message-new-instant": "../assets/sounds/freedesktop/message-new-instant.wav",
|
||||
"desktop-login": "../assets/sounds/freedesktop/desktop-login.wav"
|
||||
};
|
||||
|
||||
const specialConditions = {
|
||||
@@ -551,6 +563,10 @@ EOFCONFIG
|
||||
criticalNotificationSound.destroy();
|
||||
criticalNotificationSound = null;
|
||||
}
|
||||
if (loginSound) {
|
||||
loginSound.destroy();
|
||||
loginSound = null;
|
||||
}
|
||||
}
|
||||
|
||||
function createSoundPlayers() {
|
||||
@@ -622,6 +638,19 @@ EOFCONFIG
|
||||
}
|
||||
}
|
||||
`, 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) {
|
||||
console.warn("AudioService: Error creating sound players:", e);
|
||||
}
|
||||
@@ -661,6 +690,31 @@ EOFCONFIG
|
||||
criticalNotificationSound.play();
|
||||
}
|
||||
|
||||
function playLoginSound() {
|
||||
if (!soundsAvailable || !loginSound || notificationsAudioMuted || isMediaPlaying()) {
|
||||
return;
|
||||
}
|
||||
loginSound.play();
|
||||
}
|
||||
|
||||
function playLoginSoundIfApplicable() {
|
||||
if (SettingsData.soundsEnabled && SettingsData.soundLogin && !notificationsAudioMuted) {
|
||||
// plays login sound on session start, but only if a specific file doesn't exist,
|
||||
// to prevent it from playing on every DMS restart during the session
|
||||
const runtimeDir = Quickshell.env("XDG_RUNTIME_DIR");
|
||||
const sessionId = Quickshell.env("XDG_SESSION_ID") || "0";
|
||||
|
||||
if (!runtimeDir) return;
|
||||
|
||||
const loginFile = `${runtimeDir}/danklinux.login-${sessionId}`;
|
||||
|
||||
// if file doesn't exist, touch it (0)
|
||||
// If it exists, do nothing (1)
|
||||
loginSoundChecker.command = ["sh", "-c", `[ ! -f ${loginFile} ] && touch ${loginFile}`];
|
||||
loginSoundChecker.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
function playVolumeChangeSoundIfEnabled() {
|
||||
if (SettingsData.soundsEnabled && SettingsData.soundVolumeChanged && !notificationsAudioMuted) {
|
||||
playVolumeChangeSound();
|
||||
|
||||
BIN
quickshell/assets/sounds/freedesktop/desktop-login.oga
Normal file
BIN
quickshell/assets/sounds/freedesktop/desktop-login.oga
Normal file
Binary file not shown.
BIN
quickshell/assets/sounds/freedesktop/desktop-login.wav
Normal file
BIN
quickshell/assets/sounds/freedesktop/desktop-login.wav
Normal file
Binary file not shown.
@@ -5101,6 +5101,26 @@
|
||||
],
|
||||
"description": "Play sounds for system events"
|
||||
},
|
||||
{
|
||||
"section": "soundLogin",
|
||||
"label": "Login",
|
||||
"tabIndex": 15,
|
||||
"category": "Sounds",
|
||||
"keywords": [
|
||||
"after",
|
||||
"audio",
|
||||
"boot",
|
||||
"effects",
|
||||
"logging",
|
||||
"login",
|
||||
"play",
|
||||
"sfx",
|
||||
"sound",
|
||||
"sounds",
|
||||
"startup"
|
||||
],
|
||||
"description": "Play sound after logging in"
|
||||
},
|
||||
{
|
||||
"section": "soundNewNotification",
|
||||
"label": "New Notification",
|
||||
|
||||
Reference in New Issue
Block a user