mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-01 19:18:28 -04:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4fb6a17e1b | |||
| e771e3b675 | |||
| b1b7aa7aa2 | |||
| c128793239 | |||
| 069ddab041 | |||
| 45cf6ecefd | |||
| 7058b00091 | |||
| 6ba4b79039 | |||
| b8e2ce1da8 | |||
| c58d55db7d |
@@ -43,10 +43,10 @@ func NewManager() (*Manager, error) {
|
||||
broker := NewSubscriptionBroker(m.broadcastPairingPrompt)
|
||||
m.promptBroker = broker
|
||||
|
||||
adapter, err := m.findAdapter()
|
||||
adapter, err := findAdapter(conn)
|
||||
if err != nil {
|
||||
conn.Close()
|
||||
return nil, fmt.Errorf("no bluetooth adapter found: %w", err)
|
||||
return nil, err
|
||||
}
|
||||
m.adapterPath = adapter
|
||||
|
||||
@@ -74,12 +74,12 @@ func NewManager() (*Manager, error) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m *Manager) findAdapter() (dbus.ObjectPath, error) {
|
||||
obj := m.dbusConn.Object(bluezService, dbus.ObjectPath("/"))
|
||||
func findAdapter(conn *dbus.Conn) (dbus.ObjectPath, error) {
|
||||
obj := conn.Object(bluezService, dbus.ObjectPath("/"))
|
||||
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||
|
||||
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 {
|
||||
@@ -89,7 +89,7 @@ func (m *Manager) findAdapter() (dbus.ObjectPath, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no adapter found")
|
||||
return "", ErrNoAdapter
|
||||
}
|
||||
|
||||
func (m *Manager) initialize() error {
|
||||
@@ -487,6 +487,11 @@ func (m *Manager) StopDiscovery() 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)
|
||||
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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
@@ -229,6 +229,9 @@ func (m *Manager) snapshotState() CUPSState {
|
||||
func (m *Manager) Subscribe(id string) chan CUPSState {
|
||||
ch := make(chan CUPSState, 64)
|
||||
|
||||
m.subLifecycleMu.Lock()
|
||||
defer m.subLifecycleMu.Unlock()
|
||||
|
||||
wasEmpty := true
|
||||
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
||||
wasEmpty = false
|
||||
@@ -237,19 +240,25 @@ func (m *Manager) Subscribe(id string) chan CUPSState {
|
||||
|
||||
m.subscribers.Store(id, ch)
|
||||
|
||||
if wasEmpty && m.subscription != nil {
|
||||
if err := m.subscription.Start(); err != nil {
|
||||
log.Warnf("[CUPS] Failed to start subscription manager: %v", err)
|
||||
} else {
|
||||
m.eventWG.Add(1)
|
||||
go m.eventHandler()
|
||||
}
|
||||
if !wasEmpty || m.subscription == nil {
|
||||
return ch
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (m *Manager) Unsubscribe(id string) {
|
||||
m.subLifecycleMu.Lock()
|
||||
defer m.subLifecycleMu.Unlock()
|
||||
|
||||
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||
close(val)
|
||||
}
|
||||
@@ -260,18 +269,22 @@ func (m *Manager) Unsubscribe(id string) {
|
||||
return false
|
||||
})
|
||||
|
||||
if isEmpty && m.subscription != nil {
|
||||
m.subscription.Stop()
|
||||
m.eventWG.Wait()
|
||||
if !isEmpty || m.subscription == nil {
|
||||
return
|
||||
}
|
||||
|
||||
m.subscription.Stop()
|
||||
m.eventWG.Wait()
|
||||
}
|
||||
|
||||
func (m *Manager) Close() {
|
||||
close(m.stopChan)
|
||||
|
||||
m.subLifecycleMu.Lock()
|
||||
if m.subscription != nil {
|
||||
m.subscription.Stop()
|
||||
}
|
||||
m.subLifecycleMu.Unlock()
|
||||
|
||||
m.eventWG.Wait()
|
||||
m.notifierWg.Wait()
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package cups
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ type Manager struct {
|
||||
client CUPSClientInterface
|
||||
pkHelper PkHelper
|
||||
subscription SubscriptionManagerInterface
|
||||
subLifecycleMu sync.Mutex
|
||||
stateMutex sync.RWMutex
|
||||
subscribers syncmap.Map[string, chan CUPSState]
|
||||
stopChan chan struct{}
|
||||
|
||||
@@ -1731,10 +1731,21 @@ func Start(printDocs bool) error {
|
||||
}()
|
||||
|
||||
go func() {
|
||||
if err := InitializeBluezManager(); err != nil {
|
||||
for {
|
||||
err := InitializeBluezManager()
|
||||
if err == nil {
|
||||
notifyCapabilityChange()
|
||||
return
|
||||
}
|
||||
log.Warnf("Bluez manager unavailable: %v", err)
|
||||
} else {
|
||||
notifyCapabilityChange()
|
||||
if !errors.Is(err, bluez.ErrNoAdapter) {
|
||||
return
|
||||
}
|
||||
if err := bluez.WaitForAdapter(); err != nil {
|
||||
log.Warnf("Bluetooth adapter watch failed: %v", err)
|
||||
return
|
||||
}
|
||||
log.Info("Bluetooth adapter appeared, initializing bluez manager")
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -143,7 +143,8 @@ func wrapInTerminal(term, title, shellCmd string, extraArgs []string) []string {
|
||||
case "konsole":
|
||||
argv = []string{term, "-p", "tabtitle=" + title}
|
||||
case "gnome-terminal":
|
||||
argv = []string{term, "--title=" + title}
|
||||
// --wait: the factory process otherwise returns immediately
|
||||
argv = []string{term, "--wait", "--title=" + title}
|
||||
execFlag = "--"
|
||||
default:
|
||||
argv = []string{term}
|
||||
|
||||
@@ -412,25 +412,30 @@ func (m *Manager) runCustomUpgrade(ctx context.Context, opts UpgradeOptions) {
|
||||
onLine := func(line string) { m.appendLog(line) }
|
||||
argv := wrapInTerminal(term, "DMS — System Update (custom)", opts.CustomCommand, opts.TerminalArgs)
|
||||
if err := Run(ctx, argv, RunOptions{OnLine: onLine}); err != nil {
|
||||
code := ErrCodeBackendFailed
|
||||
switch {
|
||||
case errors.Is(ctx.Err(), context.DeadlineExceeded):
|
||||
code = ErrCodeTimeout
|
||||
m.failCustomUpgrade(ErrCodeTimeout, err)
|
||||
return
|
||||
case errors.Is(ctx.Err(), context.Canceled):
|
||||
code = ErrCodeCancelled
|
||||
m.failCustomUpgrade(ErrCodeCancelled, err)
|
||||
return
|
||||
}
|
||||
m.mu.Lock()
|
||||
m.state.Phase = PhaseError
|
||||
m.state.Error = &ErrorInfo{Code: code, Message: err.Error()}
|
||||
m.mu.Unlock()
|
||||
m.markDirty()
|
||||
return
|
||||
// exit status reflects the trailing `read`, not the update command
|
||||
m.appendLog(fmt.Sprintf("Terminal exited early: %v", err))
|
||||
}
|
||||
|
||||
m.finishSuccessfulUpgrade(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) {
|
||||
m.appendLog("Upgrade complete.")
|
||||
|
||||
|
||||
@@ -233,3 +233,27 @@ func TestUpgradeBackendsFiltersFlatpakOnly(t *testing.T) {
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,16 +386,20 @@ end)
|
||||
HYPRLAND_LUA_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
elif [[ -z "$COMPOSITOR_CONFIG" ]]; then
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
cat > "$TEMP_CONFIG" << HYPRLAND_EOF
|
||||
env = DMS_RUN_GREETER,1
|
||||
TEMP_CONFIG=$(mktemp --suffix=.lua)
|
||||
cat > "$TEMP_CONFIG" << HYPRLAND_LUA_EOF
|
||||
hl.env("DMS_RUN_GREETER", "1")
|
||||
|
||||
misc {
|
||||
disable_hyprland_logo = true
|
||||
}
|
||||
hl.config({
|
||||
misc = {
|
||||
disable_hyprland_logo = true,
|
||||
},
|
||||
})
|
||||
|
||||
exec-once = sh -c "$QS_CMD; hyprctl dispatch exit"
|
||||
HYPRLAND_EOF
|
||||
hl.on("hyprland.start", function()
|
||||
hl.exec_cmd('sh -c "$QS_CMD; hyprctl dispatch exit"')
|
||||
end)
|
||||
HYPRLAND_LUA_EOF
|
||||
COMPOSITOR_CONFIG="$TEMP_CONFIG"
|
||||
else
|
||||
TEMP_CONFIG=$(mktemp)
|
||||
|
||||
@@ -11,7 +11,7 @@ Singleton {
|
||||
id: root
|
||||
readonly property var log: Log.scoped("DMSService")
|
||||
|
||||
property bool dmsAvailable: false
|
||||
readonly property bool dmsAvailable: isConnected
|
||||
property var capabilities: []
|
||||
property int apiVersion: 0
|
||||
property string cliVersion: ""
|
||||
@@ -21,7 +21,7 @@ Singleton {
|
||||
property var availableThemes: []
|
||||
property var installedThemes: []
|
||||
property bool isConnected: false
|
||||
property bool isConnecting: false
|
||||
readonly property bool isConnecting: requestSocket.connected && !requestSocket.linkUp
|
||||
property bool subscribeConnected: false
|
||||
|
||||
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
|
||||
@@ -72,9 +72,10 @@ Singleton {
|
||||
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "wallpaper", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "sysupdate"]
|
||||
|
||||
Component.onCompleted: {
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
detectUpdateCommand();
|
||||
}
|
||||
if (!socketPath || socketPath.length === 0)
|
||||
return;
|
||||
detectUpdateCommand();
|
||||
requestSocket.connected = true;
|
||||
}
|
||||
|
||||
function detectUpdateCommand() {
|
||||
@@ -82,12 +83,6 @@ Singleton {
|
||||
checkAurHelper.running = true;
|
||||
}
|
||||
|
||||
function startSocketConnection() {
|
||||
if (socketPath && socketPath.length > 0) {
|
||||
testProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: checkAurHelper
|
||||
command: ["sh", "-c", "command -v paru || command -v yay"]
|
||||
@@ -105,7 +100,6 @@ Singleton {
|
||||
} else {
|
||||
updateCommand = "dms update";
|
||||
checkingUpdateCommand = false;
|
||||
startSocketConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,7 +108,6 @@ Singleton {
|
||||
if (exitCode !== 0) {
|
||||
updateCommand = "dms update";
|
||||
checkingUpdateCommand = false;
|
||||
startSocketConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -135,7 +128,6 @@ Singleton {
|
||||
updateCommand = "dms update";
|
||||
}
|
||||
checkingUpdateCommand = false;
|
||||
startSocketConnection();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,52 +135,27 @@ Singleton {
|
||||
if (exitCode !== 0) {
|
||||
updateCommand = "dms update";
|
||||
checkingUpdateCommand = false;
|
||||
startSocketConnection();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Process {
|
||||
id: testProcess
|
||||
command: ["test", "-S", root.socketPath]
|
||||
|
||||
onExited: exitCode => {
|
||||
if (exitCode === 0) {
|
||||
root.dmsAvailable = true;
|
||||
connectSocket();
|
||||
} else {
|
||||
root.dmsAvailable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function connectSocket() {
|
||||
if (!dmsAvailable || isConnected || isConnecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
isConnecting = true;
|
||||
requestSocket.connected = true;
|
||||
}
|
||||
|
||||
DankSocket {
|
||||
id: requestSocket
|
||||
path: root.socketPath
|
||||
connected: false
|
||||
|
||||
onConnectionStateChanged: {
|
||||
if (connected) {
|
||||
if (linkUp) {
|
||||
root.isConnected = true;
|
||||
root.isConnecting = false;
|
||||
root.connectionStateChanged();
|
||||
subscribeSocket.connected = true;
|
||||
} else {
|
||||
root.isConnected = false;
|
||||
root.isConnecting = false;
|
||||
root.apiVersion = 0;
|
||||
root.capabilities = [];
|
||||
root.connectionStateChanged();
|
||||
return;
|
||||
}
|
||||
root.isConnected = false;
|
||||
root.apiVersion = 0;
|
||||
root.capabilities = [];
|
||||
root.failPendingRequests();
|
||||
root.connectionStateChanged();
|
||||
}
|
||||
|
||||
parser: SplitParser {
|
||||
@@ -219,10 +186,10 @@ Singleton {
|
||||
connected: false
|
||||
|
||||
onConnectionStateChanged: {
|
||||
root.subscribeConnected = connected;
|
||||
if (connected) {
|
||||
sendSubscribeRequest();
|
||||
}
|
||||
root.subscribeConnected = linkUp;
|
||||
if (!linkUp)
|
||||
return;
|
||||
sendSubscribeRequest();
|
||||
}
|
||||
|
||||
parser: SplitParser {
|
||||
@@ -335,6 +302,7 @@ Singleton {
|
||||
const data = response.result.data;
|
||||
|
||||
if (service === "server") {
|
||||
const prevCapabilities = capabilities;
|
||||
apiVersion = data.apiVersion || 0;
|
||||
cliVersion = data.cliVersion || "";
|
||||
capabilities = data.capabilities || [];
|
||||
@@ -346,6 +314,12 @@ Singleton {
|
||||
}
|
||||
|
||||
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") {
|
||||
networkStateUpdate(data);
|
||||
} else if (service === "network.credentials") {
|
||||
@@ -439,10 +413,20 @@ Singleton {
|
||||
|
||||
function handleResponse(response) {
|
||||
const callback = pendingRequests[response.id];
|
||||
if (!callback)
|
||||
return;
|
||||
delete pendingRequests[response.id];
|
||||
callback(response);
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
delete pendingRequests[response.id];
|
||||
callback(response);
|
||||
function failPendingRequests() {
|
||||
const pending = pendingRequests;
|
||||
pendingRequests = {};
|
||||
clipboardRequestIds = {};
|
||||
for (const id in pending) {
|
||||
pending[id]({
|
||||
"error": "not connected to DMS socket"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -217,6 +217,64 @@ Singleton {
|
||||
return envObj;
|
||||
}
|
||||
|
||||
function splitShellArgs(str) {
|
||||
const args = [];
|
||||
let current = "";
|
||||
let hasToken = false;
|
||||
let quote = "";
|
||||
let escaped = false;
|
||||
for (const ch of str) {
|
||||
if (escaped) {
|
||||
current += ch;
|
||||
escaped = false;
|
||||
continue;
|
||||
}
|
||||
switch (quote) {
|
||||
case "'":
|
||||
if (ch === "'") {
|
||||
quote = "";
|
||||
continue;
|
||||
}
|
||||
current += ch;
|
||||
continue;
|
||||
case "\"":
|
||||
switch (ch) {
|
||||
case "\"":
|
||||
quote = "";
|
||||
continue;
|
||||
case "\\":
|
||||
escaped = true;
|
||||
continue;
|
||||
}
|
||||
current += ch;
|
||||
continue;
|
||||
}
|
||||
switch (ch) {
|
||||
case "\\":
|
||||
escaped = true;
|
||||
continue;
|
||||
case "'":
|
||||
case "\"":
|
||||
quote = ch;
|
||||
hasToken = true;
|
||||
continue;
|
||||
case " ":
|
||||
case "\t":
|
||||
case "\n":
|
||||
if (!hasToken && current.length === 0)
|
||||
continue;
|
||||
args.push(current);
|
||||
current = "";
|
||||
hasToken = false;
|
||||
continue;
|
||||
}
|
||||
current += ch;
|
||||
}
|
||||
if (current.length > 0 || hasToken)
|
||||
args.push(current);
|
||||
return args;
|
||||
}
|
||||
|
||||
function launchDesktopEntry(desktopEntry, useNvidia) {
|
||||
if (!desktopEntry || !desktopEntry.command)
|
||||
return;
|
||||
@@ -230,8 +288,7 @@ Singleton {
|
||||
cmd = [nvidiaCommand].concat(cmd);
|
||||
|
||||
if (override?.extraFlags) {
|
||||
const extraArgs = override.extraFlags.trim().split(/\s+/).filter(arg => arg.length > 0);
|
||||
cmd = cmd.concat(extraArgs);
|
||||
cmd = cmd.concat(splitShellArgs(override.extraFlags));
|
||||
}
|
||||
|
||||
const userPrefix = SettingsData.launchPrefix?.trim() || "";
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
v1.5.2
|
||||
v1.5.3
|
||||
|
||||
Reference in New Issue
Block a user