1
0
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:
bbedward
2026-02-10 15:42:40 -05:00
parent 2c360dc3e8
commit c783ff3dcf
12 changed files with 319 additions and 139 deletions

View File

@@ -524,5 +524,6 @@ func getCommonCommands() []*cobra.Command {
chromaCmd, chromaCmd,
doctorCmd, doctorCmd,
configCmd, configCmd,
dlCmd,
} }
} }

View 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
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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
} }

View 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()
}

View File

@@ -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;

View File

@@ -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
}; };
} }

View File

@@ -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++) {

View File

@@ -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() {

View File

@@ -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
} }
} }

View File

@@ -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;
} }
} }
} }