mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-11 16:22:09 -04:00
core: add DL helper, apply to TrackArt OSD, DankLocationSearch
- unrelated change to add gsettingsOrDconf helpers
This commit is contained in:
@@ -524,5 +524,6 @@ func getCommonCommands() []*cobra.Command {
|
|||||||
chromaCmd,
|
chromaCmd,
|
||||||
doctorCmd,
|
doctorCmd,
|
||||||
configCmd,
|
configCmd,
|
||||||
|
dlCmd,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
99
core/cmd/dms/commands_download.go
Normal file
99
core/cmd/dms/commands_download.go
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dlOutput string
|
||||||
|
var dlUserAgent string
|
||||||
|
var dlTimeout int
|
||||||
|
var dlIPv4Only bool
|
||||||
|
|
||||||
|
var dlCmd = &cobra.Command{
|
||||||
|
Use: "dl <url>",
|
||||||
|
Short: "Download a URL to stdout or file",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if err := runDownload(args[0]); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
dlCmd.Flags().StringVarP(&dlOutput, "output", "o", "", "Output file path (default: stdout)")
|
||||||
|
dlCmd.Flags().StringVar(&dlUserAgent, "user-agent", "", "Custom User-Agent header")
|
||||||
|
dlCmd.Flags().IntVar(&dlTimeout, "timeout", 10, "Request timeout in seconds")
|
||||||
|
dlCmd.Flags().BoolVarP(&dlIPv4Only, "ipv4", "4", false, "Force IPv4 only")
|
||||||
|
}
|
||||||
|
|
||||||
|
func runDownload(url string) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(dlTimeout)*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case dlUserAgent != "":
|
||||||
|
req.Header.Set("User-Agent", dlUserAgent)
|
||||||
|
default:
|
||||||
|
req.Header.Set("User-Agent", "DankMaterialShell/1.0 (Linux)")
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer := &net.Dialer{Timeout: 5 * time.Second}
|
||||||
|
transport := &http.Transport{DialContext: dialer.DialContext}
|
||||||
|
if dlIPv4Only {
|
||||||
|
transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
return dialer.DialContext(ctx, "tcp4", addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
client := &http.Client{Transport: transport}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("download failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
return fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dlOutput == "" {
|
||||||
|
_, err = io.Copy(os.Stdout, resp.Body)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if dir := filepath.Dir(dlOutput); dir != "." {
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("mkdir failed: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(dlOutput)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create failed: %w", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
if _, err := io.Copy(f, resp.Body); err != nil {
|
||||||
|
os.Remove(dlOutput)
|
||||||
|
return fmt.Errorf("write failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(dlOutput)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -652,16 +652,20 @@ func isDMSGTKActive(configDir string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func refreshGTK(mode ColorMode) {
|
func refreshGTK(mode ColorMode) {
|
||||||
exec.Command("gsettings", "set", "org.gnome.desktop.interface", "gtk-theme", "").Run()
|
if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", ""); err != nil {
|
||||||
exec.Command("gsettings", "set", "org.gnome.desktop.interface", "gtk-theme", mode.GTKTheme()).Run()
|
log.Warnf("Failed to reset gtk-theme: %v", err)
|
||||||
|
}
|
||||||
|
if err := utils.GsettingsSet("org.gnome.desktop.interface", "gtk-theme", mode.GTKTheme()); err != nil {
|
||||||
|
log.Warnf("Failed to set gtk-theme: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshGTK4() {
|
func refreshGTK4() {
|
||||||
output, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "color-scheme").Output()
|
output, err := utils.GsettingsGet("org.gnome.desktop.interface", "color-scheme")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
current := strings.Trim(strings.TrimSpace(string(output)), "'")
|
current := strings.Trim(output, "'")
|
||||||
|
|
||||||
var toggle string
|
var toggle string
|
||||||
if current == "prefer-dark" {
|
if current == "prefer-dark" {
|
||||||
@@ -670,17 +674,22 @@ func refreshGTK4() {
|
|||||||
toggle = "prefer-dark"
|
toggle = "prefer-dark"
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := exec.Command("gsettings", "set", "org.gnome.desktop.interface", "color-scheme", toggle).Run(); err != nil {
|
if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", toggle); err != nil {
|
||||||
|
log.Warnf("Failed to toggle color-scheme for GTK4 refresh: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
exec.Command("gsettings", "set", "org.gnome.desktop.interface", "color-scheme", current).Run()
|
if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", current); err != nil {
|
||||||
|
log.Warnf("Failed to restore color-scheme for GTK4 refresh: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func refreshQt6ct() {
|
func refreshQt6ct() {
|
||||||
confPath := filepath.Join(utils.XDGConfigHome(), "qt6ct", "qt6ct.conf")
|
confPath := filepath.Join(utils.XDGConfigHome(), "qt6ct", "qt6ct.conf")
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
_ = os.Chtimes(confPath, now, now)
|
if err := os.Chtimes(confPath, now, now); err != nil {
|
||||||
|
log.Warnf("Failed to touch qt6ct.conf: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func signalTerminals() {
|
func signalTerminals() {
|
||||||
@@ -716,8 +725,8 @@ func syncColorScheme(mode ColorMode) {
|
|||||||
scheme = "default"
|
scheme = "default"
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := exec.Command("gsettings", "set", "org.gnome.desktop.interface", "color-scheme", scheme).Run(); err != nil {
|
if err := utils.GsettingsSet("org.gnome.desktop.interface", "color-scheme", scheme); err != nil {
|
||||||
exec.Command("dconf", "write", "/org/gnome/desktop/interface/color-scheme", "'"+scheme+"'").Run()
|
log.Warnf("Failed to sync color-scheme: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -767,7 +776,9 @@ func closestAdwaitaAccent(primaryHex string) string {
|
|||||||
func syncAccentColor(primaryHex string) {
|
func syncAccentColor(primaryHex string) {
|
||||||
accent := closestAdwaitaAccent(primaryHex)
|
accent := closestAdwaitaAccent(primaryHex)
|
||||||
log.Infof("Setting GNOME accent color: %s", accent)
|
log.Infof("Setting GNOME accent color: %s", accent)
|
||||||
exec.Command("gsettings", "set", "org.gnome.desktop.interface", "accent-color", accent).Run()
|
if err := utils.GsettingsSet("org.gnome.desktop.interface", "accent-color", accent); err != nil {
|
||||||
|
log.Warnf("Failed to set accent-color: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type TemplateCheck struct {
|
type TemplateCheck struct {
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ package screenshot
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ThemeColors struct {
|
type ThemeColors struct {
|
||||||
@@ -83,12 +83,11 @@ func getColorsFilePath() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func isLightMode() bool {
|
func isLightMode() bool {
|
||||||
out, err := exec.Command("gsettings", "get", "org.gnome.desktop.interface", "color-scheme").Output()
|
scheme, err := utils.GsettingsGet("org.gnome.desktop.interface", "color-scheme")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
scheme := strings.TrimSpace(string(out))
|
|
||||||
switch scheme {
|
switch scheme {
|
||||||
case "'prefer-light'", "'default'":
|
case "'prefer-light'", "'default'":
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package freedesktop
|
package freedesktop
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -107,22 +105,8 @@ func (m *Manager) GetUserIconFile(username string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) SetIconTheme(iconTheme string) error {
|
func (m *Manager) SetIconTheme(iconTheme string) error {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
|
if err := utils.GsettingsSet("org.gnome.desktop.interface", "icon-theme", iconTheme); err != nil {
|
||||||
defer cancel()
|
return fmt.Errorf("failed to set icon theme: %w", err)
|
||||||
|
|
||||||
check := exec.CommandContext(ctx, "gsettings", "writable", "org.gnome.desktop.interface", "icon-theme")
|
|
||||||
if err := check.Run(); err == nil {
|
|
||||||
cmd := exec.CommandContext(ctx, "gsettings", "set", "org.gnome.desktop.interface", "icon-theme", iconTheme)
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("gsettings set failed: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
checkDconf := exec.CommandContext(ctx, "dconf", "write", "/org/gnome/desktop/interface/icon-theme", fmt.Sprintf("'%s'", iconTheme))
|
|
||||||
if err := checkDconf.Run(); err != nil {
|
|
||||||
return fmt.Errorf("both gsettings and dconf unavailable or failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
31
core/internal/utils/gsettings.go
Normal file
31
core/internal/utils/gsettings.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func dconfPath(schema, key string) string {
|
||||||
|
return "/" + strings.ReplaceAll(schema, ".", "/") + "/" + key
|
||||||
|
}
|
||||||
|
|
||||||
|
// GsettingsGet reads a gsettings value, falling back to dconf read.
|
||||||
|
func GsettingsGet(schema, key string) (string, error) {
|
||||||
|
if out, err := exec.Command("gsettings", "get", schema, key).Output(); err == nil {
|
||||||
|
return strings.TrimSpace(string(out)), nil
|
||||||
|
}
|
||||||
|
out, err := exec.Command("dconf", "read", dconfPath(schema, key)).Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("gsettings/dconf get failed for %s %s: %w", schema, key, err)
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(out)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GsettingsSet writes a gsettings value, falling back to dconf write.
|
||||||
|
func GsettingsSet(schema, key, value string) error {
|
||||||
|
if err := exec.Command("gsettings", "set", schema, key, value).Run(); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return exec.Command("dconf", "write", dconfPath(schema, key), "'"+value+"'").Run()
|
||||||
|
}
|
||||||
@@ -853,7 +853,8 @@ Item {
|
|||||||
icon: "folder",
|
icon: "folder",
|
||||||
priority: 4,
|
priority: 4,
|
||||||
items: fileItems,
|
items: fileItems,
|
||||||
collapsed: collapsedSections["files"] || false
|
collapsed: collapsedSections["files"] || false,
|
||||||
|
flatStartIndex: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
var newSections;
|
var newSections;
|
||||||
@@ -1284,7 +1285,11 @@ Item {
|
|||||||
},
|
},
|
||||||
actions: [],
|
actions: [],
|
||||||
primaryAction: null,
|
primaryAction: null,
|
||||||
_diskCached: true
|
_diskCached: true,
|
||||||
|
_hName: "",
|
||||||
|
_hSub: "",
|
||||||
|
_hRich: false,
|
||||||
|
_preScored: undefined
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
sectionsData.push({
|
sectionsData.push({
|
||||||
@@ -1293,7 +1298,8 @@ Item {
|
|||||||
icon: s.icon || "",
|
icon: s.icon || "",
|
||||||
priority: s.priority || 0,
|
priority: s.priority || 0,
|
||||||
items: items,
|
items: items,
|
||||||
collapsed: false
|
collapsed: false,
|
||||||
|
flatStartIndex: 0
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return sectionsData;
|
return sectionsData;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.pragma library
|
.pragma library
|
||||||
|
|
||||||
.import "ControllerUtils.js" as Utils
|
.import "ControllerUtils.js" as Utils
|
||||||
|
|
||||||
function transformApp(app, override, defaultActions, primaryActionLabel) {
|
function transformApp(app, override, defaultActions, primaryActionLabel) {
|
||||||
var appId = app.id || app.execString || app.exec || "";
|
var appId = app.id || app.execString || app.exec || "";
|
||||||
@@ -31,7 +31,11 @@ function transformApp(app, override, defaultActions, primaryActionLabel) {
|
|||||||
name: primaryActionLabel,
|
name: primaryActionLabel,
|
||||||
icon: "open_in_new",
|
icon: "open_in_new",
|
||||||
action: "launch"
|
action: "launch"
|
||||||
}
|
},
|
||||||
|
_hName: "",
|
||||||
|
_hSub: "",
|
||||||
|
_hRich: false,
|
||||||
|
_preScored: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,7 +70,11 @@ function transformCoreApp(app, openLabel) {
|
|||||||
name: openLabel,
|
name: openLabel,
|
||||||
icon: "open_in_new",
|
icon: "open_in_new",
|
||||||
action: "launch"
|
action: "launch"
|
||||||
}
|
},
|
||||||
|
_hName: "",
|
||||||
|
_hSub: "",
|
||||||
|
_hRich: false,
|
||||||
|
_preScored: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +108,11 @@ function transformBuiltInLauncherItem(item, pluginId, openLabel) {
|
|||||||
name: openLabel,
|
name: openLabel,
|
||||||
icon: "open_in_new",
|
icon: "open_in_new",
|
||||||
action: "execute"
|
action: "execute"
|
||||||
}
|
},
|
||||||
|
_hName: "",
|
||||||
|
_hSub: "",
|
||||||
|
_hRich: false,
|
||||||
|
_preScored: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +145,11 @@ function transformFileResult(file, openLabel, openFolderLabel, copyPathLabel) {
|
|||||||
name: openLabel,
|
name: openLabel,
|
||||||
icon: "open_in_new",
|
icon: "open_in_new",
|
||||||
action: "open"
|
action: "open"
|
||||||
}
|
},
|
||||||
|
_hName: "",
|
||||||
|
_hSub: "",
|
||||||
|
_hRich: false,
|
||||||
|
_preScored: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +182,11 @@ function transformPluginItem(item, pluginId, selectLabel) {
|
|||||||
name: selectLabel,
|
name: selectLabel,
|
||||||
icon: "check",
|
icon: "check",
|
||||||
action: "execute"
|
action: "execute"
|
||||||
}
|
},
|
||||||
|
_hName: "",
|
||||||
|
_hSub: "",
|
||||||
|
_hRich: false,
|
||||||
|
_preScored: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -188,7 +208,11 @@ function createCalculatorItem(calc, query, copyLabel) {
|
|||||||
name: copyLabel,
|
name: copyLabel,
|
||||||
icon: "content_copy",
|
icon: "content_copy",
|
||||||
action: "copy"
|
action: "copy"
|
||||||
}
|
},
|
||||||
|
_hName: "",
|
||||||
|
_hSub: "",
|
||||||
|
_hRich: false,
|
||||||
|
_preScored: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,6 +242,10 @@ function createPluginBrowseItem(pluginId, plugin, trigger, isBuiltIn, isAllowed,
|
|||||||
name: browseLabel,
|
name: browseLabel,
|
||||||
icon: "arrow_forward",
|
icon: "arrow_forward",
|
||||||
action: "browse_plugin"
|
action: "browse_plugin"
|
||||||
}
|
},
|
||||||
|
_hName: "",
|
||||||
|
_hSub: "",
|
||||||
|
_hRich: false,
|
||||||
|
_preScored: undefined
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -187,7 +187,8 @@ function groupBySection(scoredItems, sectionOrder, sortAlphabetically, maxPerSec
|
|||||||
icon: sectionOrder[i].icon,
|
icon: sectionOrder[i].icon,
|
||||||
priority: sectionOrder[i].priority,
|
priority: sectionOrder[i].priority,
|
||||||
items: [],
|
items: [],
|
||||||
collapsed: false
|
collapsed: false,
|
||||||
|
flatStartIndex: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,6 +221,7 @@ function groupBySection(scoredItems, sectionOrder, sortAlphabetically, maxPerSec
|
|||||||
|
|
||||||
function flattenSections(sections) {
|
function flattenSections(sections) {
|
||||||
var flat = []
|
var flat = []
|
||||||
|
flat._sectionBounds = null
|
||||||
var bounds = {}
|
var bounds = {}
|
||||||
|
|
||||||
for (var i = 0; i < sections.length; i++) {
|
for (var i = 0; i < sections.length; i++) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Layouts
|
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
@@ -37,9 +36,23 @@ DankOSD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property bool _pendingShow: false
|
||||||
|
|
||||||
onPlayerChanged: {
|
onPlayerChanged: {
|
||||||
if (!player)
|
if (!player) {
|
||||||
|
_pendingShow = false;
|
||||||
hide();
|
hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: TrackArtService
|
||||||
|
function onLoadingChanged() {
|
||||||
|
if (!TrackArtService.loading && root._pendingShow) {
|
||||||
|
root._pendingShow = false;
|
||||||
|
root.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
@@ -48,10 +61,20 @@ DankOSD {
|
|||||||
function handleUpdate() {
|
function handleUpdate() {
|
||||||
if (!root.player?.trackTitle)
|
if (!root.player?.trackTitle)
|
||||||
return;
|
return;
|
||||||
if (SettingsData.osdMediaPlaybackEnabled) {
|
if (!SettingsData.osdMediaPlaybackEnabled)
|
||||||
TrackArtService.loadArtwork(player.trackArtUrl);
|
return;
|
||||||
|
|
||||||
|
TrackArtService.loadArtwork(player.trackArtUrl);
|
||||||
|
|
||||||
|
if (!player.trackArtUrl || player.trackArtUrl === "") {
|
||||||
root.show();
|
root.show();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
if (!TrackArtService.loading) {
|
||||||
|
root.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root._pendingShow = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onTrackArtUrlChanged() {
|
function onTrackArtUrlChanged() {
|
||||||
|
|||||||
@@ -3,62 +3,62 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
import Quickshell.Io
|
|
||||||
import Quickshell.Services.Mpris
|
import Quickshell.Services.Mpris
|
||||||
|
import qs.Common
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string _lastArtUrl: ""
|
property string _lastArtUrl: ""
|
||||||
property string _bgArtSource: ""
|
property string _bgArtSource: ""
|
||||||
|
|
||||||
property string activeTrackArtFile: ""
|
property string activeTrackArtFile: ""
|
||||||
|
property bool loading: false
|
||||||
|
|
||||||
function loadArtwork(url) {
|
function loadArtwork(url) {
|
||||||
if (!url || url == "") {
|
if (!url || url === "") {
|
||||||
_bgArtSource = "";
|
_bgArtSource = "";
|
||||||
_lastArtUrl = "";
|
_lastArtUrl = "";
|
||||||
|
loading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (url == _lastArtUrl)
|
if (url === _lastArtUrl)
|
||||||
return;
|
return;
|
||||||
_lastArtUrl = url;
|
_lastArtUrl = url;
|
||||||
if (url.startsWith("http://") || url.startsWith("https://")) {
|
|
||||||
const filename = "/tmp/.dankshell/trackart_" + Date.now() + ".jpg";
|
|
||||||
activeTrackArtFile = filename;
|
|
||||||
|
|
||||||
cleanupProcess.command = ["sh", "-c", "mkdir -p /tmp/.dankshell && find /tmp/.dankshell -name 'trackart_*' ! -name '" + filename.split('/').pop() + "' -delete"];
|
_bgArtSource = "";
|
||||||
cleanupProcess.running = true;
|
loading = true;
|
||||||
|
|
||||||
imageDownloader.command = ["curl", "-L", "-s", "--user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", "-o", filename, url];
|
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||||
imageDownloader.targetFile = filename;
|
const localUrl = url;
|
||||||
imageDownloader.running = true;
|
const filePath = url.startsWith("file://") ? url.substring(7) : url;
|
||||||
|
Proc.runCommand("trackart", ["test", "-f", filePath], (output, exitCode) => {
|
||||||
|
if (_lastArtUrl !== localUrl)
|
||||||
|
return;
|
||||||
|
if (exitCode === 0)
|
||||||
|
_bgArtSource = localUrl;
|
||||||
|
loading = false;
|
||||||
|
}, 200);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// otherwise
|
|
||||||
_bgArtSource = url;
|
const filename = "/tmp/.dankshell/trackart_" + Date.now() + ".jpg";
|
||||||
|
activeTrackArtFile = filename;
|
||||||
|
|
||||||
|
Proc.runCommand("trackart_cleanup", ["sh", "-c", "mkdir -p /tmp/.dankshell && find /tmp/.dankshell -name 'trackart_*' ! -name '" + filename.split('/').pop() + "' -delete"], null, 0);
|
||||||
|
|
||||||
|
Proc.runCommand("trackart", ["dms", "dl", "-o", filename, "--user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", url], (output, exitCode) => {
|
||||||
|
const resultPath = output.trim();
|
||||||
|
if (resultPath !== filename)
|
||||||
|
return;
|
||||||
|
if (exitCode === 0)
|
||||||
|
_bgArtSource = "file://" + resultPath;
|
||||||
|
loading = false;
|
||||||
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
property MprisPlayer activePlayer: MprisController.activePlayer
|
property MprisPlayer activePlayer: MprisController.activePlayer
|
||||||
|
|
||||||
onActivePlayerChanged: {
|
onActivePlayerChanged: {
|
||||||
loadArtwork(activePlayer.trackArtUrl);
|
loadArtwork(activePlayer?.trackArtUrl ?? "");
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: imageDownloader
|
|
||||||
running: false
|
|
||||||
property string targetFile: ""
|
|
||||||
|
|
||||||
onExited: exitCode => {
|
|
||||||
if (exitCode === 0 && targetFile)
|
|
||||||
_bgArtSource = "file://" + targetFile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: cleanupProcess
|
|
||||||
running: false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ Item {
|
|||||||
|
|
||||||
onActiveFocusChanged: {
|
onActiveFocusChanged: {
|
||||||
if (activeFocus) {
|
if (activeFocus) {
|
||||||
locationInput.forceActiveFocus()
|
locationInput.forceActiveFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,10 +27,10 @@ Item {
|
|||||||
signal locationSelected(string displayName, string coordinates)
|
signal locationSelected(string displayName, string coordinates)
|
||||||
|
|
||||||
function resetSearchState() {
|
function resetSearchState() {
|
||||||
locationSearchTimer.stop()
|
locationSearchTimer.stop();
|
||||||
dropdownHideTimer.stop()
|
dropdownHideTimer.stop();
|
||||||
isLoading = false
|
isLoading = false;
|
||||||
searchResultsModel.clear()
|
searchResultsModel.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -49,52 +48,49 @@ Item {
|
|||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (locationInput.text.length > 2) {
|
if (locationInput.text.length > 2) {
|
||||||
searchResultsModel.clear()
|
searchResultsModel.clear();
|
||||||
root.isLoading = true
|
root.isLoading = true;
|
||||||
const searchLocation = locationInput.text
|
const searchLocation = locationInput.text;
|
||||||
root.currentSearchText = searchLocation
|
root.currentSearchText = searchLocation;
|
||||||
const encodedLocation = encodeURIComponent(searchLocation)
|
const encodedLocation = encodeURIComponent(searchLocation);
|
||||||
const curlCommand = `curl -4 -s --connect-timeout 5 --max-time 10 'https://nominatim.openstreetmap.org/search?q=${encodedLocation}&format=json&limit=5&addressdetails=1'`
|
const searchUrl = "https://nominatim.openstreetmap.org/search?q=" + encodedLocation + "&format=json&limit=5&addressdetails=1";
|
||||||
Proc.runCommand("locationSearch", ["bash", "-c", curlCommand], (output, exitCode) => {
|
Proc.runCommand("locationSearch", ["dms", "dl", "-4", "--timeout", "10", searchUrl], (output, exitCode) => {
|
||||||
root.isLoading = false
|
root.isLoading = false;
|
||||||
if (exitCode !== 0) {
|
if (exitCode !== 0) {
|
||||||
searchResultsModel.clear()
|
searchResultsModel.clear();
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
if (root.currentSearchText !== locationInput.text)
|
if (root.currentSearchText !== locationInput.text)
|
||||||
return
|
return;
|
||||||
|
const raw = output.trim();
|
||||||
const raw = output.trim()
|
searchResultsModel.clear();
|
||||||
searchResultsModel.clear()
|
|
||||||
if (!raw || raw[0] !== "[") {
|
if (!raw || raw[0] !== "[") {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(raw)
|
const data = JSON.parse(raw);
|
||||||
if (data.length === 0) {
|
if (data.length === 0) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
for (var i = 0; i < Math.min(data.length, 5); i++) {
|
for (var i = 0; i < Math.min(data.length, 5); i++) {
|
||||||
const location = data[i]
|
const location = data[i];
|
||||||
if (location.display_name && location.lat && location.lon) {
|
if (location.display_name && location.lat && location.lon) {
|
||||||
const parts = location.display_name.split(', ')
|
const parts = location.display_name.split(', ');
|
||||||
let cleanName = parts[0]
|
let cleanName = parts[0];
|
||||||
if (parts.length > 1) {
|
if (parts.length > 1) {
|
||||||
const state = parts[parts.length - 2]
|
const state = parts[parts.length - 2];
|
||||||
if (state && state !== cleanName)
|
if (state && state !== cleanName)
|
||||||
cleanName += `, ${state}`
|
cleanName += `, ${state}`;
|
||||||
}
|
}
|
||||||
const query = `${location.lat},${location.lon}`
|
const query = `${location.lat},${location.lon}`;
|
||||||
searchResultsModel.append({
|
searchResultsModel.append({
|
||||||
"name": cleanName,
|
"name": cleanName,
|
||||||
"query": query
|
"query": query
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {}
|
||||||
|
});
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -107,7 +103,7 @@ Item {
|
|||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!locationInput.getActiveFocus() && !searchDropdown.hovered)
|
if (!locationInput.getActiveFocus() && !searchDropdown.hovered)
|
||||||
root.resetSearchState()
|
root.resetSearchState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,23 +128,23 @@ Item {
|
|||||||
keyNavigationBacktab: root.keyNavigationBacktab
|
keyNavigationBacktab: root.keyNavigationBacktab
|
||||||
onTextEdited: {
|
onTextEdited: {
|
||||||
if (root._internalChange)
|
if (root._internalChange)
|
||||||
return
|
return;
|
||||||
if (getActiveFocus()) {
|
if (getActiveFocus()) {
|
||||||
if (text.length > 2) {
|
if (text.length > 2) {
|
||||||
root.isLoading = true
|
root.isLoading = true;
|
||||||
locationSearchTimer.restart()
|
locationSearchTimer.restart();
|
||||||
} else {
|
} else {
|
||||||
root.resetSearchState()
|
root.resetSearchState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onFocusStateChanged: hasFocus => {
|
onFocusStateChanged: hasFocus => {
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
dropdownHideTimer.stop()
|
dropdownHideTimer.stop();
|
||||||
} else {
|
} else {
|
||||||
dropdownHideTimer.start()
|
dropdownHideTimer.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
@@ -187,13 +183,13 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
onEntered: {
|
onEntered: {
|
||||||
parent.hovered = true
|
parent.hovered = true;
|
||||||
dropdownHideTimer.stop()
|
dropdownHideTimer.stop();
|
||||||
}
|
}
|
||||||
onExited: {
|
onExited: {
|
||||||
parent.hovered = false
|
parent.hovered = false;
|
||||||
if (!locationInput.getActiveFocus())
|
if (!locationInput.getActiveFocus())
|
||||||
dropdownHideTimer.start()
|
dropdownHideTimer.start();
|
||||||
}
|
}
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
}
|
}
|
||||||
@@ -245,14 +241,14 @@ Item {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root._internalChange = true
|
root._internalChange = true;
|
||||||
const selectedName = model.name
|
const selectedName = model.name;
|
||||||
const selectedQuery = model.query
|
const selectedQuery = model.query;
|
||||||
locationInput.text = selectedName
|
locationInput.text = selectedName;
|
||||||
root.locationSelected(selectedName, selectedQuery)
|
root.locationSelected(selectedName, selectedQuery);
|
||||||
root.resetSearchState()
|
root.resetSearchState();
|
||||||
locationInput.setFocus(false)
|
locationInput.setFocus(false);
|
||||||
root._internalChange = false
|
root._internalChange = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user