1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-08-07 14:08:29 -04:00

Compare commits

...

7 Commits

Author SHA1 Message Date
dms-ci[bot] 069ddab041 bump VERSION to v1.5.3 2026-07-27 00:17:35 +00:00
bbedward 45cf6ecefd sysupdate: fix gnome-terminal title
(cherry picked from commit c367153bac)
2026-07-26 19:51:46 -04:00
bbedward 7058b00091 cups: fix sub/unsub race
port 1.5

(cherry picked from commit bab2078dfd)
2026-07-26 23:43:53 +00:00
bbedward 6ba4b79039 port: record hand-ported master equivalents
(cherry picked from commit e9bc0169f6)
(cherry picked from commit 9b7d3c64fe)
(cherry picked from commit ea03fb2788)
(cherry picked from commit 0815e48465)
(cherry picked from commit 3938e60ce4)
(cherry picked from commit 3c688cfbd3)
(cherry picked from commit e63d210358)
(cherry picked from commit fa094db127)
2026-07-26 19:32:23 -04:00
bbedward b8e2ce1da8 dock: use required properties in dock item delegate, fixes context injection on Qt 6.8 (#2926)
(cherry picked from commit fa629fbf99)
2026-07-26 19:15:44 -04:00
bbedward c58d55db7d bluetooth: add soft rfkill unblock, wait for adapter to become available
instead of forfeiting subscription

fixes #2922
fixes #1537

(cherry picked from commit 77a357109a)
2026-07-26 19:15:02 -04:00
bbedward e15db00714 cc: migrate some settings to cache
port 1.5

(cherry picked from commit e9bc0169f6)
2026-07-26 21:46:22 +00:00
27 changed files with 354 additions and 74 deletions
+11 -6
View File
@@ -43,10 +43,10 @@ func NewManager() (*Manager, error) {
broker := NewSubscriptionBroker(m.broadcastPairingPrompt) broker := NewSubscriptionBroker(m.broadcastPairingPrompt)
m.promptBroker = broker m.promptBroker = broker
adapter, err := m.findAdapter() adapter, err := findAdapter(conn)
if err != nil { if err != nil {
conn.Close() conn.Close()
return nil, fmt.Errorf("no bluetooth adapter found: %w", err) return nil, err
} }
m.adapterPath = adapter m.adapterPath = adapter
@@ -74,12 +74,12 @@ func NewManager() (*Manager, error) {
return m, nil return m, nil
} }
func (m *Manager) findAdapter() (dbus.ObjectPath, error) { func findAdapter(conn *dbus.Conn) (dbus.ObjectPath, error) {
obj := m.dbusConn.Object(bluezService, dbus.ObjectPath("/")) obj := conn.Object(bluezService, dbus.ObjectPath("/"))
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
if err := obj.Call(objectMgrIface+".GetManagedObjects", 0).Store(&objects); err != nil { if err := obj.Call(objectMgrIface+".GetManagedObjects", 0).Store(&objects); err != nil {
return "", err return "", fmt.Errorf("%w: %v", ErrNoAdapter, err)
} }
for path, interfaces := range objects { for path, interfaces := range objects {
@@ -89,7 +89,7 @@ func (m *Manager) findAdapter() (dbus.ObjectPath, error) {
} }
} }
return "", fmt.Errorf("no adapter found") return "", ErrNoAdapter
} }
func (m *Manager) initialize() error { func (m *Manager) initialize() error {
@@ -487,6 +487,11 @@ func (m *Manager) StopDiscovery() error {
} }
func (m *Manager) SetPowered(powered bool) error { func (m *Manager) SetPowered(powered bool) error {
if powered {
if err := rfkillUnblockBluetooth(); err != nil {
log.Debugf("[BluezManager] rfkill unblock failed: %v", err)
}
}
obj := m.dbusConn.Object(bluezService, m.adapterPath) obj := m.dbusConn.Object(bluezService, m.adapterPath)
return obj.Call(propertiesIface+".Set", 0, adapter1Iface, "Powered", dbus.MakeVariant(powered)).Err return obj.Call(propertiesIface+".Set", 0, adapter1Iface, "Powered", dbus.MakeVariant(powered)).Err
} }
@@ -0,0 +1,5 @@
package bluez
func rfkillUnblockBluetooth() error {
return nil
}
@@ -0,0 +1,19 @@
package bluez
import "os"
// linux/rfkill.h: struct rfkill_event { __u32 idx; __u8 type; __u8 op; __u8 soft; __u8 hard; },
// RFKILL_TYPE_BLUETOOTH=2, RFKILL_OP_CHANGE_ALL=3
func rfkillUnblockBluetooth() error {
f, err := os.OpenFile("/dev/rfkill", os.O_WRONLY, 0)
if err != nil {
return err
}
defer f.Close()
var event [8]byte
event[4] = 2
event[5] = 3
_, err = f.Write(event[:])
return err
}
+46
View File
@@ -0,0 +1,46 @@
package bluez
import (
"errors"
"fmt"
"github.com/godbus/dbus/v5"
)
var ErrNoAdapter = errors.New("no bluetooth adapter found")
func WaitForAdapter() error {
conn, err := dbus.ConnectSystemBus()
if err != nil {
return err
}
defer conn.Close()
if err := conn.AddMatchSignal(
dbus.WithMatchInterface(objectMgrIface),
dbus.WithMatchMember("InterfacesAdded"),
); err != nil {
return err
}
signals := make(chan *dbus.Signal, 64)
conn.Signal(signals)
if _, err := findAdapter(conn); err == nil {
return nil
}
for sig := range signals {
if sig == nil || sig.Name != objectMgrIface+".InterfacesAdded" || len(sig.Body) < 2 {
continue
}
ifaces, ok := sig.Body[1].(map[string]map[string]dbus.Variant)
if !ok {
continue
}
if _, ok := ifaces[adapter1Iface]; ok {
return nil
}
}
return fmt.Errorf("dbus signal stream closed")
}
+23 -10
View File
@@ -229,6 +229,9 @@ func (m *Manager) snapshotState() CUPSState {
func (m *Manager) Subscribe(id string) chan CUPSState { func (m *Manager) Subscribe(id string) chan CUPSState {
ch := make(chan CUPSState, 64) ch := make(chan CUPSState, 64)
m.subLifecycleMu.Lock()
defer m.subLifecycleMu.Unlock()
wasEmpty := true wasEmpty := true
m.subscribers.Range(func(key string, ch chan CUPSState) bool { m.subscribers.Range(func(key string, ch chan CUPSState) bool {
wasEmpty = false wasEmpty = false
@@ -237,19 +240,25 @@ func (m *Manager) Subscribe(id string) chan CUPSState {
m.subscribers.Store(id, ch) m.subscribers.Store(id, ch)
if wasEmpty && m.subscription != nil { if !wasEmpty || m.subscription == nil {
if err := m.subscription.Start(); err != nil { return ch
log.Warnf("[CUPS] Failed to start subscription manager: %v", err)
} else {
m.eventWG.Add(1)
go m.eventHandler()
}
} }
if err := m.subscription.Start(); err != nil {
log.Warnf("[CUPS] Failed to start subscription manager: %v", err)
return ch
}
m.eventWG.Add(1)
go m.eventHandler()
return ch return ch
} }
func (m *Manager) Unsubscribe(id string) { func (m *Manager) Unsubscribe(id string) {
m.subLifecycleMu.Lock()
defer m.subLifecycleMu.Unlock()
if val, ok := m.subscribers.LoadAndDelete(id); ok { if val, ok := m.subscribers.LoadAndDelete(id); ok {
close(val) close(val)
} }
@@ -260,18 +269,22 @@ func (m *Manager) Unsubscribe(id string) {
return false return false
}) })
if isEmpty && m.subscription != nil { if !isEmpty || m.subscription == nil {
m.subscription.Stop() return
m.eventWG.Wait()
} }
m.subscription.Stop()
m.eventWG.Wait()
} }
func (m *Manager) Close() { func (m *Manager) Close() {
close(m.stopChan) close(m.stopChan)
m.subLifecycleMu.Lock()
if m.subscription != nil { if m.subscription != nil {
m.subscription.Stop() m.subscription.Stop()
} }
m.subLifecycleMu.Unlock()
m.eventWG.Wait() m.eventWG.Wait()
m.notifierWg.Wait() m.notifierWg.Wait()
+64
View File
@@ -1,6 +1,9 @@
package cups package cups
import ( import (
"errors"
"fmt"
"sync"
"testing" "testing"
mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups" mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups"
@@ -75,6 +78,67 @@ func TestManager_Subscribe(t *testing.T) {
assert.Equal(t, 0, count) assert.Equal(t, 0, count)
} }
// mirrors the real managers: eventChan guarded by mu, conn/running deliberately
// unsynchronized so overlapping Start/Stop trips the race detector
type stubSubscription struct {
mu sync.Mutex
events chan SubscriptionEvent
conn *int
running bool
}
func (s *stubSubscription) Start() error {
if s.running {
return errors.New("already running")
}
s.running = true
s.mu.Lock()
s.events = make(chan SubscriptionEvent)
s.mu.Unlock()
v := 0
s.conn = &v
*s.conn++
return nil
}
func (s *stubSubscription) Stop() {
if !s.running {
return
}
s.running = false
s.conn = nil
s.mu.Lock()
close(s.events)
s.mu.Unlock()
}
func (s *stubSubscription) Events() <-chan SubscriptionEvent {
s.mu.Lock()
defer s.mu.Unlock()
return s.events
}
func TestManager_SubscribeUnsubscribeRace(t *testing.T) {
m := NewTestManager(mocks_cups.NewMockCUPSClientInterface(t), nil)
m.subscription = &stubSubscription{}
var wg sync.WaitGroup
for i := range 8 {
wg.Go(func() {
id := fmt.Sprintf("client-%d", i)
for range 50 {
m.Subscribe(id)
m.Unsubscribe(id)
}
})
}
wg.Wait()
}
func TestManager_Close(t *testing.T) { func TestManager_Close(t *testing.T) {
mockClient := mocks_cups.NewMockCUPSClientInterface(t) mockClient := mocks_cups.NewMockCUPSClientInterface(t)
+1
View File
@@ -79,6 +79,7 @@ type Manager struct {
client CUPSClientInterface client CUPSClientInterface
pkHelper PkHelper pkHelper PkHelper
subscription SubscriptionManagerInterface subscription SubscriptionManagerInterface
subLifecycleMu sync.Mutex
stateMutex sync.RWMutex stateMutex sync.RWMutex
subscribers syncmap.Map[string, chan CUPSState] subscribers syncmap.Map[string, chan CUPSState]
stopChan chan struct{} stopChan chan struct{}
+14 -3
View File
@@ -1731,10 +1731,21 @@ func Start(printDocs bool) error {
}() }()
go func() { go func() {
if err := InitializeBluezManager(); err != nil { for {
err := InitializeBluezManager()
if err == nil {
notifyCapabilityChange()
return
}
log.Warnf("Bluez manager unavailable: %v", err) log.Warnf("Bluez manager unavailable: %v", err)
} else { if !errors.Is(err, bluez.ErrNoAdapter) {
notifyCapabilityChange() return
}
if err := bluez.WaitForAdapter(); err != nil {
log.Warnf("Bluetooth adapter watch failed: %v", err)
return
}
log.Info("Bluetooth adapter appeared, initializing bluez manager")
} }
}() }()
+2 -1
View File
@@ -143,7 +143,8 @@ func wrapInTerminal(term, title, shellCmd string, extraArgs []string) []string {
case "konsole": case "konsole":
argv = []string{term, "-p", "tabtitle=" + title} argv = []string{term, "-p", "tabtitle=" + title}
case "gnome-terminal": case "gnome-terminal":
argv = []string{term, "--title=" + title} // --wait: the factory process otherwise returns immediately
argv = []string{term, "--wait", "--title=" + title}
execFlag = "--" execFlag = "--"
default: default:
argv = []string{term} argv = []string{term}
+14 -9
View File
@@ -412,25 +412,30 @@ func (m *Manager) runCustomUpgrade(ctx context.Context, opts UpgradeOptions) {
onLine := func(line string) { m.appendLog(line) } onLine := func(line string) { m.appendLog(line) }
argv := wrapInTerminal(term, "DMS — System Update (custom)", opts.CustomCommand, opts.TerminalArgs) argv := wrapInTerminal(term, "DMS — System Update (custom)", opts.CustomCommand, opts.TerminalArgs)
if err := Run(ctx, argv, RunOptions{OnLine: onLine}); err != nil { if err := Run(ctx, argv, RunOptions{OnLine: onLine}); err != nil {
code := ErrCodeBackendFailed
switch { switch {
case errors.Is(ctx.Err(), context.DeadlineExceeded): case errors.Is(ctx.Err(), context.DeadlineExceeded):
code = ErrCodeTimeout m.failCustomUpgrade(ErrCodeTimeout, err)
return
case errors.Is(ctx.Err(), context.Canceled): case errors.Is(ctx.Err(), context.Canceled):
code = ErrCodeCancelled m.failCustomUpgrade(ErrCodeCancelled, err)
return
} }
m.mu.Lock() // exit status reflects the trailing `read`, not the update command
m.state.Phase = PhaseError m.appendLog(fmt.Sprintf("Terminal exited early: %v", err))
m.state.Error = &ErrorInfo{Code: code, Message: err.Error()}
m.mu.Unlock()
m.markDirty()
return
} }
m.finishSuccessfulUpgrade(false) m.finishSuccessfulUpgrade(false)
m.runRefresh(context.Background(), false) m.runRefresh(context.Background(), false)
} }
func (m *Manager) failCustomUpgrade(code ErrorCode, err error) {
m.mu.Lock()
m.state.Phase = PhaseError
m.state.Error = &ErrorInfo{Code: code, Message: err.Error()}
m.mu.Unlock()
m.markDirty()
}
func (m *Manager) finishSuccessfulUpgrade(clearPackages bool) { func (m *Manager) finishSuccessfulUpgrade(clearPackages bool) {
m.appendLog("Upgrade complete.") m.appendLog("Upgrade complete.")
@@ -233,3 +233,27 @@ func TestUpgradeBackendsFiltersFlatpakOnly(t *testing.T) {
t.Fatalf("upgradeBackends(mixed) = %#v, want dnf5 then flatpak", got) t.Fatalf("upgradeBackends(mixed) = %#v, want dnf5 then flatpak", got)
} }
} }
func TestWrapInTerminal(t *testing.T) {
tests := []struct {
term string
wantPrefix []string
}{
{"kitty", []string{"kitty", "--class", "com.danklinux.dms", "-T", "Title"}},
{"gnome-terminal", []string{"gnome-terminal", "--wait", "--title=Title"}},
{"foot", []string{"foot", "--app-id=com.danklinux.dms", "--title=Title"}},
}
for _, tt := range tests {
got := wrapInTerminal(tt.term, "Title", "echo hi", nil)
if len(got) < len(tt.wantPrefix) || !reflect.DeepEqual(got[:len(tt.wantPrefix)], tt.wantPrefix) {
t.Errorf("wrapInTerminal(%q) = %#v, want prefix %#v", tt.term, got, tt.wantPrefix)
}
tail := got[len(got)-3:]
if tail[0] != "sh" || tail[1] != "-c" {
t.Errorf("wrapInTerminal(%q) tail = %#v, want [sh -c <cmd>]", tt.term, tail)
}
if !strings.Contains(tail[2], "echo hi") {
t.Errorf("wrapInTerminal(%q) command %q does not contain shell command", tt.term, tail[2])
}
}
}
+64 -6
View File
@@ -11,7 +11,7 @@ Singleton {
id: root id: root
readonly property var log: Log.scoped("CacheData") readonly property var log: Log.scoped("CacheData")
readonly property int cacheConfigVersion: 1 readonly property int cacheConfigVersion: 2
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true" readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
@@ -19,10 +19,21 @@ Singleton {
readonly property string _stateDir: Paths.strip(_stateUrl) readonly property string _stateDir: Paths.strip(_stateUrl)
property bool _loading: false property bool _loading: false
property bool _hasLoaded: false
property int _loadedCacheVersion: 0
readonly property var _pinKeys: ["brightnessDevicePins", "wifiNetworkPins", "bluetoothDevicePins", "audioInputDevicePins", "audioOutputDevicePins"]
readonly property var _dataKeys: ["wallpaperLastPath", "profileLastPath", "fileBrowserSettings"].concat(_pinKeys)
property string wallpaperLastPath: "" property string wallpaperLastPath: ""
property string profileLastPath: "" property string profileLastPath: ""
property var brightnessDevicePins: ({})
property var wifiNetworkPins: ({})
property var bluetoothDevicePins: ({})
property var audioInputDevicePins: ({})
property var audioOutputDevicePins: ({})
property var fileBrowserSettings: ({ property var fileBrowserSettings: ({
"wallpaper": { "wallpaper": {
"lastPath": "", "lastPath": "",
@@ -82,8 +93,46 @@ Singleton {
function loadCache() { function loadCache() {
_loading = true; _loading = true;
parseCache(cacheFile.text()); try {
_loading = false; parseCache(cacheFile.text());
} finally {
_loading = false;
_hasLoaded = true;
}
}
function set(key, value) {
if (_dataKeys.indexOf(key) < 0) {
log.warn("Unknown cache key:", key);
return;
}
root[key] = value;
saveCache();
}
function migratePins(pins) {
if (!pins)
return;
if (!_hasLoaded)
loadCache();
if (_loadedCacheVersion >= cacheConfigVersion)
return;
let migrated = false;
for (const key of _pinKeys) {
const legacy = pins[key];
if (!legacy || Object.keys(legacy).length === 0)
continue;
if (Object.keys(root[key] || {}).length > 0)
continue;
root[key] = legacy;
migrated = true;
}
if (!migrated)
return;
log.info("Migrated device pins from settings.json");
saveCache();
} }
function parseCache(content) { function parseCache(content) {
@@ -91,6 +140,7 @@ Singleton {
try { try {
if (content && content.trim()) { if (content && content.trim()) {
const cache = JSON.parse(content); const cache = JSON.parse(content);
_loadedCacheVersion = cache.configVersion || 0;
wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : ""; wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : "";
profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : ""; profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : "";
@@ -126,6 +176,10 @@ Singleton {
}; };
} }
for (const key of _pinKeys) {
root[key] = cache[key] !== undefined ? cache[key] : {};
}
if (cache.configVersion === undefined) { if (cache.configVersion === undefined) {
migrateFromUndefinedToV1(cache); migrateFromUndefinedToV1(cache);
cleanupUnusedKeys(); cleanupUnusedKeys();
@@ -142,12 +196,16 @@ Singleton {
function saveCache() { function saveCache() {
if (_loading) if (_loading)
return; return;
cacheFile.setText(JSON.stringify({ const data = {
"wallpaperLastPath": wallpaperLastPath, "wallpaperLastPath": wallpaperLastPath,
"profileLastPath": profileLastPath, "profileLastPath": profileLastPath,
"fileBrowserSettings": fileBrowserSettings, "fileBrowserSettings": fileBrowserSettings,
"configVersion": cacheConfigVersion "configVersion": cacheConfigVersion
}, null, 2)); };
for (const key of _pinKeys) {
data[key] = root[key];
}
cacheFile.setText(JSON.stringify(data, null, 2));
} }
function migrateFromUndefinedToV1(cache) { function migrateFromUndefinedToV1(cache) {
@@ -155,7 +213,7 @@ Singleton {
} }
function cleanupUnusedKeys() { function cleanupUnusedKeys() {
const validKeys = ["wallpaperLastPath", "profileLastPath", "fileBrowserSettings", "configVersion"]; const validKeys = _dataKeys.concat(["configVersion"]);
try { try {
const content = cacheFile.text(); const content = cacheFile.text();
+4 -6
View File
@@ -15,7 +15,7 @@ Singleton {
id: root id: root
readonly property var log: Log.scoped("SettingsData") readonly property var log: Log.scoped("SettingsData")
readonly property int settingsConfigVersion: 12 readonly property int settingsConfigVersion: 13
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true" readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
@@ -773,11 +773,6 @@ Singleton {
property bool fadeToDpmsEnabled: true property bool fadeToDpmsEnabled: true
property int fadeToDpmsGracePeriod: 5 property int fadeToDpmsGracePeriod: 5
property string launchPrefix: "" property string launchPrefix: ""
property var brightnessDevicePins: ({})
property var wifiNetworkPins: ({})
property var bluetoothDevicePins: ({})
property var audioInputDevicePins: ({})
property var audioOutputDevicePins: ({})
property bool gtkThemingEnabled: false property bool gtkThemingEnabled: false
property bool qtThemingEnabled: false property bool qtThemingEnabled: false
@@ -1741,6 +1736,7 @@ Singleton {
let obj = (txt && txt.trim()) ? JSON.parse(txt) : null; let obj = (txt && txt.trim()) ? JSON.parse(txt) : null;
const oldVersion = obj?.configVersion ?? 0; const oldVersion = obj?.configVersion ?? 0;
const legacyPins = oldVersion < 13 ? Store.extractPins(obj) : null;
if (oldVersion < settingsConfigVersion) { if (oldVersion < settingsConfigVersion) {
const migrated = Store.migrateToVersion(obj, settingsConfigVersion); const migrated = Store.migrateToVersion(obj, settingsConfigVersion);
if (migrated) { if (migrated) {
@@ -1748,6 +1744,8 @@ Singleton {
obj = migrated; obj = migrated;
} }
} }
if (legacyPins)
Qt.callLater(() => CacheData.migratePins(legacyPins));
if (obj?.lockScreenActiveMonitor !== undefined) { if (obj?.lockScreenActiveMonitor !== undefined) {
var oldVal = obj.lockScreenActiveMonitor; var oldVal = obj.lockScreenActiveMonitor;
@@ -354,11 +354,6 @@ var SPEC = {
fadeToDpmsEnabled: { def: true }, fadeToDpmsEnabled: { def: true },
fadeToDpmsGracePeriod: { def: 5 }, fadeToDpmsGracePeriod: { def: 5 },
launchPrefix: { def: "" }, launchPrefix: { def: "" },
brightnessDevicePins: { def: {} },
wifiNetworkPins: { def: {} },
bluetoothDevicePins: { def: {} },
audioInputDevicePins: { def: {} },
audioOutputDevicePins: { def: {} },
gtkThemingEnabled: { def: false, onChange: "regenSystemThemes" }, gtkThemingEnabled: { def: false, onChange: "regenSystemThemes" },
qtThemingEnabled: { def: false, onChange: "regenSystemThemes" }, qtThemingEnabled: { def: false, onChange: "regenSystemThemes" },
@@ -2,6 +2,21 @@
.import "./SettingsSpec.js" as SpecModule .import "./SettingsSpec.js" as SpecModule
var PIN_KEYS = ["brightnessDevicePins", "wifiNetworkPins", "bluetoothDevicePins", "audioInputDevicePins", "audioOutputDevicePins"];
function extractPins(obj) {
if (!obj) return null;
var pins = null;
for (var i = 0; i < PIN_KEYS.length; i++) {
var value = obj[PIN_KEYS[i]];
if (!value || Object.keys(value).length === 0) continue;
if (!pins) pins = {};
pins[PIN_KEYS[i]] = value;
}
return pins;
}
function parse(root, jsonObj) { function parse(root, jsonObj) {
var SPEC = SpecModule.SPEC; var SPEC = SpecModule.SPEC;
@@ -263,6 +278,17 @@ function migrateToVersion(obj, targetVersion) {
settings.configVersion = 12; settings.configVersion = 12;
} }
if (currentVersion < 13) {
console.info("Migrating settings from version", currentVersion, "to version 13");
console.info("Moving device and network pins to cache.json");
for (var p = 0; p < PIN_KEYS.length; p++) {
delete settings[PIN_KEYS[p]];
}
settings.configVersion = 13;
}
return settings; return settings;
} }
@@ -159,7 +159,7 @@ Rectangle {
} }
function getPinnedInputs() { function getPinnedInputs() {
const pins = SettingsData.audioInputDevicePins || {}; const pins = CacheData.audioInputDevicePins || {};
return normalizePinList(pins["preferredInput"]); return normalizePinList(pins["preferredInput"]);
} }
@@ -315,7 +315,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y) onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {})); const pins = JSON.parse(JSON.stringify(CacheData.audioInputDevicePins || {}));
let pinnedList = audioContent.normalizePinList(pins["preferredInput"]); let pinnedList = audioContent.normalizePinList(pins["preferredInput"]);
const pinIndex = pinnedList.indexOf(modelData.name); const pinIndex = pinnedList.indexOf(modelData.name);
@@ -332,7 +332,7 @@ Rectangle {
else else
delete pins["preferredInput"]; delete pins["preferredInput"];
SettingsData.set("audioInputDevicePins", pins); CacheData.set("audioInputDevicePins", pins);
} }
} }
} }
@@ -169,7 +169,7 @@ Rectangle {
} }
function getPinnedOutputs() { function getPinnedOutputs() {
const pins = SettingsData.audioOutputDevicePins || {}; const pins = CacheData.audioOutputDevicePins || {};
return normalizePinList(pins["preferredOutput"]); return normalizePinList(pins["preferredOutput"]);
} }
@@ -324,7 +324,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y) onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {})); const pins = JSON.parse(JSON.stringify(CacheData.audioOutputDevicePins || {}));
let pinnedList = audioContent.normalizePinList(pins["preferredOutput"]); let pinnedList = audioContent.normalizePinList(pins["preferredOutput"]);
const pinIndex = pinnedList.indexOf(modelData.name); const pinIndex = pinnedList.indexOf(modelData.name);
@@ -341,7 +341,7 @@ Rectangle {
else else
delete pins["preferredOutput"]; delete pins["preferredOutput"];
SettingsData.set("audioOutputDevicePins", pins); CacheData.set("audioOutputDevicePins", pins);
} }
} }
} }
@@ -112,7 +112,7 @@ Rectangle {
} }
function getPinnedDevices() { function getPinnedDevices() {
const pins = SettingsData.bluetoothDevicePins || {}; const pins = CacheData.bluetoothDevicePins || {};
return normalizePinList(pins["preferredDevice"]); return normalizePinList(pins["preferredDevice"]);
} }
@@ -400,7 +400,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.bluetoothDevicePins || {})); const pins = JSON.parse(JSON.stringify(CacheData.bluetoothDevicePins || {}));
let pinnedList = root.normalizePinList(pins["preferredDevice"]); let pinnedList = root.normalizePinList(pins["preferredDevice"]);
const pinIndex = pinnedList.indexOf(pairedDelegate.modelData.address); const pinIndex = pinnedList.indexOf(pairedDelegate.modelData.address);
@@ -418,7 +418,7 @@ Rectangle {
delete pins["preferredDevice"]; delete pins["preferredDevice"];
} }
SettingsData.set("bluetoothDevicePins", pins); CacheData.set("bluetoothDevicePins", pins);
} }
} }
} }
@@ -37,7 +37,7 @@ Rectangle {
const pinKey = getScreenPinKey(); const pinKey = getScreenPinKey();
if (pinKey.length > 0) { if (pinKey.length > 0) {
const pins = SettingsData.brightnessDevicePins || {}; const pins = CacheData.brightnessDevicePins || {};
const pinnedDevice = pins[pinKey]; const pinnedDevice = pins[pinKey];
if (pinnedDevice && pinnedDevice.length > 0) { if (pinnedDevice && pinnedDevice.length > 0) {
const found = devices.find(d => d.name === pinnedDevice); const found = devices.find(d => d.name === pinnedDevice);
@@ -85,12 +85,12 @@ Rectangle {
} }
const pinKey = getScreenPinKey(); const pinKey = getScreenPinKey();
if (pinKey.length > 0) { if (pinKey.length > 0) {
const pins = SettingsData.brightnessDevicePins || {}; const pins = CacheData.brightnessDevicePins || {};
const existing = pins[pinKey]; const existing = pins[pinKey];
if (existing && existing !== deviceName) { if (existing && existing !== deviceName) {
const next = JSON.parse(JSON.stringify(pins)); const next = JSON.parse(JSON.stringify(pins));
delete next[pinKey]; delete next[pinKey];
SettingsData.set("brightnessDevicePins", next); CacheData.set("brightnessDevicePins", next);
} }
} }
root.currentDeviceName = deviceName; root.currentDeviceName = deviceName;
@@ -106,7 +106,7 @@ Rectangle {
const pinKey = getScreenPinKey(); const pinKey = getScreenPinKey();
if (!pinKey || !deviceName) if (!pinKey || !deviceName)
return false; return false;
const pins = SettingsData.brightnessDevicePins || {}; const pins = CacheData.brightnessDevicePins || {};
return pins[pinKey] === deviceName; return pins[pinKey] === deviceName;
} }
@@ -114,13 +114,13 @@ Rectangle {
const pinKey = getScreenPinKey(); const pinKey = getScreenPinKey();
if (!pinKey || !deviceName) if (!pinKey || !deviceName)
return; return;
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {})); const pins = JSON.parse(JSON.stringify(CacheData.brightnessDevicePins || {}));
if (pins[pinKey] === deviceName) { if (pins[pinKey] === deviceName) {
delete pins[pinKey]; delete pins[pinKey];
} else { } else {
pins[pinKey] = deviceName; pins[pinKey] = deviceName;
} }
SettingsData.set("brightnessDevicePins", pins); CacheData.set("brightnessDevicePins", pins);
} }
implicitHeight: { implicitHeight: {
@@ -269,7 +269,7 @@ Rectangle {
readonly property bool selected: !!(modelData && modelData.name === root.currentDeviceName) readonly property bool selected: !!(modelData && modelData.name === root.currentDeviceName)
readonly property bool devicePinnedHere: { readonly property bool devicePinnedHere: {
SettingsData.brightnessDevicePins; CacheData.brightnessDevicePins;
return root.isDevicePinnedToScreen(modelData ? modelData.name : ""); return root.isDevicePinnedToScreen(modelData ? modelData.name : "");
} }
@@ -49,7 +49,7 @@ Rectangle {
} }
function getPinnedNetworks() { function getPinnedNetworks() {
const pins = SettingsData.wifiNetworkPins || {}; const pins = CacheData.wifiNetworkPins || {};
return normalizePinList(pins["preferredWifi"]); return normalizePinList(pins["preferredWifi"]);
} }
@@ -710,7 +710,7 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y) onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
onClicked: { onClicked: {
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {})); const pins = JSON.parse(JSON.stringify(CacheData.wifiNetworkPins || {}));
let pinnedList = root.normalizePinList(pins["preferredWifi"]); let pinnedList = root.normalizePinList(pins["preferredWifi"]);
const pinIndex = pinnedList.indexOf(modelData.ssid); const pinIndex = pinnedList.indexOf(modelData.ssid);
@@ -727,7 +727,7 @@ Rectangle {
else else
delete pins["preferredWifi"]; delete pins["preferredWifi"];
SettingsData.set("wifiNetworkPins", pins); CacheData.set("wifiNetworkPins", pins);
} }
} }
} }
@@ -34,7 +34,7 @@ Row {
if (screenName && screenName.length > 0) { if (screenName && screenName.length > 0) {
const screen = Quickshell.screens.find(s => s.name === screenName); const screen = Quickshell.screens.find(s => s.name === screenName);
const pinKey = screen ? SettingsData.getScreenDisplayName(screen) : screenName; const pinKey = screen ? SettingsData.getScreenDisplayName(screen) : screenName;
const pins = SettingsData.brightnessDevicePins || {}; const pins = CacheData.brightnessDevicePins || {};
const pinnedDevice = pins[pinKey]; const pinnedDevice = pins[pinKey];
if (pinnedDevice && pinnedDevice.length > 0) { if (pinnedDevice && pinnedDevice.length > 0) {
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice); const found = DisplayService.devices.find(dev => dev.name === pinnedDevice);
@@ -203,7 +203,7 @@ BasePill {
const pinKey = getScreenPinKey(); const pinKey = getScreenPinKey();
if (!pinKey) if (!pinKey)
return ""; return "";
const pins = SettingsData.brightnessDevicePins || {}; const pins = CacheData.brightnessDevicePins || {};
return pins[pinKey] || ""; return pins[pinKey] || "";
} }
+4 -2
View File
@@ -479,6 +479,8 @@ Item {
delegate: Item { delegate: Item {
id: delegateItem id: delegateItem
required property var modelData
required property int index
property var dockButton: { property var dockButton: {
switch (itemData.type) { switch (itemData.type) {
@@ -599,7 +601,7 @@ Item {
height: delegateItem.height height: delegateItem.height
actualIconSize: root.iconSize actualIconSize: root.iconSize
dockApps: root dockApps: root
index: model.index index: delegateItem.index
} }
DockTrashButton { DockTrashButton {
@@ -624,7 +626,7 @@ Item {
appData: itemData appData: itemData
contextMenu: root.contextMenu contextMenu: root.contextMenu
dockApps: root dockApps: root
index: model.index index: delegateItem.index
parentDockScreen: root.dockScreen parentDockScreen: root.dockScreen
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped" showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
windowTitle: { windowTitle: {
@@ -55,12 +55,12 @@ Item {
} }
function getPinnedWifiNetworks() { function getPinnedWifiNetworks() {
const pins = SettingsData.wifiNetworkPins || {}; const pins = CacheData.wifiNetworkPins || {};
return normalizePinList(pins["preferredWifi"]); return normalizePinList(pins["preferredWifi"]);
} }
function toggleWifiPin(ssid) { function toggleWifiPin(ssid) {
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {})); const pins = JSON.parse(JSON.stringify(CacheData.wifiNetworkPins || {}));
let pinnedList = normalizePinList(pins["preferredWifi"]); let pinnedList = normalizePinList(pins["preferredWifi"]);
const pinIndex = pinnedList.indexOf(ssid); const pinIndex = pinnedList.indexOf(ssid);
@@ -77,7 +77,7 @@ Item {
else else
delete pins["preferredWifi"]; delete pins["preferredWifi"];
SettingsData.set("wifiNetworkPins", pins); CacheData.set("wifiNetworkPins", pins);
} }
property var forgetNetworkConfirm: ConfirmModal {} property var forgetNetworkConfirm: ConfirmModal {}
+7
View File
@@ -335,6 +335,7 @@ Singleton {
const data = response.result.data; const data = response.result.data;
if (service === "server") { if (service === "server") {
const prevCapabilities = capabilities;
apiVersion = data.apiVersion || 0; apiVersion = data.apiVersion || 0;
cliVersion = data.cliVersion || ""; cliVersion = data.cliVersion || "";
capabilities = data.capabilities || []; capabilities = data.capabilities || [];
@@ -346,6 +347,12 @@ Singleton {
} }
capabilitiesReceived(); capabilitiesReceived();
const capabilitiesChanged = prevCapabilities.length !== capabilities.length || capabilities.some(c => !prevCapabilities.includes(c));
if (prevCapabilities.length > 0 && capabilitiesChanged) {
log.info("Capabilities changed, resubscribing");
subscribe(activeSubscriptions);
}
} else if (service === "network") { } else if (service === "network") {
networkStateUpdate(data); networkStateUpdate(data);
} else if (service === "network.credentials") { } else if (service === "network.credentials") {
+1 -1
View File
@@ -386,7 +386,7 @@ Singleton {
if (!focusedScreen) if (!focusedScreen)
return ""; return "";
const pins = SettingsData.brightnessDevicePins || {}; const pins = CacheData.brightnessDevicePins || {};
const screenKey = SettingsData.getScreenDisplayName(focusedScreen); const screenKey = SettingsData.getScreenDisplayName(focusedScreen);
if (!screenKey) if (!screenKey)
return ""; return "";
+1 -1
View File
@@ -1 +1 @@
v1.5.2 v1.5.3