mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-30 16:32:50 -05:00
Compare commits
5 Commits
7f15227de1
...
c2787f1282
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2787f1282 | ||
|
|
df940124b1 | ||
|
|
5288d042ca | ||
|
|
fa98a27c90 | ||
|
|
d341a5a60b |
@@ -28,7 +28,7 @@ packages:
|
||||
outpkg: mocks_brightness
|
||||
interfaces:
|
||||
DBusConn:
|
||||
github.com/AvengeMedia/danklinux/internal/server/network:
|
||||
github.com/AvengeMedia/DankMaterialShell/core/internal/server/network:
|
||||
config:
|
||||
dir: "internal/mocks/network"
|
||||
outpkg: mocks_network
|
||||
|
||||
@@ -509,6 +509,52 @@ func (_c *MockBackend_DisconnectWiFi_Call) RunAndReturn(run func() error) *MockB
|
||||
return _c
|
||||
}
|
||||
|
||||
// DisconnectWiFiDevice provides a mock function with given fields: device
|
||||
func (_m *MockBackend) DisconnectWiFiDevice(device string) error {
|
||||
ret := _m.Called(device)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DisconnectWiFiDevice")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(device)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_DisconnectWiFiDevice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DisconnectWiFiDevice'
|
||||
type MockBackend_DisconnectWiFiDevice_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DisconnectWiFiDevice is a helper method to define mock.On call
|
||||
// - device string
|
||||
func (_e *MockBackend_Expecter) DisconnectWiFiDevice(device interface{}) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
return &MockBackend_DisconnectWiFiDevice_Call{Call: _e.mock.On("DisconnectWiFiDevice", device)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectWiFiDevice_Call) Run(run func(device string)) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectWiFiDevice_Call) Return(_a0 error) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_DisconnectWiFiDevice_Call) RunAndReturn(run func(string) error) *MockBackend_DisconnectWiFiDevice_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ForgetWiFiNetwork provides a mock function with given fields: ssid
|
||||
func (_m *MockBackend) ForgetWiFiNetwork(ssid string) error {
|
||||
ret := _m.Called(ssid)
|
||||
@@ -659,6 +705,53 @@ func (_c *MockBackend_GetPromptBroker_Call) RunAndReturn(run func() network.Prom
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetWiFiDevices provides a mock function with no fields
|
||||
func (_m *MockBackend) GetWiFiDevices() []network.WiFiDevice {
|
||||
ret := _m.Called()
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetWiFiDevices")
|
||||
}
|
||||
|
||||
var r0 []network.WiFiDevice
|
||||
if rf, ok := ret.Get(0).(func() []network.WiFiDevice); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]network.WiFiDevice)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_GetWiFiDevices_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetWiFiDevices'
|
||||
type MockBackend_GetWiFiDevices_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetWiFiDevices is a helper method to define mock.On call
|
||||
func (_e *MockBackend_Expecter) GetWiFiDevices() *MockBackend_GetWiFiDevices_Call {
|
||||
return &MockBackend_GetWiFiDevices_Call{Call: _e.mock.On("GetWiFiDevices")}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetWiFiDevices_Call) Run(run func()) *MockBackend_GetWiFiDevices_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run()
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetWiFiDevices_Call) Return(_a0 []network.WiFiDevice) *MockBackend_GetWiFiDevices_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_GetWiFiDevices_Call) RunAndReturn(run func() []network.WiFiDevice) *MockBackend_GetWiFiDevices_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetWiFiEnabled provides a mock function with no fields
|
||||
func (_m *MockBackend) GetWiFiEnabled() (bool, error) {
|
||||
ret := _m.Called()
|
||||
@@ -1091,6 +1184,52 @@ func (_c *MockBackend_ScanWiFi_Call) RunAndReturn(run func() error) *MockBackend
|
||||
return _c
|
||||
}
|
||||
|
||||
// ScanWiFiDevice provides a mock function with given fields: device
|
||||
func (_m *MockBackend) ScanWiFiDevice(device string) error {
|
||||
ret := _m.Called(device)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ScanWiFiDevice")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string) error); ok {
|
||||
r0 = rf(device)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// MockBackend_ScanWiFiDevice_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ScanWiFiDevice'
|
||||
type MockBackend_ScanWiFiDevice_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ScanWiFiDevice is a helper method to define mock.On call
|
||||
// - device string
|
||||
func (_e *MockBackend_Expecter) ScanWiFiDevice(device interface{}) *MockBackend_ScanWiFiDevice_Call {
|
||||
return &MockBackend_ScanWiFiDevice_Call{Call: _e.mock.On("ScanWiFiDevice", device)}
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ScanWiFiDevice_Call) Run(run func(device string)) *MockBackend_ScanWiFiDevice_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ScanWiFiDevice_Call) Return(_a0 error) *MockBackend_ScanWiFiDevice_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *MockBackend_ScanWiFiDevice_Call) RunAndReturn(run func(string) error) *MockBackend_ScanWiFiDevice_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetPromptBroker provides a mock function with given fields: broker
|
||||
func (_m *MockBackend) SetPromptBroker(broker network.PromptBroker) error {
|
||||
ret := _m.Called(broker)
|
||||
|
||||
@@ -8,10 +8,13 @@ type Backend interface {
|
||||
SetWiFiEnabled(enabled bool) error
|
||||
|
||||
ScanWiFi() error
|
||||
ScanWiFiDevice(device string) error
|
||||
GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error)
|
||||
GetWiFiDevices() []WiFiDevice
|
||||
|
||||
ConnectWiFi(req ConnectionRequest) error
|
||||
DisconnectWiFi() error
|
||||
DisconnectWiFiDevice(device string) error
|
||||
ForgetWiFiNetwork(ssid string) error
|
||||
SetWiFiAutoconnect(ssid string, autoconnect bool) error
|
||||
|
||||
@@ -54,11 +57,13 @@ type BackendState struct {
|
||||
WiFiBSSID string
|
||||
WiFiSignal uint8
|
||||
WiFiNetworks []WiFiNetwork
|
||||
WiFiDevices []WiFiDevice
|
||||
WiredConnections []WiredConnection
|
||||
VPNProfiles []VPNProfile
|
||||
VPNActive []VPNActive
|
||||
IsConnecting bool
|
||||
ConnectingSSID string
|
||||
ConnectingDevice string
|
||||
IsConnectingVPN bool
|
||||
ConnectingVPNUUID string
|
||||
LastError string
|
||||
|
||||
@@ -196,3 +196,15 @@ func (b *HybridIwdNetworkdBackend) CancelCredentials(token string) error {
|
||||
func (b *HybridIwdNetworkdBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||
return b.wifi.SetWiFiAutoconnect(ssid, autoconnect)
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) ScanWiFiDevice(device string) error {
|
||||
return b.wifi.ScanWiFiDevice(device)
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) DisconnectWiFiDevice(device string) error {
|
||||
return b.wifi.DisconnectWiFiDevice(device)
|
||||
}
|
||||
|
||||
func (b *HybridIwdNetworkdBackend) GetWiFiDevices() []WiFiDevice {
|
||||
return b.wifi.GetWiFiDevices()
|
||||
}
|
||||
|
||||
@@ -139,9 +139,13 @@ func (b *IWDBackend) discoverDevices() error {
|
||||
}
|
||||
|
||||
func (b *IWDBackend) GetCurrentState() (*BackendState, error) {
|
||||
b.stateMutex.RLock()
|
||||
defer b.stateMutex.RUnlock()
|
||||
|
||||
state := *b.state
|
||||
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
||||
state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
|
||||
state.WiFiDevices = b.getWiFiDevicesLocked()
|
||||
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
@@ -45,3 +45,38 @@ func (b *IWDBackend) DisconnectAllVPN() error {
|
||||
func (b *IWDBackend) ClearVPNCredentials(uuidOrName string) error {
|
||||
return fmt.Errorf("VPN not supported by iwd backend")
|
||||
}
|
||||
|
||||
func (b *IWDBackend) ScanWiFiDevice(device string) error {
|
||||
return b.ScanWiFi()
|
||||
}
|
||||
|
||||
func (b *IWDBackend) DisconnectWiFiDevice(device string) error {
|
||||
return b.DisconnectWiFi()
|
||||
}
|
||||
|
||||
func (b *IWDBackend) GetWiFiDevices() []WiFiDevice {
|
||||
b.stateMutex.RLock()
|
||||
defer b.stateMutex.RUnlock()
|
||||
return b.getWiFiDevicesLocked()
|
||||
}
|
||||
|
||||
func (b *IWDBackend) getWiFiDevicesLocked() []WiFiDevice {
|
||||
if b.state.WiFiDevice == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
stateStr := "disconnected"
|
||||
if b.state.WiFiConnected {
|
||||
stateStr = "connected"
|
||||
}
|
||||
|
||||
return []WiFiDevice{{
|
||||
Name: b.state.WiFiDevice,
|
||||
State: stateStr,
|
||||
Connected: b.state.WiFiConnected,
|
||||
SSID: b.state.WiFiSSID,
|
||||
Signal: b.state.WiFiSignal,
|
||||
IP: b.state.WiFiIP,
|
||||
Networks: b.state.WiFiNetworks,
|
||||
}}
|
||||
}
|
||||
|
||||
@@ -57,3 +57,15 @@ func (b *SystemdNetworkdBackend) ClearVPNCredentials(uuidOrName string) error {
|
||||
func (b *SystemdNetworkdBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||
return fmt.Errorf("WiFi autoconnect not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) ScanWiFiDevice(device string) error {
|
||||
return fmt.Errorf("WiFi scan not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) DisconnectWiFiDevice(device string) error {
|
||||
return fmt.Errorf("WiFi disconnect not supported by networkd backend")
|
||||
}
|
||||
|
||||
func (b *SystemdNetworkdBackend) GetWiFiDevices() []WiFiDevice {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -30,12 +30,20 @@ const (
|
||||
NmDeviceStateReasonNewActivation = 60
|
||||
)
|
||||
|
||||
type wifiDeviceInfo struct {
|
||||
device gonetworkmanager.Device
|
||||
wireless gonetworkmanager.DeviceWireless
|
||||
name string
|
||||
hwAddress string
|
||||
}
|
||||
|
||||
type NetworkManagerBackend struct {
|
||||
nmConn interface{}
|
||||
ethernetDevice interface{}
|
||||
wifiDevice interface{}
|
||||
settings interface{}
|
||||
wifiDev interface{}
|
||||
wifiDevices map[string]*wifiDeviceInfo
|
||||
|
||||
dbusConn *dbus.Conn
|
||||
signals chan *dbus.Signal
|
||||
@@ -71,8 +79,9 @@ func NewNetworkManagerBackend(nmConn ...gonetworkmanager.NetworkManager) (*Netwo
|
||||
}
|
||||
|
||||
backend := &NetworkManagerBackend{
|
||||
nmConn: nm,
|
||||
stopChan: make(chan struct{}),
|
||||
nmConn: nm,
|
||||
stopChan: make(chan struct{}),
|
||||
wifiDevices: make(map[string]*wifiDeviceInfo),
|
||||
state: &BackendState{
|
||||
Backend: "networkmanager",
|
||||
},
|
||||
@@ -114,27 +123,48 @@ func (b *NetworkManagerBackend) Initialize() error {
|
||||
}
|
||||
|
||||
case gonetworkmanager.NmDeviceTypeWifi:
|
||||
b.wifiDevice = dev
|
||||
if w, err := gonetworkmanager.NewDeviceWireless(dev.GetPath()); err == nil {
|
||||
b.wifiDev = w
|
||||
}
|
||||
wifiEnabled, err := nm.GetPropertyWirelessEnabled()
|
||||
if err == nil {
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiEnabled = wifiEnabled
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
if err := b.updateWiFiState(); err != nil {
|
||||
iface, err := dev.GetPropertyInterface()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if wifiEnabled {
|
||||
if _, err := b.updateWiFiNetworks(); err != nil {
|
||||
log.Warnf("Failed to get initial networks: %v", err)
|
||||
}
|
||||
w, err := gonetworkmanager.NewDeviceWireless(dev.GetPath())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
hwAddr, _ := w.GetPropertyHwAddress()
|
||||
|
||||
b.wifiDevices[iface] = &wifiDeviceInfo{
|
||||
device: dev,
|
||||
wireless: w,
|
||||
name: iface,
|
||||
hwAddress: hwAddr,
|
||||
}
|
||||
|
||||
if b.wifiDevice == nil {
|
||||
b.wifiDevice = dev
|
||||
b.wifiDev = w
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
wifiEnabled, err := nm.GetPropertyWirelessEnabled()
|
||||
if err == nil {
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiEnabled = wifiEnabled
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
|
||||
if err := b.updateWiFiState(); err != nil {
|
||||
log.Warnf("Failed to update WiFi state: %v", err)
|
||||
}
|
||||
|
||||
if wifiEnabled {
|
||||
if _, err := b.updateWiFiNetworks(); err != nil {
|
||||
log.Warnf("Failed to get initial networks: %v", err)
|
||||
}
|
||||
b.updateAllWiFiDevices()
|
||||
}
|
||||
|
||||
if err := b.updatePrimaryConnection(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -165,6 +195,7 @@ func (b *NetworkManagerBackend) GetCurrentState() (*BackendState, error) {
|
||||
|
||||
state := *b.state
|
||||
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
||||
state.WiFiDevices = append([]WiFiDevice(nil), b.state.WiFiDevices...)
|
||||
state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
|
||||
state.VPNProfiles = append([]VPNProfile(nil), b.state.VPNProfiles...)
|
||||
state.VPNActive = append([]VPNActive(nil), b.state.VPNActive...)
|
||||
|
||||
@@ -197,21 +197,23 @@ func (b *NetworkManagerBackend) GetWiFiNetworkDetails(ssid string) (*NetworkInfo
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||
if b.wifiDevice == nil {
|
||||
return fmt.Errorf("no WiFi device available")
|
||||
devInfo, err := b.getWifiDeviceForConnection(req.Device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b.stateMutex.RLock()
|
||||
alreadyConnected := b.state.WiFiConnected && b.state.WiFiSSID == req.SSID
|
||||
b.stateMutex.RUnlock()
|
||||
|
||||
if alreadyConnected && !req.Interactive {
|
||||
if alreadyConnected && !req.Interactive && req.Device == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnecting = true
|
||||
b.state.ConnectingSSID = req.SSID
|
||||
b.state.ConnectingDevice = req.Device
|
||||
b.state.LastError = ""
|
||||
b.stateMutex.Unlock()
|
||||
|
||||
@@ -223,14 +225,13 @@ func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||
|
||||
existingConn, err := b.findConnection(req.SSID)
|
||||
if err == nil && existingConn != nil {
|
||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
||||
|
||||
_, err := nm.ActivateConnection(existingConn, dev, nil)
|
||||
_, err := nm.ActivateConnection(existingConn, devInfo.device, nil)
|
||||
if err != nil {
|
||||
log.Warnf("[ConnectWiFi] Failed to activate existing connection: %v", err)
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnecting = false
|
||||
b.state.ConnectingSSID = ""
|
||||
b.state.ConnectingDevice = ""
|
||||
b.state.LastError = fmt.Sprintf("failed to activate connection: %v", err)
|
||||
b.stateMutex.Unlock()
|
||||
if b.onStateChange != nil {
|
||||
@@ -242,11 +243,12 @@ func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := b.createAndConnectWiFi(req); err != nil {
|
||||
if err := b.createAndConnectWiFiOnDevice(req, devInfo); err != nil {
|
||||
log.Warnf("[ConnectWiFi] Failed to create and connect: %v", err)
|
||||
b.stateMutex.Lock()
|
||||
b.state.IsConnecting = false
|
||||
b.state.ConnectingSSID = ""
|
||||
b.state.ConnectingDevice = ""
|
||||
b.state.LastError = err.Error()
|
||||
b.stateMutex.Unlock()
|
||||
if b.onStateChange != nil {
|
||||
@@ -502,19 +504,17 @@ func (b *NetworkManagerBackend) findConnection(ssid string) (gonetworkmanager.Co
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) createAndConnectWiFi(req ConnectionRequest) error {
|
||||
if b.wifiDevice == nil {
|
||||
return fmt.Errorf("no WiFi device available")
|
||||
}
|
||||
|
||||
nm := b.nmConn.(gonetworkmanager.NetworkManager)
|
||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
||||
|
||||
if err := b.ensureWiFiDevice(); err != nil {
|
||||
devInfo, err := b.getWifiDeviceForConnection(req.Device)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wifiDev := b.wifiDev
|
||||
return b.createAndConnectWiFiOnDevice(req, devInfo)
|
||||
}
|
||||
|
||||
w := wifiDev.(gonetworkmanager.DeviceWireless)
|
||||
func (b *NetworkManagerBackend) createAndConnectWiFiOnDevice(req ConnectionRequest, devInfo *wifiDeviceInfo) error {
|
||||
nm := b.nmConn.(gonetworkmanager.NetworkManager)
|
||||
dev := devInfo.device
|
||||
w := devInfo.wireless
|
||||
apPaths, err := w.GetAccessPoints()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get access points: %w", err)
|
||||
@@ -716,3 +716,254 @@ func (b *NetworkManagerBackend) SetWiFiAutoconnect(ssid string, autoconnect bool
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) ScanWiFiDevice(device string) error {
|
||||
devInfo, ok := b.wifiDevices[device]
|
||||
if !ok {
|
||||
return fmt.Errorf("WiFi device not found: %s", device)
|
||||
}
|
||||
|
||||
b.stateMutex.RLock()
|
||||
enabled := b.state.WiFiEnabled
|
||||
b.stateMutex.RUnlock()
|
||||
|
||||
if !enabled {
|
||||
return fmt.Errorf("WiFi is disabled")
|
||||
}
|
||||
|
||||
if err := devInfo.wireless.RequestScan(); err != nil {
|
||||
return fmt.Errorf("scan request failed: %w", err)
|
||||
}
|
||||
|
||||
b.updateAllWiFiDevices()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) DisconnectWiFiDevice(device string) error {
|
||||
devInfo, ok := b.wifiDevices[device]
|
||||
if !ok {
|
||||
return fmt.Errorf("WiFi device not found: %s", device)
|
||||
}
|
||||
|
||||
if err := devInfo.device.Disconnect(); err != nil {
|
||||
return fmt.Errorf("failed to disconnect: %w", err)
|
||||
}
|
||||
|
||||
b.updateWiFiState()
|
||||
b.updateAllWiFiDevices()
|
||||
b.updatePrimaryConnection()
|
||||
|
||||
if b.onStateChange != nil {
|
||||
b.onStateChange()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) GetWiFiDevices() []WiFiDevice {
|
||||
b.stateMutex.RLock()
|
||||
defer b.stateMutex.RUnlock()
|
||||
return append([]WiFiDevice(nil), b.state.WiFiDevices...)
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) updateAllWiFiDevices() {
|
||||
s := b.settings
|
||||
if s == nil {
|
||||
var err error
|
||||
s, err = gonetworkmanager.NewSettings()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
b.settings = s
|
||||
}
|
||||
|
||||
settingsMgr := s.(gonetworkmanager.Settings)
|
||||
connections, err := settingsMgr.ListConnections()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
savedSSIDs := make(map[string]bool)
|
||||
autoconnectMap := make(map[string]bool)
|
||||
for _, conn := range connections {
|
||||
connSettings, err := conn.GetSettings()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
connMeta, ok := connSettings["connection"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
connType, ok := connMeta["type"].(string)
|
||||
if !ok || connType != "802-11-wireless" {
|
||||
continue
|
||||
}
|
||||
|
||||
wifiSettings, ok := connSettings["802-11-wireless"]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ssidBytes, ok := wifiSettings["ssid"].([]byte)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
ssid := string(ssidBytes)
|
||||
savedSSIDs[ssid] = true
|
||||
autoconnect := true
|
||||
if ac, ok := connMeta["autoconnect"].(bool); ok {
|
||||
autoconnect = ac
|
||||
}
|
||||
autoconnectMap[ssid] = autoconnect
|
||||
}
|
||||
|
||||
var devices []WiFiDevice
|
||||
|
||||
for name, devInfo := range b.wifiDevices {
|
||||
state, _ := devInfo.device.GetPropertyState()
|
||||
connected := state == gonetworkmanager.NmDeviceStateActivated
|
||||
|
||||
var ssid, bssid, ip string
|
||||
var signal uint8
|
||||
|
||||
if connected {
|
||||
if activeAP, err := devInfo.wireless.GetPropertyActiveAccessPoint(); err == nil && activeAP != nil && activeAP.GetPath() != "/" {
|
||||
ssid, _ = activeAP.GetPropertySSID()
|
||||
signal, _ = activeAP.GetPropertyStrength()
|
||||
bssid, _ = activeAP.GetPropertyHWAddress()
|
||||
}
|
||||
ip = b.getDeviceIP(devInfo.device)
|
||||
}
|
||||
|
||||
stateStr := "disconnected"
|
||||
switch state {
|
||||
case gonetworkmanager.NmDeviceStateActivated:
|
||||
stateStr = "connected"
|
||||
case gonetworkmanager.NmDeviceStateConfig, gonetworkmanager.NmDeviceStateIpConfig:
|
||||
stateStr = "connecting"
|
||||
case gonetworkmanager.NmDeviceStatePrepare:
|
||||
stateStr = "preparing"
|
||||
case gonetworkmanager.NmDeviceStateDeactivating:
|
||||
stateStr = "disconnecting"
|
||||
}
|
||||
|
||||
apPaths, err := devInfo.wireless.GetAccessPoints()
|
||||
var networks []WiFiNetwork
|
||||
if err == nil {
|
||||
seenSSIDs := make(map[string]*WiFiNetwork)
|
||||
for _, ap := range apPaths {
|
||||
apSSID, err := ap.GetPropertySSID()
|
||||
if err != nil || apSSID == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if existing, exists := seenSSIDs[apSSID]; exists {
|
||||
strength, _ := ap.GetPropertyStrength()
|
||||
if strength > existing.Signal {
|
||||
existing.Signal = strength
|
||||
freq, _ := ap.GetPropertyFrequency()
|
||||
existing.Frequency = freq
|
||||
apBSSID, _ := ap.GetPropertyHWAddress()
|
||||
existing.BSSID = apBSSID
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
strength, _ := ap.GetPropertyStrength()
|
||||
flags, _ := ap.GetPropertyFlags()
|
||||
wpaFlags, _ := ap.GetPropertyWPAFlags()
|
||||
rsnFlags, _ := ap.GetPropertyRSNFlags()
|
||||
freq, _ := ap.GetPropertyFrequency()
|
||||
maxBitrate, _ := ap.GetPropertyMaxBitrate()
|
||||
apBSSID, _ := ap.GetPropertyHWAddress()
|
||||
mode, _ := ap.GetPropertyMode()
|
||||
|
||||
secured := flags != uint32(gonetworkmanager.Nm80211APFlagsNone) ||
|
||||
wpaFlags != uint32(gonetworkmanager.Nm80211APSecNone) ||
|
||||
rsnFlags != uint32(gonetworkmanager.Nm80211APSecNone)
|
||||
|
||||
enterprise := (rsnFlags&uint32(gonetworkmanager.Nm80211APSecKeyMgmt8021X) != 0) ||
|
||||
(wpaFlags&uint32(gonetworkmanager.Nm80211APSecKeyMgmt8021X) != 0)
|
||||
|
||||
var modeStr string
|
||||
switch mode {
|
||||
case gonetworkmanager.Nm80211ModeAdhoc:
|
||||
modeStr = "adhoc"
|
||||
case gonetworkmanager.Nm80211ModeInfra:
|
||||
modeStr = "infrastructure"
|
||||
case gonetworkmanager.Nm80211ModeAp:
|
||||
modeStr = "ap"
|
||||
default:
|
||||
modeStr = "unknown"
|
||||
}
|
||||
|
||||
channel := frequencyToChannel(freq)
|
||||
|
||||
network := WiFiNetwork{
|
||||
SSID: apSSID,
|
||||
BSSID: apBSSID,
|
||||
Signal: strength,
|
||||
Secured: secured,
|
||||
Enterprise: enterprise,
|
||||
Connected: connected && apSSID == ssid,
|
||||
Saved: savedSSIDs[apSSID],
|
||||
Autoconnect: autoconnectMap[apSSID],
|
||||
Frequency: freq,
|
||||
Mode: modeStr,
|
||||
Rate: maxBitrate / 1000,
|
||||
Channel: channel,
|
||||
Device: name,
|
||||
}
|
||||
|
||||
seenSSIDs[apSSID] = &network
|
||||
networks = append(networks, network)
|
||||
}
|
||||
sortWiFiNetworks(networks)
|
||||
}
|
||||
|
||||
devices = append(devices, WiFiDevice{
|
||||
Name: name,
|
||||
HwAddress: devInfo.hwAddress,
|
||||
State: stateStr,
|
||||
Connected: connected,
|
||||
SSID: ssid,
|
||||
BSSID: bssid,
|
||||
Signal: signal,
|
||||
IP: ip,
|
||||
Networks: networks,
|
||||
})
|
||||
}
|
||||
|
||||
sort.Slice(devices, func(i, j int) bool {
|
||||
return devices[i].Name < devices[j].Name
|
||||
})
|
||||
|
||||
b.stateMutex.Lock()
|
||||
b.state.WiFiDevices = devices
|
||||
b.stateMutex.Unlock()
|
||||
}
|
||||
|
||||
func (b *NetworkManagerBackend) getWifiDeviceForConnection(deviceName string) (*wifiDeviceInfo, error) {
|
||||
if deviceName != "" {
|
||||
devInfo, ok := b.wifiDevices[deviceName]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("WiFi device not found: %s", deviceName)
|
||||
}
|
||||
return devInfo, nil
|
||||
}
|
||||
|
||||
if b.wifiDevice == nil {
|
||||
return nil, fmt.Errorf("no WiFi device available")
|
||||
}
|
||||
|
||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
||||
iface, _ := dev.GetPropertyInterface()
|
||||
if devInfo, ok := b.wifiDevices[iface]; ok {
|
||||
return devInfo, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no WiFi device available")
|
||||
}
|
||||
|
||||
@@ -101,10 +101,21 @@ func TestNetworkManagerBackend_ConnectWiFi_AlreadyConnected(t *testing.T) {
|
||||
|
||||
backend.wifiDevice = mockDeviceWireless
|
||||
backend.wifiDev = mockDeviceWireless
|
||||
backend.wifiDevices = map[string]*wifiDeviceInfo{
|
||||
"wlan0": {
|
||||
device: nil,
|
||||
wireless: mockDeviceWireless,
|
||||
name: "wlan0",
|
||||
hwAddress: "00:11:22:33:44:55",
|
||||
},
|
||||
}
|
||||
|
||||
mockDeviceWireless.EXPECT().GetPropertyInterface().Return("wlan0", nil)
|
||||
|
||||
backend.stateMutex.Lock()
|
||||
backend.state.WiFiConnected = true
|
||||
backend.state.WiFiSSID = "TestNetwork"
|
||||
backend.state.WiFiDevice = "wlan0"
|
||||
backend.stateMutex.Unlock()
|
||||
|
||||
req := ConnectionRequest{SSID: "TestNetwork", Password: "password"}
|
||||
|
||||
@@ -135,7 +135,14 @@ func handleGetState(conn net.Conn, req Request, manager *Manager) {
|
||||
}
|
||||
|
||||
func handleScanWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
if err := manager.ScanWiFi(); err != nil {
|
||||
device, _ := req.Params["device"].(string)
|
||||
var err error
|
||||
if device != "" {
|
||||
err = manager.ScanWiFiDevice(device)
|
||||
} else {
|
||||
err = manager.ScanWiFi()
|
||||
}
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
@@ -163,6 +170,9 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
if username, ok := req.Params["username"].(string); ok {
|
||||
connReq.Username = username
|
||||
}
|
||||
if device, ok := req.Params["device"].(string); ok {
|
||||
connReq.Device = device
|
||||
}
|
||||
|
||||
if interactive, ok := req.Params["interactive"].(bool); ok {
|
||||
connReq.Interactive = interactive
|
||||
@@ -170,7 +180,7 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
state := manager.GetState()
|
||||
alreadyConnected := state.WiFiConnected && state.WiFiSSID == ssid
|
||||
|
||||
if alreadyConnected {
|
||||
if alreadyConnected && connReq.Device == "" {
|
||||
connReq.Interactive = false
|
||||
} else {
|
||||
networkInfo, err := manager.GetNetworkInfo(ssid)
|
||||
@@ -200,7 +210,14 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
}
|
||||
|
||||
func handleDisconnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
||||
if err := manager.DisconnectWiFi(); err != nil {
|
||||
device, _ := req.Params["device"].(string)
|
||||
var err error
|
||||
if device != "" {
|
||||
err = manager.DisconnectWiFiDevice(device)
|
||||
} else {
|
||||
err = manager.DisconnectWiFi()
|
||||
}
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
@@ -117,11 +117,13 @@ func (m *Manager) syncStateFromBackend() error {
|
||||
m.state.WiFiBSSID = backendState.WiFiBSSID
|
||||
m.state.WiFiSignal = backendState.WiFiSignal
|
||||
m.state.WiFiNetworks = backendState.WiFiNetworks
|
||||
m.state.WiFiDevices = backendState.WiFiDevices
|
||||
m.state.WiredConnections = backendState.WiredConnections
|
||||
m.state.VPNProfiles = backendState.VPNProfiles
|
||||
m.state.VPNActive = backendState.VPNActive
|
||||
m.state.IsConnecting = backendState.IsConnecting
|
||||
m.state.ConnectingSSID = backendState.ConnectingSSID
|
||||
m.state.ConnectingDevice = backendState.ConnectingDevice
|
||||
m.state.LastError = backendState.LastError
|
||||
m.stateMutex.Unlock()
|
||||
|
||||
@@ -151,6 +153,7 @@ func (m *Manager) snapshotState() NetworkState {
|
||||
defer m.stateMutex.RUnlock()
|
||||
s := *m.state
|
||||
s.WiFiNetworks = append([]WiFiNetwork(nil), m.state.WiFiNetworks...)
|
||||
s.WiFiDevices = append([]WiFiDevice(nil), m.state.WiFiDevices...)
|
||||
s.WiredConnections = append([]WiredConnection(nil), m.state.WiredConnections...)
|
||||
s.VPNProfiles = append([]VPNProfile(nil), m.state.VPNProfiles...)
|
||||
s.VPNActive = append([]VPNActive(nil), m.state.VPNActive...)
|
||||
@@ -204,6 +207,9 @@ func stateChangedMeaningfully(old, new *NetworkState) bool {
|
||||
if len(old.WiFiNetworks) != len(new.WiFiNetworks) {
|
||||
return true
|
||||
}
|
||||
if len(old.WiFiDevices) != len(new.WiFiDevices) {
|
||||
return true
|
||||
}
|
||||
if len(old.WiredConnections) != len(new.WiredConnections) {
|
||||
return true
|
||||
}
|
||||
@@ -505,3 +511,19 @@ func (m *Manager) ClearVPNCredentials(uuidOrName string) error {
|
||||
func (m *Manager) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||
return m.backend.SetWiFiAutoconnect(ssid, autoconnect)
|
||||
}
|
||||
|
||||
func (m *Manager) GetWiFiDevices() []WiFiDevice {
|
||||
m.stateMutex.RLock()
|
||||
defer m.stateMutex.RUnlock()
|
||||
devices := make([]WiFiDevice, len(m.state.WiFiDevices))
|
||||
copy(devices, m.state.WiFiDevices)
|
||||
return devices
|
||||
}
|
||||
|
||||
func (m *Manager) ScanWiFiDevice(device string) error {
|
||||
return m.backend.ScanWiFiDevice(device)
|
||||
}
|
||||
|
||||
func (m *Manager) DisconnectWiFiDevice(device string) error {
|
||||
return m.backend.DisconnectWiFiDevice(device)
|
||||
}
|
||||
|
||||
@@ -37,6 +37,19 @@ type WiFiNetwork struct {
|
||||
Mode string `json:"mode"`
|
||||
Rate uint32 `json:"rate"`
|
||||
Channel uint32 `json:"channel"`
|
||||
Device string `json:"device,omitempty"`
|
||||
}
|
||||
|
||||
type WiFiDevice struct {
|
||||
Name string `json:"name"`
|
||||
HwAddress string `json:"hwAddress"`
|
||||
State string `json:"state"`
|
||||
Connected bool `json:"connected"`
|
||||
SSID string `json:"ssid,omitempty"`
|
||||
BSSID string `json:"bssid,omitempty"`
|
||||
Signal uint8 `json:"signal,omitempty"`
|
||||
IP string `json:"ip,omitempty"`
|
||||
Networks []WiFiNetwork `json:"networks"`
|
||||
}
|
||||
|
||||
type VPNProfile struct {
|
||||
@@ -76,11 +89,13 @@ type NetworkState struct {
|
||||
WiFiBSSID string `json:"wifiBSSID"`
|
||||
WiFiSignal uint8 `json:"wifiSignal"`
|
||||
WiFiNetworks []WiFiNetwork `json:"wifiNetworks"`
|
||||
WiFiDevices []WiFiDevice `json:"wifiDevices"`
|
||||
WiredConnections []WiredConnection `json:"wiredConnections"`
|
||||
VPNProfiles []VPNProfile `json:"vpnProfiles"`
|
||||
VPNActive []VPNActive `json:"vpnActive"`
|
||||
IsConnecting bool `json:"isConnecting"`
|
||||
ConnectingSSID string `json:"connectingSSID"`
|
||||
ConnectingDevice string `json:"connectingDevice,omitempty"`
|
||||
LastError string `json:"lastError"`
|
||||
}
|
||||
|
||||
@@ -91,6 +106,7 @@ type ConnectionRequest struct {
|
||||
AnonymousIdentity string `json:"anonymousIdentity,omitempty"`
|
||||
DomainSuffixMatch string `json:"domainSuffixMatch,omitempty"`
|
||||
Interactive bool `json:"interactive,omitempty"`
|
||||
Device string `json:"device,omitempty"`
|
||||
}
|
||||
|
||||
type WiredConnection struct {
|
||||
|
||||
@@ -31,7 +31,7 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||
)
|
||||
|
||||
const APIVersion = 19
|
||||
const APIVersion = 20
|
||||
|
||||
type Capabilities struct {
|
||||
Capabilities []string `json:"capabilities"`
|
||||
@@ -1071,10 +1071,10 @@ func Start(printDocs bool) error {
|
||||
log.Info(" plugins.search - Search plugins (params: query, category?, compositor?, capability?)")
|
||||
log.Info("Network:")
|
||||
log.Info(" network.getState - Get current network state")
|
||||
log.Info(" network.wifi.scan - Scan for WiFi networks")
|
||||
log.Info(" network.wifi.scan - Scan for WiFi networks (params: device?)")
|
||||
log.Info(" network.wifi.networks - Get WiFi network list")
|
||||
log.Info(" network.wifi.connect - Connect to WiFi (params: ssid, password?, username?)")
|
||||
log.Info(" network.wifi.disconnect - Disconnect WiFi")
|
||||
log.Info(" network.wifi.connect - Connect to WiFi (params: ssid, password?, username?, device?)")
|
||||
log.Info(" network.wifi.disconnect - Disconnect WiFi (params: device?)")
|
||||
log.Info(" network.wifi.forget - Forget network (params: ssid)")
|
||||
log.Info(" network.wifi.toggle - Toggle WiFi radio")
|
||||
log.Info(" network.wifi.enable - Enable WiFi")
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -105,6 +105,11 @@ Singleton {
|
||||
property bool controlCenterShowNetworkIcon: true
|
||||
property bool controlCenterShowBluetoothIcon: true
|
||||
property bool controlCenterShowAudioIcon: true
|
||||
property bool controlCenterShowVpnIcon: false
|
||||
property bool controlCenterShowBrightnessIcon: false
|
||||
property bool controlCenterShowMicIcon: false
|
||||
property bool controlCenterShowBatteryIcon: false
|
||||
property bool controlCenterShowPrinterIcon: false
|
||||
property bool showPrivacyButton: true
|
||||
property bool privacyShowMicIcon: false
|
||||
property bool privacyShowCameraIcon: false
|
||||
|
||||
@@ -51,6 +51,11 @@ var SPEC = {
|
||||
controlCenterShowNetworkIcon: { def: true },
|
||||
controlCenterShowBluetoothIcon: { def: true },
|
||||
controlCenterShowAudioIcon: { def: true },
|
||||
controlCenterShowVpnIcon: { def: false },
|
||||
controlCenterShowBrightnessIcon: { def: false },
|
||||
controlCenterShowMicIcon: { def: false },
|
||||
controlCenterShowBatteryIcon: { def: false },
|
||||
controlCenterShowPrinterIcon: { def: false },
|
||||
|
||||
showPrivacyButton: { def: true },
|
||||
privacyShowMicIcon: { def: false },
|
||||
|
||||
@@ -563,4 +563,42 @@ Item {
|
||||
|
||||
target: "file"
|
||||
}
|
||||
|
||||
IpcHandler {
|
||||
function toggle(widgetId: string): string {
|
||||
if (!widgetId)
|
||||
return "ERROR: No widget ID specified";
|
||||
|
||||
if (!BarWidgetService.hasWidget(widgetId))
|
||||
return `WIDGET_NOT_FOUND: ${widgetId}`;
|
||||
|
||||
const success = BarWidgetService.triggerWidgetPopout(widgetId);
|
||||
return success ? `WIDGET_TOGGLE_SUCCESS: ${widgetId}` : `WIDGET_TOGGLE_FAILED: ${widgetId}`;
|
||||
}
|
||||
|
||||
function list(): string {
|
||||
const widgets = BarWidgetService.getRegisteredWidgetIds();
|
||||
if (widgets.length === 0)
|
||||
return "No widgets registered";
|
||||
return widgets.join("\n");
|
||||
}
|
||||
|
||||
function status(widgetId: string): string {
|
||||
if (!widgetId)
|
||||
return "ERROR: No widget ID specified";
|
||||
|
||||
if (!BarWidgetService.hasWidget(widgetId))
|
||||
return `WIDGET_NOT_FOUND: ${widgetId}`;
|
||||
|
||||
const widget = BarWidgetService.getWidgetOnFocusedScreen(widgetId);
|
||||
if (!widget)
|
||||
return `WIDGET_NOT_AVAILABLE: ${widgetId}`;
|
||||
|
||||
if (widget.popoutTarget?.shouldBeVisible)
|
||||
return "visible";
|
||||
return "hidden";
|
||||
}
|
||||
|
||||
target: "widget"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import QtQuick
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Modules.ControlCenter.Details
|
||||
import qs.Modules.ControlCenter.Models
|
||||
|
||||
Item {
|
||||
id: root
|
||||
@@ -11,17 +10,21 @@ Item {
|
||||
property var expandedWidgetData: null
|
||||
property var bluetoothCodecSelector: null
|
||||
property string screenName: ""
|
||||
property string screenModel: ""
|
||||
|
||||
property var pluginDetailInstance: null
|
||||
property var widgetModel: null
|
||||
property var collapseCallback: null
|
||||
|
||||
function getDetailHeight(section) {
|
||||
const maxAvailable = parent ? parent.height - Theme.spacingS : 9999
|
||||
if (section === "wifi") return Math.min(350, maxAvailable)
|
||||
if (section === "bluetooth") return Math.min(350, maxAvailable)
|
||||
if (section.startsWith("brightnessSlider_")) return Math.min(400, maxAvailable)
|
||||
return Math.min(250, maxAvailable)
|
||||
const maxAvailable = parent ? parent.height - Theme.spacingS : 9999;
|
||||
if (section === "wifi")
|
||||
return Math.min(350, maxAvailable);
|
||||
if (section === "bluetooth")
|
||||
return Math.min(350, maxAvailable);
|
||||
if (section.startsWith("brightnessSlider_"))
|
||||
return Math.min(400, maxAvailable);
|
||||
return Math.min(250, maxAvailable);
|
||||
}
|
||||
|
||||
Loader {
|
||||
@@ -49,18 +52,18 @@ Item {
|
||||
|
||||
function onDeviceNameChanged(newDeviceName) {
|
||||
if (root.expandedWidgetData && root.expandedWidgetData.id === "brightnessSlider") {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
const widgets = SettingsData.controlCenterWidgets || [];
|
||||
const newWidgets = widgets.map(w => {
|
||||
if (w.id === "brightnessSlider" && w.instanceId === root.expandedWidgetData.instanceId) {
|
||||
const updatedWidget = Object.assign({}, w)
|
||||
updatedWidget.deviceName = newDeviceName
|
||||
return updatedWidget
|
||||
const updatedWidget = Object.assign({}, w);
|
||||
updatedWidget.deviceName = newDeviceName;
|
||||
return updatedWidget;
|
||||
}
|
||||
return w
|
||||
})
|
||||
SettingsData.set("controlCenterWidgets", newWidgets)
|
||||
return w;
|
||||
});
|
||||
SettingsData.set("controlCenterWidgets", newWidgets);
|
||||
if (root.collapseCallback) {
|
||||
root.collapseCallback()
|
||||
root.collapseCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,18 +76,18 @@ Item {
|
||||
|
||||
function onMountPathChanged(newMountPath) {
|
||||
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
const widgets = SettingsData.controlCenterWidgets || [];
|
||||
const newWidgets = widgets.map(w => {
|
||||
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
|
||||
const updatedWidget = Object.assign({}, w)
|
||||
updatedWidget.mountPath = newMountPath
|
||||
return updatedWidget
|
||||
const updatedWidget = Object.assign({}, w);
|
||||
updatedWidget.mountPath = newMountPath;
|
||||
return updatedWidget;
|
||||
}
|
||||
return w
|
||||
})
|
||||
SettingsData.set("controlCenterWidgets", newWidgets)
|
||||
return w;
|
||||
});
|
||||
SettingsData.set("controlCenterWidgets", newWidgets);
|
||||
if (root.collapseCallback) {
|
||||
root.collapseCallback()
|
||||
root.collapseCallback();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,86 +95,97 @@ Item {
|
||||
|
||||
onExpandedSectionChanged: {
|
||||
if (pluginDetailInstance) {
|
||||
pluginDetailInstance.destroy()
|
||||
pluginDetailInstance = null
|
||||
pluginDetailInstance.destroy();
|
||||
pluginDetailInstance = null;
|
||||
}
|
||||
pluginDetailLoader.active = false
|
||||
coreDetailLoader.active = false
|
||||
pluginDetailLoader.active = false;
|
||||
coreDetailLoader.active = false;
|
||||
|
||||
if (!root.expandedSection) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.expandedSection.startsWith("builtin_")) {
|
||||
const builtinId = root.expandedSection
|
||||
let builtinInstance = null
|
||||
const builtinId = root.expandedSection;
|
||||
let builtinInstance = null;
|
||||
|
||||
if (builtinId === "builtin_vpn") {
|
||||
if (widgetModel?.vpnLoader) {
|
||||
widgetModel.vpnLoader.active = true
|
||||
widgetModel.vpnLoader.active = true;
|
||||
}
|
||||
builtinInstance = widgetModel.vpnBuiltinInstance
|
||||
builtinInstance = widgetModel.vpnBuiltinInstance;
|
||||
}
|
||||
if (builtinId === "builtin_cups") {
|
||||
if (widgetModel?.cupsLoader) {
|
||||
widgetModel.cupsLoader.active = true
|
||||
widgetModel.cupsLoader.active = true;
|
||||
}
|
||||
builtinInstance = widgetModel.cupsBuiltinInstance
|
||||
builtinInstance = widgetModel.cupsBuiltinInstance;
|
||||
}
|
||||
|
||||
if (!builtinInstance || !builtinInstance.ccDetailContent) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
pluginDetailLoader.sourceComponent = builtinInstance.ccDetailContent
|
||||
pluginDetailLoader.active = parent.height > 0
|
||||
return
|
||||
pluginDetailLoader.sourceComponent = builtinInstance.ccDetailContent;
|
||||
pluginDetailLoader.active = parent.height > 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.expandedSection.startsWith("plugin_")) {
|
||||
const pluginId = root.expandedSection.replace("plugin_", "")
|
||||
const pluginComponent = PluginService.pluginWidgetComponents[pluginId]
|
||||
const pluginId = root.expandedSection.replace("plugin_", "");
|
||||
const pluginComponent = PluginService.pluginWidgetComponents[pluginId];
|
||||
if (!pluginComponent) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
pluginDetailInstance = pluginComponent.createObject(null)
|
||||
pluginDetailInstance = pluginComponent.createObject(null);
|
||||
if (!pluginDetailInstance || !pluginDetailInstance.ccDetailContent) {
|
||||
if (pluginDetailInstance) {
|
||||
pluginDetailInstance.destroy()
|
||||
pluginDetailInstance = null
|
||||
pluginDetailInstance.destroy();
|
||||
pluginDetailInstance = null;
|
||||
}
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
pluginDetailLoader.sourceComponent = pluginDetailInstance.ccDetailContent
|
||||
pluginDetailLoader.active = parent.height > 0
|
||||
return
|
||||
pluginDetailLoader.sourceComponent = pluginDetailInstance.ccDetailContent;
|
||||
pluginDetailLoader.active = parent.height > 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.expandedSection.startsWith("diskUsage_")) {
|
||||
coreDetailLoader.sourceComponent = diskUsageDetailComponent
|
||||
coreDetailLoader.active = parent.height > 0
|
||||
return
|
||||
coreDetailLoader.sourceComponent = diskUsageDetailComponent;
|
||||
coreDetailLoader.active = parent.height > 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.expandedSection.startsWith("brightnessSlider_")) {
|
||||
coreDetailLoader.sourceComponent = brightnessDetailComponent
|
||||
coreDetailLoader.active = parent.height > 0
|
||||
return
|
||||
coreDetailLoader.sourceComponent = brightnessDetailComponent;
|
||||
coreDetailLoader.active = parent.height > 0;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (root.expandedSection) {
|
||||
case "network":
|
||||
case "wifi": coreDetailLoader.sourceComponent = networkDetailComponent; break
|
||||
case "bluetooth": coreDetailLoader.sourceComponent = bluetoothDetailComponent; break
|
||||
case "audioOutput": coreDetailLoader.sourceComponent = audioOutputDetailComponent; break
|
||||
case "audioInput": coreDetailLoader.sourceComponent = audioInputDetailComponent; break
|
||||
case "battery": coreDetailLoader.sourceComponent = batteryDetailComponent; break
|
||||
default: return
|
||||
case "wifi":
|
||||
coreDetailLoader.sourceComponent = networkDetailComponent;
|
||||
break;
|
||||
case "bluetooth":
|
||||
coreDetailLoader.sourceComponent = bluetoothDetailComponent;
|
||||
break;
|
||||
case "audioOutput":
|
||||
coreDetailLoader.sourceComponent = audioOutputDetailComponent;
|
||||
break;
|
||||
case "audioInput":
|
||||
coreDetailLoader.sourceComponent = audioInputDetailComponent;
|
||||
break;
|
||||
case "battery":
|
||||
coreDetailLoader.sourceComponent = batteryDetailComponent;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
coreDetailLoader.active = parent.height > 0
|
||||
coreDetailLoader.active = parent.height > 0;
|
||||
}
|
||||
|
||||
Component {
|
||||
@@ -183,12 +197,12 @@ Item {
|
||||
id: bluetoothDetailComponent
|
||||
BluetoothDetail {
|
||||
id: bluetoothDetail
|
||||
onShowCodecSelector: function(device) {
|
||||
onShowCodecSelector: function (device) {
|
||||
if (root.bluetoothCodecSelector) {
|
||||
root.bluetoothCodecSelector.show(device)
|
||||
root.bluetoothCodecSelector.codecSelected.connect(function(deviceAddress, codecName) {
|
||||
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName)
|
||||
})
|
||||
root.bluetoothCodecSelector.show(device);
|
||||
root.bluetoothCodecSelector.codecSelected.connect(function (deviceAddress, codecName) {
|
||||
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,6 +237,7 @@ Item {
|
||||
initialDeviceName: root.expandedWidgetData?.deviceName || ""
|
||||
instanceId: root.expandedWidgetData?.instanceId || ""
|
||||
screenName: root.screenName
|
||||
screenModel: root.screenModel
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -82,6 +82,7 @@ DankPopout {
|
||||
|
||||
onShouldBeVisibleChanged: {
|
||||
if (shouldBeVisible) {
|
||||
collapseAll();
|
||||
Qt.callLater(() => {
|
||||
if (NetworkService.activeService) {
|
||||
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled;
|
||||
@@ -179,6 +180,7 @@ DankPopout {
|
||||
bluetoothCodecSelector: bluetoothCodecSelector
|
||||
colorPickerModal: root.colorPickerModal
|
||||
screenName: root.triggerScreen?.name || ""
|
||||
screenModel: root.triggerScreen?.model || ""
|
||||
parentScreen: root.triggerScreen
|
||||
onExpandClicked: (widgetData, globalIndex) => {
|
||||
root.expandedWidgetIndex = globalIndex;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Common
|
||||
@@ -10,8 +9,8 @@ Rectangle {
|
||||
id: root
|
||||
|
||||
property bool hasInputVolumeSliderInCC: {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
return widgets.some(widget => widget.id === "inputVolumeSlider")
|
||||
const widgets = SettingsData.controlCenterWidgets || [];
|
||||
return widgets.some(widget => widget.id === "inputVolumeSlider");
|
||||
}
|
||||
|
||||
implicitHeight: headerRow.height + (hasInputVolumeSliderInCC ? 0 : volumeSlider.height) + audioContent.height + Theme.spacingM
|
||||
@@ -66,7 +65,7 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.muted = !AudioService.source.audio.muted
|
||||
AudioService.source.audio.muted = !AudioService.source.audio.muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,9 +73,10 @@ Rectangle {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
if (!AudioService.source || !AudioService.source.audio) return "mic_off"
|
||||
let muted = AudioService.source.audio.muted
|
||||
return muted ? "mic_off" : "mic"
|
||||
if (!AudioService.source || !AudioService.source.audio)
|
||||
return "mic_off";
|
||||
let muted = AudioService.source.audio.muted;
|
||||
return muted ? "mic_off" : "mic";
|
||||
}
|
||||
size: Theme.iconSize
|
||||
color: AudioService.source && AudioService.source.audio && !AudioService.source.audio.muted && AudioService.source.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
||||
@@ -97,11 +97,11 @@ Rectangle {
|
||||
valueOverride: actualVolumePercent
|
||||
thumbOutlineColor: Theme.surfaceVariant
|
||||
|
||||
onSliderValueChanged: function(newValue) {
|
||||
onSliderValueChanged: function (newValue) {
|
||||
if (AudioService.source && AudioService.source.audio) {
|
||||
AudioService.source.audio.volume = newValue / 100
|
||||
AudioService.source.audio.volume = newValue / 100;
|
||||
if (newValue > 0 && AudioService.source.audio.muted) {
|
||||
AudioService.source.audio.muted = false
|
||||
AudioService.source.audio.muted = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,22 +128,26 @@ Rectangle {
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const nodes = Pipewire.nodes.values.filter(node => {
|
||||
return node.audio && !node.isSink && !node.isStream
|
||||
})
|
||||
const pins = SettingsData.audioInputDevicePins || {}
|
||||
const pinnedName = pins["preferredInput"]
|
||||
|
||||
let sorted = [...nodes]
|
||||
return node.audio && !node.isSink && !node.isStream;
|
||||
});
|
||||
const pins = SettingsData.audioInputDevicePins || {};
|
||||
const pinnedName = pins["preferredInput"];
|
||||
|
||||
let sorted = [...nodes];
|
||||
sorted.sort((a, b) => {
|
||||
// Pinned device first
|
||||
if (a.name === pinnedName && b.name !== pinnedName) return -1
|
||||
if (b.name === pinnedName && a.name !== pinnedName) return 1
|
||||
if (a.name === pinnedName && b.name !== pinnedName)
|
||||
return -1;
|
||||
if (b.name === pinnedName && a.name !== pinnedName)
|
||||
return 1;
|
||||
// Then active device
|
||||
if (a === AudioService.source && b !== AudioService.source) return -1
|
||||
if (b === AudioService.source && a !== AudioService.source) return 1
|
||||
return 0
|
||||
})
|
||||
return sorted
|
||||
if (a === AudioService.source && b !== AudioService.source)
|
||||
return -1;
|
||||
if (b === AudioService.source && a !== AudioService.source)
|
||||
return 1;
|
||||
return 0;
|
||||
});
|
||||
return sorted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,11 +171,11 @@ Rectangle {
|
||||
DankIcon {
|
||||
name: {
|
||||
if (modelData.name.includes("bluez"))
|
||||
return "headset"
|
||||
return "headset";
|
||||
else if (modelData.name.includes("usb"))
|
||||
return "headset"
|
||||
return "headset";
|
||||
else
|
||||
return "mic"
|
||||
return "mic";
|
||||
}
|
||||
size: Theme.iconSize - 4
|
||||
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||
@@ -181,9 +185,9 @@ Rectangle {
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: {
|
||||
const iconWidth = Theme.iconSize
|
||||
const pinButtonWidth = pinInputRow.width + Theme.spacingS * 4 + Theme.spacingM
|
||||
return parent.parent.width - iconWidth - parent.spacing - pinButtonWidth - Theme.spacingM * 2
|
||||
const iconWidth = Theme.iconSize;
|
||||
const pinButtonWidth = pinInputRow.width + Theme.spacingS * 4 + Theme.spacingM;
|
||||
return parent.parent.width - iconWidth - parent.spacing - pinButtonWidth - Theme.spacingM * 2;
|
||||
}
|
||||
|
||||
StyledText {
|
||||
@@ -215,8 +219,8 @@ Rectangle {
|
||||
height: 28
|
||||
radius: height / 2
|
||||
color: {
|
||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name
|
||||
return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
|
||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
|
||||
return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -228,21 +232,21 @@ Rectangle {
|
||||
name: "push_pin"
|
||||
size: 16
|
||||
color: {
|
||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name
|
||||
return isThisDevicePinned ? Theme.primary : Theme.surfaceText
|
||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
|
||||
return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name
|
||||
return isThisDevicePinned ? "Pinned" : "Pin"
|
||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
|
||||
return isThisDevicePinned ? "Pinned" : "Pin";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name
|
||||
return isThisDevicePinned ? Theme.primary : Theme.surfaceText
|
||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
|
||||
return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
@@ -252,16 +256,16 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {}))
|
||||
const isCurrentlyPinned = pins["preferredInput"] === modelData.name
|
||||
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {}));
|
||||
const isCurrentlyPinned = pins["preferredInput"] === modelData.name;
|
||||
|
||||
if (isCurrentlyPinned) {
|
||||
delete pins["preferredInput"]
|
||||
delete pins["preferredInput"];
|
||||
} else {
|
||||
pins["preferredInput"] = modelData.name
|
||||
pins["preferredInput"] = modelData.name;
|
||||
}
|
||||
|
||||
SettingsData.set("audioInputDevicePins", pins)
|
||||
|
||||
SettingsData.set("audioInputDevicePins", pins);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,7 +278,7 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
Pipewire.preferredDefaultAudioSource = modelData
|
||||
Pipewire.preferredDefaultAudioSource = modelData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -282,4 +286,4 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
@@ -11,88 +9,91 @@ Rectangle {
|
||||
property string initialDeviceName: ""
|
||||
property string instanceId: ""
|
||||
property string screenName: ""
|
||||
property string screenModel: ""
|
||||
|
||||
signal deviceNameChanged(string newDeviceName)
|
||||
|
||||
property string currentDeviceName: ""
|
||||
|
||||
function getScreenPinKey() {
|
||||
if (SettingsData.displayNameMode === "model" && screenModel && screenModel.length > 0) {
|
||||
return screenModel;
|
||||
}
|
||||
return screenName || "";
|
||||
}
|
||||
|
||||
function resolveDeviceName() {
|
||||
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
|
||||
return ""
|
||||
return "";
|
||||
}
|
||||
|
||||
if (screenName && screenName.length > 0) {
|
||||
const pins = SettingsData.brightnessDevicePins || {}
|
||||
const pinnedDevice = pins[screenName]
|
||||
const pinKey = getScreenPinKey();
|
||||
if (pinKey.length > 0) {
|
||||
const pins = SettingsData.brightnessDevicePins || {};
|
||||
const pinnedDevice = pins[pinKey];
|
||||
if (pinnedDevice && pinnedDevice.length > 0) {
|
||||
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice)
|
||||
if (found) {
|
||||
return found.name
|
||||
}
|
||||
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice);
|
||||
if (found)
|
||||
return found.name;
|
||||
}
|
||||
}
|
||||
|
||||
if (initialDeviceName && initialDeviceName.length > 0) {
|
||||
const found = DisplayService.devices.find(dev => dev.name === initialDeviceName)
|
||||
if (found) {
|
||||
return found.name
|
||||
}
|
||||
const found = DisplayService.devices.find(dev => dev.name === initialDeviceName);
|
||||
if (found)
|
||||
return found.name;
|
||||
}
|
||||
|
||||
const currentDeviceNameFromService = DisplayService.currentDevice
|
||||
const currentDeviceNameFromService = DisplayService.currentDevice;
|
||||
if (currentDeviceNameFromService) {
|
||||
const found = DisplayService.devices.find(dev => dev.name === currentDeviceNameFromService)
|
||||
if (found) {
|
||||
return found.name
|
||||
}
|
||||
const found = DisplayService.devices.find(dev => dev.name === currentDeviceNameFromService);
|
||||
if (found)
|
||||
return found.name;
|
||||
}
|
||||
|
||||
const backlight = DisplayService.devices.find(d => d.class === "backlight")
|
||||
if (backlight) {
|
||||
return backlight.name
|
||||
}
|
||||
const backlight = DisplayService.devices.find(d => d.class === "backlight");
|
||||
if (backlight)
|
||||
return backlight.name;
|
||||
|
||||
const ddc = DisplayService.devices.find(d => d.class === "ddc")
|
||||
if (ddc) {
|
||||
return ddc.name
|
||||
}
|
||||
const ddc = DisplayService.devices.find(d => d.class === "ddc");
|
||||
if (ddc)
|
||||
return ddc.name;
|
||||
|
||||
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""
|
||||
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : "";
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
currentDeviceName = resolveDeviceName()
|
||||
currentDeviceName = resolveDeviceName();
|
||||
}
|
||||
|
||||
property bool isPinnedToScreen: {
|
||||
if (!screenName || screenName.length === 0) {
|
||||
return false
|
||||
}
|
||||
const pins = SettingsData.brightnessDevicePins || {}
|
||||
return pins[screenName] === currentDeviceName
|
||||
const pinKey = getScreenPinKey();
|
||||
if (!pinKey || pinKey.length === 0)
|
||||
return false;
|
||||
const pins = SettingsData.brightnessDevicePins || {};
|
||||
return pins[pinKey] === currentDeviceName;
|
||||
}
|
||||
|
||||
function togglePinToScreen() {
|
||||
if (!screenName || screenName.length === 0 || !currentDeviceName || currentDeviceName.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}))
|
||||
const pinKey = getScreenPinKey();
|
||||
if (!pinKey || pinKey.length === 0 || !currentDeviceName || currentDeviceName.length === 0)
|
||||
return;
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}));
|
||||
|
||||
if (isPinnedToScreen) {
|
||||
delete pins[screenName]
|
||||
delete pins[pinKey];
|
||||
} else {
|
||||
pins[screenName] = currentDeviceName
|
||||
pins[pinKey] = currentDeviceName;
|
||||
}
|
||||
|
||||
SettingsData.set("brightnessDevicePins", pins)
|
||||
SettingsData.set("brightnessDevicePins", pins);
|
||||
}
|
||||
|
||||
implicitHeight: {
|
||||
if (height > 0) {
|
||||
return height
|
||||
return height;
|
||||
}
|
||||
return brightnessContent.height + Theme.spacingM
|
||||
return brightnessContent.height + Theme.spacingM;
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
@@ -165,7 +166,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: screenName || "Unknown Monitor"
|
||||
text: root.getScreenPinKey() || "Unknown Monitor"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
@@ -216,8 +217,8 @@ Rectangle {
|
||||
required property int index
|
||||
|
||||
property real deviceBrightness: {
|
||||
DisplayService.brightnessVersion
|
||||
return DisplayService.getDeviceBrightness(modelData.name)
|
||||
DisplayService.brightnessVersion;
|
||||
return DisplayService.getDeviceBrightness(modelData.name);
|
||||
}
|
||||
|
||||
width: parent.width
|
||||
@@ -248,19 +249,19 @@ Rectangle {
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
const deviceClass = modelData.class || ""
|
||||
const deviceName = modelData.name || ""
|
||||
const deviceClass = modelData.class || "";
|
||||
const deviceName = modelData.name || "";
|
||||
|
||||
if (deviceClass === "backlight" || deviceClass === "ddc") {
|
||||
if (deviceBrightness <= 33)
|
||||
return "brightness_low"
|
||||
return "brightness_low";
|
||||
if (deviceBrightness <= 66)
|
||||
return "brightness_medium"
|
||||
return "brightness_high"
|
||||
return "brightness_medium";
|
||||
return "brightness_high";
|
||||
} else if (deviceName.includes("kbd")) {
|
||||
return "keyboard"
|
||||
return "keyboard";
|
||||
} else {
|
||||
return "lightbulb"
|
||||
return "lightbulb";
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize
|
||||
@@ -283,12 +284,12 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const name = modelData.name || ""
|
||||
const deviceClass = modelData.class || ""
|
||||
const name = modelData.name || "";
|
||||
const deviceClass = modelData.class || "";
|
||||
if (deviceClass === "backlight") {
|
||||
return name.replace("_", " ").replace(/\b\w/g, c => c.toUpperCase())
|
||||
return name.replace("_", " ").replace(/\b\w/g, c => c.toUpperCase());
|
||||
}
|
||||
return name
|
||||
return name;
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
@@ -307,14 +308,14 @@ Rectangle {
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const deviceClass = modelData.class || ""
|
||||
const deviceClass = modelData.class || "";
|
||||
if (deviceClass === "backlight")
|
||||
return "Backlight device"
|
||||
return "Backlight device";
|
||||
if (deviceClass === "ddc")
|
||||
return "DDC/CI monitor"
|
||||
return "DDC/CI monitor";
|
||||
if (deviceClass === "leds")
|
||||
return "LED device"
|
||||
return deviceClass
|
||||
return "LED device";
|
||||
return deviceClass;
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
@@ -353,9 +354,9 @@ Rectangle {
|
||||
cornerRadius: parent.radius
|
||||
enabled: SessionData.getBrightnessExponent(modelData.name) > 1.0
|
||||
onClicked: {
|
||||
const current = SessionData.getBrightnessExponent(modelData.name)
|
||||
const newValue = Math.max(1.0, Math.round((current - 0.1) * 10) / 10)
|
||||
SessionData.setBrightnessExponent(modelData.name, newValue)
|
||||
const current = SessionData.getBrightnessExponent(modelData.name);
|
||||
const newValue = Math.max(1.0, Math.round((current - 0.1) * 10) / 10);
|
||||
SessionData.setBrightnessExponent(modelData.name, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -395,9 +396,9 @@ Rectangle {
|
||||
cornerRadius: parent.radius
|
||||
enabled: SessionData.getBrightnessExponent(modelData.name) < 2.5
|
||||
onClicked: {
|
||||
const current = SessionData.getBrightnessExponent(modelData.name)
|
||||
const newValue = Math.min(2.5, Math.round((current + 0.1) * 10) / 10)
|
||||
SessionData.setBrightnessExponent(modelData.name, newValue)
|
||||
const current = SessionData.getBrightnessExponent(modelData.name);
|
||||
const newValue = Math.min(2.5, Math.round((current + 0.1) * 10) / 10);
|
||||
SessionData.setBrightnessExponent(modelData.name, newValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -433,8 +434,8 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
const currentState = SessionData.getBrightnessExponential(modelData.name)
|
||||
SessionData.setBrightnessExponential(modelData.name, !currentState)
|
||||
const currentState = SessionData.getBrightnessExponential(modelData.name);
|
||||
SessionData.setBrightnessExponential(modelData.name, !currentState);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -447,15 +448,16 @@ Rectangle {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (screenName && screenName.length > 0 && modelData.name !== currentDeviceName) {
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}))
|
||||
if (pins[screenName]) {
|
||||
delete pins[screenName]
|
||||
SettingsData.set("brightnessDevicePins", pins)
|
||||
const pinKey = root.getScreenPinKey();
|
||||
if (pinKey.length > 0 && modelData.name !== currentDeviceName) {
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}));
|
||||
if (pins[pinKey]) {
|
||||
delete pins[pinKey];
|
||||
SettingsData.set("brightnessDevicePins", pins);
|
||||
}
|
||||
}
|
||||
currentDeviceName = modelData.name
|
||||
deviceNameChanged(modelData.name)
|
||||
currentDeviceName = modelData.name;
|
||||
deviceNameChanged(modelData.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,15 +11,15 @@ Rectangle {
|
||||
|
||||
implicitHeight: {
|
||||
if (height > 0) {
|
||||
return height
|
||||
return height;
|
||||
}
|
||||
if (NetworkService.wifiToggling) {
|
||||
return headerRow.height + wifiToggleContent.height + Theme.spacingM
|
||||
return headerRow.height + wifiToggleContent.height + Theme.spacingM;
|
||||
}
|
||||
if (NetworkService.wifiEnabled) {
|
||||
return headerRow.height + wifiContent.height + Theme.spacingM
|
||||
return headerRow.height + wifiContent.height + Theme.spacingM;
|
||||
}
|
||||
return headerRow.height + wifiOffContent.height + Theme.spacingM
|
||||
return headerRow.height + wifiOffContent.height + Theme.spacingM;
|
||||
}
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
@@ -27,35 +27,35 @@ Rectangle {
|
||||
border.width: 0
|
||||
|
||||
Component.onCompleted: {
|
||||
NetworkService.addRef()
|
||||
NetworkService.addRef();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
NetworkService.removeRef()
|
||||
NetworkService.removeRef();
|
||||
}
|
||||
|
||||
property int currentPreferenceIndex: {
|
||||
if (DMSService.apiVersion < 5) {
|
||||
return 1
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (NetworkService.backend !== "networkmanager" || DMSService.apiVersion <= 10) {
|
||||
return 1
|
||||
return 1;
|
||||
}
|
||||
|
||||
const pref = NetworkService.userPreference
|
||||
const status = NetworkService.networkStatus
|
||||
let index = 1
|
||||
const pref = NetworkService.userPreference;
|
||||
const status = NetworkService.networkStatus;
|
||||
let index = 1;
|
||||
|
||||
if (pref === "ethernet") {
|
||||
index = 0
|
||||
index = 0;
|
||||
} else if (pref === "wifi") {
|
||||
index = 1
|
||||
index = 1;
|
||||
} else {
|
||||
index = status === "ethernet" ? 0 : 1
|
||||
index = status === "ethernet" ? 0 : 1;
|
||||
}
|
||||
|
||||
return index
|
||||
return index;
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -78,28 +78,56 @@ Rectangle {
|
||||
}
|
||||
|
||||
Item {
|
||||
width: Math.max(0, parent.width - headerText.implicitWidth - preferenceControls.width - Theme.spacingM)
|
||||
height: parent.height
|
||||
height: 1
|
||||
width: parent.width - headerText.width - rightControls.width
|
||||
}
|
||||
|
||||
DankButtonGroup {
|
||||
id: preferenceControls
|
||||
Row {
|
||||
id: rightControls
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
|
||||
spacing: Theme.spacingS
|
||||
|
||||
model: ["Ethernet", "WiFi"]
|
||||
currentIndex: currentPreferenceIndex
|
||||
selectionMode: "single"
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected) return
|
||||
console.log("NetworkDetail: Setting preference to", index === 0 ? "ethernet" : "wifi")
|
||||
NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi")
|
||||
DankDropdown {
|
||||
id: wifiDeviceDropdown
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: currentPreferenceIndex === 1 && (NetworkService.wifiDevices?.length ?? 0) > 1
|
||||
compactMode: true
|
||||
dropdownWidth: 120
|
||||
popupWidth: 160
|
||||
alignPopupRight: true
|
||||
|
||||
options: {
|
||||
const devices = NetworkService.wifiDevices;
|
||||
if (!devices || devices.length === 0)
|
||||
return [I18n.tr("Auto")];
|
||||
return [I18n.tr("Auto")].concat(devices.map(d => d.name));
|
||||
}
|
||||
|
||||
currentValue: NetworkService.wifiDeviceOverride || I18n.tr("Auto")
|
||||
|
||||
onValueChanged: value => {
|
||||
const deviceName = value === I18n.tr("Auto") ? "" : value;
|
||||
NetworkService.setWifiDeviceOverride(deviceName);
|
||||
}
|
||||
}
|
||||
|
||||
DankButtonGroup {
|
||||
id: preferenceControls
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
|
||||
|
||||
model: ["Ethernet", "WiFi"]
|
||||
currentIndex: currentPreferenceIndex
|
||||
selectionMode: "single"
|
||||
onSelectionChanged: (index, selected) => {
|
||||
if (!selected)
|
||||
return;
|
||||
NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Item {
|
||||
id: wifiToggleContent
|
||||
anchors.top: headerRow.bottom
|
||||
@@ -194,7 +222,6 @@ Rectangle {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: NetworkService.toggleWifiRadio()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,15 +246,17 @@ Rectangle {
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const currentUuid = NetworkService.ethernetConnectionUuid
|
||||
const networks = NetworkService.wiredConnections
|
||||
let sorted = [...networks]
|
||||
const currentUuid = NetworkService.ethernetConnectionUuid;
|
||||
const networks = NetworkService.wiredConnections;
|
||||
let sorted = [...networks];
|
||||
sorted.sort((a, b) => {
|
||||
if (a.isActive && !b.isActive) return -1
|
||||
if (!a.isActive && b.isActive) return 1
|
||||
return a.id.localeCompare(b.id)
|
||||
})
|
||||
return sorted
|
||||
if (a.isActive && !b.isActive)
|
||||
return -1;
|
||||
if (!a.isActive && b.isActive)
|
||||
return 1;
|
||||
return a.id.localeCompare(b.id);
|
||||
});
|
||||
return sorted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,12 +308,12 @@ Rectangle {
|
||||
buttonSize: 28
|
||||
onClicked: {
|
||||
if (wiredNetworkContextMenu.visible) {
|
||||
wiredNetworkContextMenu.close()
|
||||
wiredNetworkContextMenu.close();
|
||||
} else {
|
||||
wiredNetworkContextMenu.currentID = modelData.id
|
||||
wiredNetworkContextMenu.currentUUID = modelData.uuid
|
||||
wiredNetworkContextMenu.currentConnected = modelData.isActive
|
||||
wiredNetworkContextMenu.popup(wiredOptionsButton, -wiredNetworkContextMenu.width + wiredOptionsButton.width, wiredOptionsButton.height + Theme.spacingXS)
|
||||
wiredNetworkContextMenu.currentID = modelData.id;
|
||||
wiredNetworkContextMenu.currentUUID = modelData.uuid;
|
||||
wiredNetworkContextMenu.currentConnected = modelData.isActive;
|
||||
wiredNetworkContextMenu.popup(wiredOptionsButton, -wiredNetworkContextMenu.width + wiredOptionsButton.width, wiredOptionsButton.height + Theme.spacingXS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -295,14 +324,13 @@ Rectangle {
|
||||
anchors.rightMargin: wiredOptionsButton.width + Theme.spacingS
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: function(event) {
|
||||
onClicked: function (event) {
|
||||
if (modelData.uuid !== NetworkService.ethernetConnectionUuid) {
|
||||
NetworkService.connectToSpecificWiredConfig(modelData.uuid)
|
||||
NetworkService.connectToSpecificWiredConfig(modelData.uuid);
|
||||
}
|
||||
event.accepted = true
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -343,7 +371,7 @@ Rectangle {
|
||||
|
||||
onTriggered: {
|
||||
if (!networkContextMenu.currentConnected) {
|
||||
NetworkService.connectToSpecificWiredConfig(wiredNetworkContextMenu.currentUUID)
|
||||
NetworkService.connectToSpecificWiredConfig(wiredNetworkContextMenu.currentUUID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -366,8 +394,8 @@ Rectangle {
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
let networkData = NetworkService.getWiredNetworkInfo(wiredNetworkContextMenu.currentUUID)
|
||||
networkWiredInfoModal.showNetworkInfo(wiredNetworkContextMenu.currentID, networkData)
|
||||
let networkData = NetworkService.getWiredNetworkInfo(wiredNetworkContextMenu.currentUUID);
|
||||
networkWiredInfoModal.showNetworkInfo(wiredNetworkContextMenu.currentID, networkData);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,26 +444,30 @@ Rectangle {
|
||||
Repeater {
|
||||
model: ScriptModel {
|
||||
values: {
|
||||
const ssid = NetworkService.currentWifiSSID
|
||||
const networks = NetworkService.wifiNetworks
|
||||
const pins = SettingsData.wifiNetworkPins || {}
|
||||
const pinnedSSID = pins["preferredWifi"]
|
||||
|
||||
let sorted = [...networks]
|
||||
const ssid = NetworkService.currentWifiSSID;
|
||||
const networks = NetworkService.wifiNetworks;
|
||||
const pins = SettingsData.wifiNetworkPins || {};
|
||||
const pinnedSSID = pins["preferredWifi"];
|
||||
|
||||
let sorted = [...networks];
|
||||
sorted.sort((a, b) => {
|
||||
// Pinned network first
|
||||
if (a.ssid === pinnedSSID && b.ssid !== pinnedSSID) return -1
|
||||
if (b.ssid === pinnedSSID && a.ssid !== pinnedSSID) return 1
|
||||
if (a.ssid === pinnedSSID && b.ssid !== pinnedSSID)
|
||||
return -1;
|
||||
if (b.ssid === pinnedSSID && a.ssid !== pinnedSSID)
|
||||
return 1;
|
||||
// Then currently connected
|
||||
if (a.ssid === ssid) return -1
|
||||
if (b.ssid === ssid) return 1
|
||||
if (a.ssid === ssid)
|
||||
return -1;
|
||||
if (b.ssid === ssid)
|
||||
return 1;
|
||||
// Then by signal strength
|
||||
return b.signal - a.signal
|
||||
})
|
||||
return b.signal - a.signal;
|
||||
});
|
||||
if (!wifiContent.menuOpen) {
|
||||
wifiContent.frozenNetworks = sorted
|
||||
wifiContent.frozenNetworks = sorted;
|
||||
}
|
||||
return wifiContent.menuOpen ? wifiContent.frozenNetworks : sorted
|
||||
return wifiContent.menuOpen ? wifiContent.frozenNetworks : sorted;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -458,10 +490,12 @@ Rectangle {
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
let strength = modelData.signal || 0
|
||||
if (strength >= 50) return "wifi"
|
||||
if (strength >= 25) return "wifi_2_bar"
|
||||
return "wifi_1_bar"
|
||||
let strength = modelData.signal || 0;
|
||||
if (strength >= 50)
|
||||
return "wifi";
|
||||
if (strength >= 25)
|
||||
return "wifi_2_bar";
|
||||
return "wifi_1_bar";
|
||||
}
|
||||
size: Theme.iconSize - 4
|
||||
color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Theme.surfaceText
|
||||
@@ -515,16 +549,16 @@ Rectangle {
|
||||
buttonSize: 28
|
||||
onClicked: {
|
||||
if (networkContextMenu.visible) {
|
||||
networkContextMenu.close()
|
||||
networkContextMenu.close();
|
||||
} else {
|
||||
wifiContent.menuOpen = true
|
||||
networkContextMenu.currentSSID = modelData.ssid
|
||||
networkContextMenu.currentSecured = modelData.secured
|
||||
networkContextMenu.currentConnected = modelData.ssid === NetworkService.currentWifiSSID
|
||||
networkContextMenu.currentSaved = modelData.saved
|
||||
networkContextMenu.currentSignal = modelData.signal
|
||||
networkContextMenu.currentAutoconnect = modelData.autoconnect || false
|
||||
networkContextMenu.popup(optionsButton, -networkContextMenu.width + optionsButton.width, optionsButton.height + Theme.spacingXS)
|
||||
wifiContent.menuOpen = true;
|
||||
networkContextMenu.currentSSID = modelData.ssid;
|
||||
networkContextMenu.currentSecured = modelData.secured;
|
||||
networkContextMenu.currentConnected = modelData.ssid === NetworkService.currentWifiSSID;
|
||||
networkContextMenu.currentSaved = modelData.saved;
|
||||
networkContextMenu.currentSignal = modelData.signal;
|
||||
networkContextMenu.currentAutoconnect = modelData.autoconnect || false;
|
||||
networkContextMenu.popup(optionsButton, -networkContextMenu.width + optionsButton.width, optionsButton.height + Theme.spacingXS);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -537,8 +571,8 @@ Rectangle {
|
||||
height: 28
|
||||
radius: height / 2
|
||||
color: {
|
||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid
|
||||
return isThisNetworkPinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05)
|
||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
|
||||
return isThisNetworkPinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
|
||||
}
|
||||
|
||||
Row {
|
||||
@@ -550,21 +584,21 @@ Rectangle {
|
||||
name: "push_pin"
|
||||
size: 16
|
||||
color: {
|
||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid
|
||||
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText
|
||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
|
||||
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid
|
||||
return isThisNetworkPinned ? "Pinned" : "Pin"
|
||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
|
||||
return isThisNetworkPinned ? "Pinned" : "Pin";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: {
|
||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid
|
||||
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText
|
||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
|
||||
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText;
|
||||
}
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
@@ -574,16 +608,16 @@ Rectangle {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}))
|
||||
const isCurrentlyPinned = pins["preferredWifi"] === modelData.ssid
|
||||
|
||||
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}));
|
||||
const isCurrentlyPinned = pins["preferredWifi"] === modelData.ssid;
|
||||
|
||||
if (isCurrentlyPinned) {
|
||||
delete pins["preferredWifi"]
|
||||
delete pins["preferredWifi"];
|
||||
} else {
|
||||
pins["preferredWifi"] = modelData.ssid
|
||||
pins["preferredWifi"] = modelData.ssid;
|
||||
}
|
||||
|
||||
SettingsData.set("wifiNetworkPins", pins)
|
||||
|
||||
SettingsData.set("wifiNetworkPins", pins);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -594,22 +628,21 @@ Rectangle {
|
||||
anchors.rightMargin: optionsButton.width + Theme.spacingM + Theme.spacingS + pinWifiRow.width + Theme.spacingS * 4
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: function(event) {
|
||||
onClicked: function (event) {
|
||||
if (modelData.ssid !== NetworkService.currentWifiSSID) {
|
||||
if (modelData.secured && !modelData.saved) {
|
||||
if (DMSService.apiVersion >= 7) {
|
||||
NetworkService.connectToWifi(modelData.ssid)
|
||||
NetworkService.connectToWifi(modelData.ssid);
|
||||
} else if (PopoutService.wifiPasswordModal) {
|
||||
PopoutService.wifiPasswordModal.show(modelData.ssid)
|
||||
PopoutService.wifiPasswordModal.show(modelData.ssid);
|
||||
}
|
||||
} else {
|
||||
NetworkService.connectToWifi(modelData.ssid)
|
||||
NetworkService.connectToWifi(modelData.ssid);
|
||||
}
|
||||
}
|
||||
event.accepted = true
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -628,7 +661,7 @@ Rectangle {
|
||||
property bool currentAutoconnect: false
|
||||
|
||||
onClosed: {
|
||||
wifiContent.menuOpen = false
|
||||
wifiContent.menuOpen = false;
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
@@ -657,16 +690,16 @@ Rectangle {
|
||||
|
||||
onTriggered: {
|
||||
if (networkContextMenu.currentConnected) {
|
||||
NetworkService.disconnectWifi()
|
||||
NetworkService.disconnectWifi();
|
||||
} else {
|
||||
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
|
||||
if (DMSService.apiVersion >= 7) {
|
||||
NetworkService.connectToWifi(networkContextMenu.currentSSID)
|
||||
NetworkService.connectToWifi(networkContextMenu.currentSSID);
|
||||
} else if (PopoutService.wifiPasswordModal) {
|
||||
PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID)
|
||||
PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID);
|
||||
}
|
||||
} else {
|
||||
NetworkService.connectToWifi(networkContextMenu.currentSSID)
|
||||
NetworkService.connectToWifi(networkContextMenu.currentSSID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -690,8 +723,8 @@ Rectangle {
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID)
|
||||
networkInfoModal.showNetworkInfo(networkContextMenu.currentSSID, networkData)
|
||||
let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID);
|
||||
networkInfoModal.showNetworkInfo(networkContextMenu.currentSSID, networkData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,7 +747,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
NetworkService.setWifiAutoconnect(networkContextMenu.currentSSID, !networkContextMenu.currentAutoconnect)
|
||||
NetworkService.setWifiAutoconnect(networkContextMenu.currentSSID, !networkContextMenu.currentAutoconnect);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,7 +770,7 @@ Rectangle {
|
||||
}
|
||||
|
||||
onTriggered: {
|
||||
NetworkService.forgetWifiNetwork(networkContextMenu.currentSSID)
|
||||
NetworkService.forgetWifiNetwork(networkContextMenu.currentSSID);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -749,4 +782,4 @@ Rectangle {
|
||||
NetworkWiredInfoModal {
|
||||
id: networkWiredInfoModal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,17 +17,17 @@ QtObject {
|
||||
}
|
||||
|
||||
onItemChanged: {
|
||||
root.vpnBuiltinInstance = item
|
||||
root.vpnBuiltinInstance = item;
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onControlCenterWidgetsChanged() {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
const hasVpnWidget = widgets.some(w => w.id === "builtin_vpn")
|
||||
const widgets = SettingsData.controlCenterWidgets || [];
|
||||
const hasVpnWidget = widgets.some(w => w.id === "builtin_vpn");
|
||||
if (!hasVpnWidget && vpnLoader.active) {
|
||||
console.log("VpnWidget: No VPN widget in control center, deactivating loader")
|
||||
vpnLoader.active = false
|
||||
console.log("VpnWidget: No VPN widget in control center, deactivating loader");
|
||||
vpnLoader.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,35 +40,36 @@ QtObject {
|
||||
}
|
||||
|
||||
onItemChanged: {
|
||||
root.cupsBuiltinInstance = item
|
||||
root.cupsBuiltinInstance = item;
|
||||
if (item && !DMSService.activeSubscriptions.includes("cups") && !DMSService.activeSubscriptions.includes("all")) {
|
||||
DMSService.addSubscription("cups")
|
||||
DMSService.addSubscription("cups");
|
||||
}
|
||||
}
|
||||
|
||||
onActiveChanged: {
|
||||
if (!active) {
|
||||
if (DMSService.activeSubscriptions.includes("cups")) {
|
||||
DMSService.removeSubscription("cups")
|
||||
DMSService.removeSubscription("cups");
|
||||
}
|
||||
root.cupsBuiltinInstance = null
|
||||
root.cupsBuiltinInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: SettingsData
|
||||
function onControlCenterWidgetsChanged() {
|
||||
const widgets = SettingsData.controlCenterWidgets || []
|
||||
const hasCupsWidget = widgets.some(w => w.id === "builtin_cups")
|
||||
const widgets = SettingsData.controlCenterWidgets || [];
|
||||
const hasCupsWidget = widgets.some(w => w.id === "builtin_cups");
|
||||
if (!hasCupsWidget && cupsLoader.active) {
|
||||
console.log("CupsWidget: No CUPS widget in control center, deactivating loader")
|
||||
cupsLoader.active = false
|
||||
console.log("CupsWidget: No CUPS widget in control center, deactivating loader");
|
||||
cupsLoader.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
readonly property var coreWidgetDefinitions: [{
|
||||
readonly property var coreWidgetDefinitions: [
|
||||
{
|
||||
"id": "nightMode",
|
||||
"text": "Night Mode",
|
||||
"description": "Blue light filter",
|
||||
@@ -76,28 +77,32 @@ QtObject {
|
||||
"type": "toggle",
|
||||
"enabled": DisplayService.automationAvailable,
|
||||
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "darkMode",
|
||||
"text": "Dark Mode",
|
||||
"description": "System theme toggle",
|
||||
"icon": "contrast",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "doNotDisturb",
|
||||
"text": "Do Not Disturb",
|
||||
"description": "Block notifications",
|
||||
"icon": "do_not_disturb_on",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "idleInhibitor",
|
||||
"text": "Keep Awake",
|
||||
"description": "Prevent screen timeout",
|
||||
"icon": "motion_sensor_active",
|
||||
"type": "toggle",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "wifi",
|
||||
"text": "Network",
|
||||
"description": "Wi-Fi and Ethernet connection",
|
||||
@@ -105,7 +110,8 @@ QtObject {
|
||||
"type": "connection",
|
||||
"enabled": NetworkService.wifiAvailable,
|
||||
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "bluetooth",
|
||||
"text": "Bluetooth",
|
||||
"description": "Device connections",
|
||||
@@ -113,28 +119,32 @@ QtObject {
|
||||
"type": "connection",
|
||||
"enabled": BluetoothService.available,
|
||||
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "audioOutput",
|
||||
"text": "Audio Output",
|
||||
"description": "Speaker settings",
|
||||
"icon": "volume_up",
|
||||
"type": "connection",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "audioInput",
|
||||
"text": "Audio Input",
|
||||
"description": "Microphone settings",
|
||||
"icon": "mic",
|
||||
"type": "connection",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "volumeSlider",
|
||||
"text": "Volume Slider",
|
||||
"description": "Audio volume control",
|
||||
"icon": "volume_up",
|
||||
"type": "slider",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "brightnessSlider",
|
||||
"text": "Brightness Slider",
|
||||
"description": "Display brightness control",
|
||||
@@ -143,21 +153,24 @@ QtObject {
|
||||
"enabled": DisplayService.brightnessAvailable,
|
||||
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined,
|
||||
"allowMultiple": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "inputVolumeSlider",
|
||||
"text": "Input Volume Slider",
|
||||
"description": "Microphone volume control",
|
||||
"icon": "mic",
|
||||
"type": "slider",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "battery",
|
||||
"text": "Battery",
|
||||
"description": "Battery and power management",
|
||||
"icon": "battery_std",
|
||||
"type": "action",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "diskUsage",
|
||||
"text": "Disk Usage",
|
||||
"description": "Filesystem usage monitoring",
|
||||
@@ -166,14 +179,16 @@ QtObject {
|
||||
"enabled": DgopService.dgopAvailable,
|
||||
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
|
||||
"allowMultiple": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "colorPicker",
|
||||
"text": "Color Picker",
|
||||
"description": "Choose colors from palette",
|
||||
"icon": "palette",
|
||||
"type": "action",
|
||||
"enabled": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "builtin_vpn",
|
||||
"text": "VPN",
|
||||
"description": "VPN connections",
|
||||
@@ -182,7 +197,8 @@ QtObject {
|
||||
"enabled": DMSNetworkService.available,
|
||||
"warning": !DMSNetworkService.available ? "VPN not available" : undefined,
|
||||
"isBuiltinPlugin": true
|
||||
}, {
|
||||
},
|
||||
{
|
||||
"id": "builtin_cups",
|
||||
"text": "Printers",
|
||||
"description": "Print Server Management",
|
||||
@@ -191,78 +207,79 @@ QtObject {
|
||||
"enabled": CupsService.available,
|
||||
"warning": !CupsService.available ? "CUPS not available" : undefined,
|
||||
"isBuiltinPlugin": true
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
function getPluginWidgets() {
|
||||
const plugins = []
|
||||
const loadedPlugins = PluginService.getLoadedPlugins()
|
||||
const plugins = [];
|
||||
const loadedPlugins = PluginService.getLoadedPlugins();
|
||||
|
||||
for (var i = 0; i < loadedPlugins.length; i++) {
|
||||
const plugin = loadedPlugins[i]
|
||||
const plugin = loadedPlugins[i];
|
||||
|
||||
if (plugin.type === "daemon") {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
const pluginComponent = PluginService.pluginWidgetComponents[plugin.id]
|
||||
const pluginComponent = PluginService.pluginWidgetComponents[plugin.id];
|
||||
if (!pluginComponent || typeof pluginComponent.createObject !== 'function') {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
const tempInstance = pluginComponent.createObject(null)
|
||||
const tempInstance = pluginComponent.createObject(null);
|
||||
if (!tempInstance) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
const hasCCWidget = tempInstance.ccWidgetIcon && tempInstance.ccWidgetIcon.length > 0
|
||||
tempInstance.destroy()
|
||||
const hasCCWidget = tempInstance.ccWidgetIcon && tempInstance.ccWidgetIcon.length > 0;
|
||||
tempInstance.destroy();
|
||||
|
||||
if (!hasCCWidget) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
plugins.push({
|
||||
"id": "plugin_" + plugin.id,
|
||||
"pluginId": plugin.id,
|
||||
"text": plugin.name || "Plugin",
|
||||
"description": plugin.description || "",
|
||||
"icon": plugin.icon || "extension",
|
||||
"type": "plugin",
|
||||
"enabled": true,
|
||||
"isPlugin": true
|
||||
})
|
||||
"id": "plugin_" + plugin.id,
|
||||
"pluginId": plugin.id,
|
||||
"text": plugin.name || "Plugin",
|
||||
"description": plugin.description || "",
|
||||
"icon": plugin.icon || "extension",
|
||||
"type": "plugin",
|
||||
"enabled": true,
|
||||
"isPlugin": true
|
||||
});
|
||||
}
|
||||
|
||||
return plugins
|
||||
return plugins;
|
||||
}
|
||||
|
||||
readonly property var baseWidgetDefinitions: coreWidgetDefinitions
|
||||
|
||||
function getWidgetForId(widgetId) {
|
||||
return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId)
|
||||
return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId);
|
||||
}
|
||||
|
||||
function addWidget(widgetId) {
|
||||
WidgetUtils.addWidget(widgetId)
|
||||
WidgetUtils.addWidget(widgetId);
|
||||
}
|
||||
|
||||
function removeWidget(index) {
|
||||
WidgetUtils.removeWidget(index)
|
||||
WidgetUtils.removeWidget(index);
|
||||
}
|
||||
|
||||
function toggleWidgetSize(index) {
|
||||
WidgetUtils.toggleWidgetSize(index)
|
||||
WidgetUtils.toggleWidgetSize(index);
|
||||
}
|
||||
|
||||
function moveWidget(fromIndex, toIndex) {
|
||||
WidgetUtils.moveWidget(fromIndex, toIndex)
|
||||
WidgetUtils.moveWidget(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
function resetToDefault() {
|
||||
WidgetUtils.resetToDefault()
|
||||
WidgetUtils.resetToDefault();
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
WidgetUtils.clearAll()
|
||||
WidgetUtils.clearAll();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1144,6 +1144,8 @@ Item {
|
||||
return controlCenterLoader.item;
|
||||
}
|
||||
parentScreen: barWindow.screen
|
||||
screenName: barWindow.screen?.name || ""
|
||||
screenModel: barWindow.screen?.model || ""
|
||||
widgetData: parent.widgetData
|
||||
|
||||
Component.onCompleted: {
|
||||
|
||||
@@ -155,35 +155,56 @@ Loader {
|
||||
}
|
||||
|
||||
onLoaded: {
|
||||
if (item) {
|
||||
contentItemReady(item);
|
||||
if (axis && "isVertical" in item) {
|
||||
try {
|
||||
item.isVertical = axis.isVertical;
|
||||
} catch (e) {}
|
||||
}
|
||||
if (!item)
|
||||
return;
|
||||
|
||||
if (item.pluginService !== undefined) {
|
||||
var parts = widgetId.split(":");
|
||||
var pluginId = parts[0];
|
||||
var variantId = parts.length > 1 ? parts[1] : null;
|
||||
contentItemReady(item);
|
||||
|
||||
if (item.pluginId !== undefined) {
|
||||
item.pluginId = pluginId;
|
||||
}
|
||||
if (item.variantId !== undefined) {
|
||||
item.variantId = variantId;
|
||||
}
|
||||
if (item.variantData !== undefined && variantId) {
|
||||
item.variantData = PluginService.getPluginVariantData(pluginId, variantId);
|
||||
}
|
||||
item.pluginService = PluginService;
|
||||
}
|
||||
|
||||
if (item.popoutService !== undefined) {
|
||||
item.popoutService = PopoutService;
|
||||
}
|
||||
if (axis && "isVertical" in item) {
|
||||
try {
|
||||
item.isVertical = axis.isVertical;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (item.pluginService !== undefined) {
|
||||
var parts = widgetId.split(":");
|
||||
var pluginId = parts[0];
|
||||
var variantId = parts.length > 1 ? parts[1] : null;
|
||||
|
||||
if (item.pluginId !== undefined)
|
||||
item.pluginId = pluginId;
|
||||
if (item.variantId !== undefined)
|
||||
item.variantId = variantId;
|
||||
if (item.variantData !== undefined && variantId)
|
||||
item.variantData = PluginService.getPluginVariantData(pluginId, variantId);
|
||||
item.pluginService = PluginService;
|
||||
}
|
||||
|
||||
if (item.popoutService !== undefined)
|
||||
item.popoutService = PopoutService;
|
||||
|
||||
registerWidgetIfEligible();
|
||||
}
|
||||
|
||||
Component.onDestruction: {
|
||||
unregisterWidget();
|
||||
}
|
||||
|
||||
function registerWidgetIfEligible() {
|
||||
if (!item || !widgetId || !parentScreen?.name)
|
||||
return;
|
||||
|
||||
const hasPopout = item.popoutTarget !== undefined || typeof item.triggerPopout === "function" || typeof item.clicked === "function";
|
||||
if (!hasPopout)
|
||||
return;
|
||||
|
||||
BarWidgetService.registerWidget(widgetId, parentScreen.name, item);
|
||||
}
|
||||
|
||||
function unregisterWidget() {
|
||||
if (!widgetId || !parentScreen?.name)
|
||||
return;
|
||||
BarWidgetService.unregisterWidget(widgetId, parentScreen.name);
|
||||
}
|
||||
|
||||
function getWidgetComponent(widgetId, components) {
|
||||
|
||||
@@ -10,9 +10,138 @@ BasePill {
|
||||
property bool isActive: false
|
||||
property var popoutTarget: null
|
||||
property var widgetData: null
|
||||
property string screenName: ""
|
||||
property string screenModel: ""
|
||||
property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon
|
||||
property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon
|
||||
property bool showAudioIcon: SettingsData.controlCenterShowAudioIcon
|
||||
property bool showVpnIcon: SettingsData.controlCenterShowVpnIcon
|
||||
property bool showBrightnessIcon: SettingsData.controlCenterShowBrightnessIcon
|
||||
property bool showMicIcon: SettingsData.controlCenterShowMicIcon
|
||||
property bool showBatteryIcon: SettingsData.controlCenterShowBatteryIcon
|
||||
property bool showPrinterIcon: SettingsData.controlCenterShowPrinterIcon
|
||||
|
||||
function getNetworkIconName() {
|
||||
if (NetworkService.wifiToggling)
|
||||
return "sync";
|
||||
switch (NetworkService.networkStatus) {
|
||||
case "ethernet":
|
||||
return "lan";
|
||||
case "vpn":
|
||||
return NetworkService.ethernetConnected ? "lan" : NetworkService.wifiSignalIcon;
|
||||
default:
|
||||
return NetworkService.wifiSignalIcon;
|
||||
}
|
||||
}
|
||||
|
||||
function getNetworkIconColor() {
|
||||
if (NetworkService.wifiToggling)
|
||||
return Theme.primary;
|
||||
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton;
|
||||
}
|
||||
|
||||
function getVolumeIconName() {
|
||||
if (!AudioService.sink?.audio)
|
||||
return "volume_up";
|
||||
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0)
|
||||
return "volume_off";
|
||||
if (AudioService.sink.audio.volume * 100 < 33)
|
||||
return "volume_down";
|
||||
return "volume_up";
|
||||
}
|
||||
|
||||
function getMicIconName() {
|
||||
if (!AudioService.source?.audio)
|
||||
return "mic";
|
||||
if (AudioService.source.audio.muted || AudioService.source.audio.volume === 0)
|
||||
return "mic_off";
|
||||
return "mic";
|
||||
}
|
||||
|
||||
function getMicIconColor() {
|
||||
if (!AudioService.source?.audio)
|
||||
return Theme.outlineButton;
|
||||
if (AudioService.source.audio.muted || AudioService.source.audio.volume === 0)
|
||||
return Theme.outlineButton;
|
||||
return Theme.widgetIconColor;
|
||||
}
|
||||
|
||||
function getBrightnessIconName() {
|
||||
const deviceName = getPinnedBrightnessDevice();
|
||||
if (!deviceName)
|
||||
return "brightness_medium";
|
||||
const level = DisplayService.getDeviceBrightness(deviceName);
|
||||
if (level <= 33)
|
||||
return "brightness_low";
|
||||
if (level <= 66)
|
||||
return "brightness_medium";
|
||||
return "brightness_high";
|
||||
}
|
||||
|
||||
function getScreenPinKey() {
|
||||
if (SettingsData.displayNameMode === "model" && root.screenModel && root.screenModel.length > 0) {
|
||||
return root.screenModel;
|
||||
}
|
||||
return root.screenName || "";
|
||||
}
|
||||
|
||||
function getPinnedBrightnessDevice() {
|
||||
const pinKey = getScreenPinKey();
|
||||
if (!pinKey)
|
||||
return "";
|
||||
const pins = SettingsData.brightnessDevicePins || {};
|
||||
return pins[pinKey] || "";
|
||||
}
|
||||
|
||||
function hasPinnedBrightnessDevice() {
|
||||
return getPinnedBrightnessDevice().length > 0;
|
||||
}
|
||||
|
||||
function handleVolumeWheel(delta) {
|
||||
if (!AudioService.sink?.audio)
|
||||
return;
|
||||
const currentVolume = AudioService.sink.audio.volume * 100;
|
||||
const newVolume = delta > 0 ? Math.min(100, currentVolume + 5) : Math.max(0, currentVolume - 5);
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
AudioService.playVolumeChangeSoundIfEnabled();
|
||||
}
|
||||
|
||||
function handleMicWheel(delta) {
|
||||
if (!AudioService.source?.audio)
|
||||
return;
|
||||
const currentVolume = AudioService.source.audio.volume * 100;
|
||||
const newVolume = delta > 0 ? Math.min(100, currentVolume + 5) : Math.max(0, currentVolume - 5);
|
||||
AudioService.source.audio.muted = false;
|
||||
AudioService.source.audio.volume = newVolume / 100;
|
||||
}
|
||||
|
||||
function handleBrightnessWheel(delta) {
|
||||
const deviceName = getPinnedBrightnessDevice();
|
||||
if (!deviceName)
|
||||
return;
|
||||
const currentBrightness = DisplayService.getDeviceBrightness(deviceName);
|
||||
const newBrightness = delta > 0 ? Math.min(100, currentBrightness + 5) : Math.max(1, currentBrightness - 5);
|
||||
DisplayService.setBrightness(newBrightness, deviceName, false);
|
||||
}
|
||||
|
||||
function getBatteryIconColor() {
|
||||
if (!BatteryService.batteryAvailable)
|
||||
return Theme.widgetIconColor;
|
||||
if (BatteryService.isLowBattery && !BatteryService.isCharging)
|
||||
return Theme.error;
|
||||
if (BatteryService.isCharging || BatteryService.isPluggedIn)
|
||||
return Theme.primary;
|
||||
return Theme.widgetIconColor;
|
||||
}
|
||||
|
||||
function hasPrintJobs() {
|
||||
return CupsService.getTotalJobsNum() > 0;
|
||||
}
|
||||
|
||||
function hasNoVisibleIcons() {
|
||||
return !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon && !root.showVpnIcon && !root.showBrightnessIcon && !root.showMicIcon && !root.showBatteryIcon && !root.showPrinterIcon;
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
@@ -26,34 +155,21 @@ BasePill {
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
DankIcon {
|
||||
name: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return "sync"
|
||||
}
|
||||
|
||||
const status = NetworkService.networkStatus
|
||||
if (status === "ethernet") {
|
||||
return "lan"
|
||||
}
|
||||
|
||||
if (status === "vpn") {
|
||||
return NetworkService.ethernetConnected ? "lan" : NetworkService.wifiSignalIcon
|
||||
}
|
||||
|
||||
return NetworkService.wifiSignalIcon
|
||||
}
|
||||
name: root.getNetworkIconName()
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return Theme.primary
|
||||
}
|
||||
|
||||
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
|
||||
}
|
||||
color: root.getNetworkIconColor()
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "vpn_lock"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: NetworkService.vpnConnected ? Theme.primary : Theme.outlineButton
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "bluetooth"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
@@ -71,19 +187,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
id: audioIconV
|
||||
|
||||
name: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
|
||||
return "volume_off"
|
||||
} else if (AudioService.sink.audio.volume * 100 < 33) {
|
||||
return "volume_down"
|
||||
} else {
|
||||
return "volume_up"
|
||||
}
|
||||
}
|
||||
return "volume_up"
|
||||
}
|
||||
name: root.getVolumeIconName()
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.widgetIconColor
|
||||
anchors.centerIn: parent
|
||||
@@ -92,31 +196,85 @@ BasePill {
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function(wheelEvent) {
|
||||
let delta = wheelEvent.angleDelta.y
|
||||
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0
|
||||
let newVolume
|
||||
if (delta > 0) {
|
||||
newVolume = Math.min(100, currentVolume + 5)
|
||||
} else {
|
||||
newVolume = Math.max(0, currentVolume - 5)
|
||||
}
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false
|
||||
AudioService.sink.audio.volume = newVolume / 100
|
||||
AudioService.playVolumeChangeSoundIfEnabled()
|
||||
}
|
||||
wheelEvent.accepted = true
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleVolumeWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: micIconV.implicitWidth + 4
|
||||
height: micIconV.implicitHeight + 4
|
||||
color: "transparent"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showMicIcon
|
||||
|
||||
DankIcon {
|
||||
id: micIconV
|
||||
name: root.getMicIconName()
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: root.getMicIconColor()
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleMicWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: brightnessIconV.implicitWidth + 4
|
||||
height: brightnessIconV.implicitHeight + 4
|
||||
color: "transparent"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
|
||||
|
||||
DankIcon {
|
||||
id: brightnessIconV
|
||||
name: root.getBrightnessIconName()
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.widgetIconColor
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleBrightnessWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: root.getBatteryIconColor()
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "print"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.primary
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "settings"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
|
||||
visible: root.hasNoVisibleIcons()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,38 +286,24 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
id: networkIcon
|
||||
|
||||
name: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return "sync"
|
||||
}
|
||||
|
||||
const status = NetworkService.networkStatus
|
||||
if (status === "ethernet") {
|
||||
return "lan"
|
||||
}
|
||||
|
||||
if (status === "vpn") {
|
||||
return NetworkService.ethernetConnected ? "lan" : NetworkService.wifiSignalIcon
|
||||
}
|
||||
|
||||
return NetworkService.wifiSignalIcon
|
||||
}
|
||||
name: root.getNetworkIconName()
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: {
|
||||
if (NetworkService.wifiToggling) {
|
||||
return Theme.primary
|
||||
}
|
||||
|
||||
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
|
||||
}
|
||||
color: root.getNetworkIconColor()
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: bluetoothIcon
|
||||
id: vpnIcon
|
||||
name: "vpn_lock"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: NetworkService.vpnConnected ? Theme.primary : Theme.outlineButton
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: bluetoothIcon
|
||||
name: "bluetooth"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
|
||||
@@ -176,19 +320,7 @@ BasePill {
|
||||
|
||||
DankIcon {
|
||||
id: audioIcon
|
||||
|
||||
name: {
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
if (AudioService.sink.audio.muted || AudioService.sink.audio.volume === 0) {
|
||||
return "volume_off";
|
||||
} else if (AudioService.sink.audio.volume * 100 < 33) {
|
||||
return "volume_down";
|
||||
} else {
|
||||
return "volume_up";
|
||||
}
|
||||
}
|
||||
return "volume_up";
|
||||
}
|
||||
name: root.getVolumeIconName()
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.widgetIconColor
|
||||
anchors.centerIn: parent
|
||||
@@ -196,34 +328,83 @@ BasePill {
|
||||
|
||||
MouseArea {
|
||||
id: audioWheelArea
|
||||
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function(wheelEvent) {
|
||||
let delta = wheelEvent.angleDelta.y;
|
||||
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
|
||||
let newVolume;
|
||||
if (delta > 0) {
|
||||
newVolume = Math.min(100, currentVolume + 5);
|
||||
} else {
|
||||
newVolume = Math.max(0, currentVolume - 5);
|
||||
}
|
||||
if (AudioService.sink && AudioService.sink.audio) {
|
||||
AudioService.sink.audio.muted = false;
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
AudioService.playVolumeChangeSoundIfEnabled();
|
||||
}
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleVolumeWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: micIcon.implicitWidth + 4
|
||||
height: micIcon.implicitHeight + 4
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showMicIcon
|
||||
|
||||
DankIcon {
|
||||
id: micIcon
|
||||
name: root.getMicIconName()
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: root.getMicIconColor()
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: micWheelArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleMicWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: brightnessIcon.implicitWidth + 4
|
||||
height: brightnessIcon.implicitHeight + 4
|
||||
color: "transparent"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showBrightnessIcon && DisplayService.brightnessAvailable && root.hasPinnedBrightnessDevice()
|
||||
|
||||
DankIcon {
|
||||
id: brightnessIcon
|
||||
name: root.getBrightnessIconName()
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.widgetIconColor
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: brightnessWheelArea
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
onWheel: function (wheelEvent) {
|
||||
root.handleBrightnessWheel(wheelEvent.angleDelta.y);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
name: "mic"
|
||||
id: batteryIcon
|
||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: root.getBatteryIconColor()
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
id: printerIcon
|
||||
name: "print"
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: Theme.primary
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: false
|
||||
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
@@ -231,9 +412,15 @@ BasePill {
|
||||
size: Theme.barIconSize(root.barThickness)
|
||||
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
|
||||
visible: root.hasNoVisibleIcons()
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Wayland
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
import qs.Modules.DankDash
|
||||
|
||||
DankPopout {
|
||||
id: root
|
||||
@@ -27,42 +22,130 @@ DankPopout {
|
||||
property bool __focusArmed: false
|
||||
property bool __contentReady: false
|
||||
|
||||
property var __mediaTabRef: null
|
||||
|
||||
property int __dropdownType: 0
|
||||
property point __dropdownAnchor: Qt.point(0, 0)
|
||||
property bool __dropdownRightEdge: false
|
||||
property var __dropdownPlayer: null
|
||||
property var __dropdownPlayers: []
|
||||
|
||||
function __showVolumeDropdown(pos, rightEdge, player, players) {
|
||||
__dropdownAnchor = pos;
|
||||
__dropdownRightEdge = rightEdge;
|
||||
__dropdownPlayer = player;
|
||||
__dropdownPlayers = players;
|
||||
__dropdownType = 1;
|
||||
}
|
||||
|
||||
function __showAudioDevicesDropdown(pos, rightEdge) {
|
||||
__dropdownAnchor = pos;
|
||||
__dropdownRightEdge = rightEdge;
|
||||
__dropdownType = 2;
|
||||
}
|
||||
|
||||
function __showPlayersDropdown(pos, rightEdge, player, players) {
|
||||
__dropdownAnchor = pos;
|
||||
__dropdownRightEdge = rightEdge;
|
||||
__dropdownPlayer = player;
|
||||
__dropdownPlayers = players;
|
||||
__dropdownType = 3;
|
||||
}
|
||||
|
||||
function __hideDropdowns() {
|
||||
__volumeCloseTimer.stop();
|
||||
__dropdownType = 0;
|
||||
__mediaTabRef?.resetDropdownStates();
|
||||
}
|
||||
|
||||
function __startCloseTimer() {
|
||||
__volumeCloseTimer.restart();
|
||||
}
|
||||
|
||||
function __stopCloseTimer() {
|
||||
__volumeCloseTimer.stop();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: __volumeCloseTimer
|
||||
interval: 400
|
||||
onTriggered: {
|
||||
if (__dropdownType === 1) {
|
||||
__hideDropdowns();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
overlayContent: Component {
|
||||
MediaDropdownOverlay {
|
||||
dropdownType: root.__dropdownType
|
||||
anchorPos: root.__dropdownAnchor
|
||||
isRightEdge: root.__dropdownRightEdge
|
||||
activePlayer: root.__dropdownPlayer
|
||||
allPlayers: root.__dropdownPlayers
|
||||
onCloseRequested: root.__hideDropdowns()
|
||||
onPanelEntered: root.__stopCloseTimer()
|
||||
onPanelExited: root.__startCloseTimer()
|
||||
onVolumeChanged: volume => {
|
||||
const player = root.__dropdownPlayer;
|
||||
const isChrome = player?.identity?.toLowerCase().includes("chrome") || player?.identity?.toLowerCase().includes("chromium");
|
||||
const usePlayerVolume = player && player.volumeSupported && !isChrome;
|
||||
if (usePlayerVolume) {
|
||||
player.volume = volume;
|
||||
} else if (AudioService.sink?.audio) {
|
||||
AudioService.sink.audio.volume = volume;
|
||||
}
|
||||
}
|
||||
onPlayerSelected: player => {
|
||||
const currentPlayer = MprisController.activePlayer;
|
||||
if (currentPlayer && currentPlayer !== player && currentPlayer.canPause) {
|
||||
currentPlayer.pause();
|
||||
}
|
||||
MprisController.activePlayer = player;
|
||||
root.__hideDropdowns();
|
||||
}
|
||||
onDeviceSelected: device => {
|
||||
root.__hideDropdowns();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function __tryFocusOnce() {
|
||||
if (!__focusArmed)
|
||||
return
|
||||
const win = root.window
|
||||
return;
|
||||
const win = root.window;
|
||||
if (!win || !win.visible)
|
||||
return
|
||||
return;
|
||||
if (!contentLoader.item)
|
||||
return
|
||||
|
||||
return;
|
||||
if (win.requestActivate)
|
||||
win.requestActivate()
|
||||
contentLoader.item.forceActiveFocus(Qt.TabFocusReason)
|
||||
win.requestActivate();
|
||||
contentLoader.item.forceActiveFocus(Qt.TabFocusReason);
|
||||
|
||||
if (contentLoader.item.activeFocus)
|
||||
__focusArmed = false
|
||||
__focusArmed = false;
|
||||
}
|
||||
|
||||
onDashVisibleChanged: {
|
||||
if (dashVisible) {
|
||||
__focusArmed = true
|
||||
__contentReady = !!contentLoader.item
|
||||
open()
|
||||
__tryFocusOnce()
|
||||
__focusArmed = true;
|
||||
__contentReady = !!contentLoader.item;
|
||||
open();
|
||||
__tryFocusOnce();
|
||||
} else {
|
||||
__focusArmed = false
|
||||
__contentReady = false
|
||||
close()
|
||||
__focusArmed = false;
|
||||
__contentReady = false;
|
||||
__hideDropdowns();
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: contentLoader
|
||||
function onLoaded() {
|
||||
__contentReady = true
|
||||
__contentReady = true;
|
||||
if (__focusArmed)
|
||||
__tryFocusOnce()
|
||||
__tryFocusOnce();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,12 +154,12 @@ DankPopout {
|
||||
enabled: !!root.window
|
||||
function onVisibleChanged() {
|
||||
if (__focusArmed)
|
||||
__tryFocusOnce()
|
||||
__tryFocusOnce();
|
||||
}
|
||||
}
|
||||
|
||||
onBackgroundClicked: {
|
||||
dashVisible = false
|
||||
dashVisible = false;
|
||||
}
|
||||
|
||||
content: Component {
|
||||
@@ -90,7 +173,7 @@ DankPopout {
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.shouldBeVisible) {
|
||||
mainContainer.forceActiveFocus()
|
||||
mainContainer.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,54 +182,54 @@ DankPopout {
|
||||
function onShouldBeVisibleChanged() {
|
||||
if (root.shouldBeVisible) {
|
||||
Qt.callLater(function () {
|
||||
mainContainer.forceActiveFocus()
|
||||
})
|
||||
mainContainer.forceActiveFocus();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: function (event) {
|
||||
if (event.key === Qt.Key_Escape) {
|
||||
root.dashVisible = false
|
||||
event.accepted = true
|
||||
return
|
||||
root.dashVisible = false;
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Tab && !(event.modifiers & Qt.ShiftModifier)) {
|
||||
let nextIndex = root.currentTabIndex + 1
|
||||
let nextIndex = root.currentTabIndex + 1;
|
||||
while (nextIndex < tabBar.model.length && tabBar.model[nextIndex] && tabBar.model[nextIndex].isAction) {
|
||||
nextIndex++
|
||||
nextIndex++;
|
||||
}
|
||||
if (nextIndex >= tabBar.model.length) {
|
||||
nextIndex = 0
|
||||
nextIndex = 0;
|
||||
}
|
||||
root.currentTabIndex = nextIndex
|
||||
event.accepted = true
|
||||
return
|
||||
root.currentTabIndex = nextIndex;
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) {
|
||||
let prevIndex = root.currentTabIndex - 1
|
||||
let prevIndex = root.currentTabIndex - 1;
|
||||
while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) {
|
||||
prevIndex--
|
||||
prevIndex--;
|
||||
}
|
||||
if (prevIndex < 0) {
|
||||
prevIndex = tabBar.model.length - 1
|
||||
prevIndex = tabBar.model.length - 1;
|
||||
while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) {
|
||||
prevIndex--
|
||||
prevIndex--;
|
||||
}
|
||||
}
|
||||
if (prevIndex >= 0) {
|
||||
root.currentTabIndex = prevIndex
|
||||
root.currentTabIndex = prevIndex;
|
||||
}
|
||||
event.accepted = true
|
||||
return
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (root.currentTabIndex === 2 && wallpaperTab.handleKeyEvent) {
|
||||
if (wallpaperTab.handleKeyEvent(event)) {
|
||||
event.accepted = true
|
||||
return
|
||||
event.accepted = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -171,50 +254,54 @@ DankPopout {
|
||||
focus: false
|
||||
activeFocusOnTab: false
|
||||
nextFocusTarget: {
|
||||
const item = pages.currentItem
|
||||
const item = pages.currentItem;
|
||||
if (!item)
|
||||
return null
|
||||
return null;
|
||||
if (item.focusTarget)
|
||||
return item.focusTarget
|
||||
return item
|
||||
return item.focusTarget;
|
||||
return item;
|
||||
}
|
||||
|
||||
model: {
|
||||
let tabs = [{
|
||||
"icon": "dashboard",
|
||||
"text": I18n.tr("Overview")
|
||||
}, {
|
||||
"icon": "music_note",
|
||||
"text": I18n.tr("Media")
|
||||
}, {
|
||||
"icon": "wallpaper",
|
||||
"text": I18n.tr("Wallpapers")
|
||||
}]
|
||||
let tabs = [
|
||||
{
|
||||
"icon": "dashboard",
|
||||
"text": I18n.tr("Overview")
|
||||
},
|
||||
{
|
||||
"icon": "music_note",
|
||||
"text": I18n.tr("Media")
|
||||
},
|
||||
{
|
||||
"icon": "wallpaper",
|
||||
"text": I18n.tr("Wallpapers")
|
||||
}
|
||||
];
|
||||
|
||||
if (SettingsData.weatherEnabled) {
|
||||
tabs.push({
|
||||
"icon": "wb_sunny",
|
||||
"text": I18n.tr("Weather")
|
||||
})
|
||||
"icon": "wb_sunny",
|
||||
"text": I18n.tr("Weather")
|
||||
});
|
||||
}
|
||||
|
||||
tabs.push({
|
||||
"icon": "settings",
|
||||
"text": I18n.tr("Settings"),
|
||||
"isAction": true
|
||||
})
|
||||
return tabs
|
||||
"icon": "settings",
|
||||
"text": I18n.tr("Settings"),
|
||||
"isAction": true
|
||||
});
|
||||
return tabs;
|
||||
}
|
||||
|
||||
onTabClicked: function (index) {
|
||||
root.currentTabIndex = index
|
||||
root.currentTabIndex = index;
|
||||
}
|
||||
|
||||
onActionTriggered: function (index) {
|
||||
let settingsIndex = SettingsData.weatherEnabled ? 4 : 3
|
||||
let settingsIndex = SettingsData.weatherEnabled ? 4 : 3;
|
||||
if (index === settingsIndex) {
|
||||
dashVisible = false
|
||||
settingsModal.show()
|
||||
dashVisible = false;
|
||||
settingsModal.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,14 +316,14 @@ DankPopout {
|
||||
width: parent.width
|
||||
implicitHeight: {
|
||||
if (currentIndex === 0)
|
||||
return overviewTab.implicitHeight
|
||||
return overviewTab.implicitHeight;
|
||||
if (currentIndex === 1)
|
||||
return mediaTab.implicitHeight
|
||||
return mediaTab.implicitHeight;
|
||||
if (currentIndex === 2)
|
||||
return wallpaperTab.implicitHeight
|
||||
return wallpaperTab.implicitHeight;
|
||||
if (SettingsData.weatherEnabled && currentIndex === 3)
|
||||
return weatherTab.implicitHeight
|
||||
return overviewTab.implicitHeight
|
||||
return weatherTab.implicitHeight;
|
||||
return overviewTab.implicitHeight;
|
||||
}
|
||||
currentIndex: root.currentTabIndex
|
||||
|
||||
@@ -244,24 +331,42 @@ DankPopout {
|
||||
id: overviewTab
|
||||
|
||||
onCloseDash: {
|
||||
root.dashVisible = false
|
||||
root.dashVisible = false;
|
||||
}
|
||||
|
||||
onSwitchToWeatherTab: {
|
||||
if (SettingsData.weatherEnabled) {
|
||||
tabBar.currentIndex = 3
|
||||
tabBar.tabClicked(3)
|
||||
tabBar.currentIndex = 3;
|
||||
tabBar.tabClicked(3);
|
||||
}
|
||||
}
|
||||
|
||||
onSwitchToMediaTab: {
|
||||
tabBar.currentIndex = 1
|
||||
tabBar.tabClicked(1)
|
||||
tabBar.currentIndex = 1;
|
||||
tabBar.tabClicked(1);
|
||||
}
|
||||
}
|
||||
|
||||
MediaPlayerTab {
|
||||
id: mediaTab
|
||||
targetScreen: root.screen
|
||||
popoutX: root.alignedX
|
||||
popoutY: root.alignedY
|
||||
popoutWidth: root.alignedWidth
|
||||
popoutHeight: root.alignedHeight
|
||||
contentOffsetY: Theme.spacingM + 48 + Theme.spacingS + Theme.spacingXS
|
||||
Component.onCompleted: root.__mediaTabRef = this
|
||||
onShowVolumeDropdown: (pos, screen, rightEdge, player, players) => {
|
||||
root.__showVolumeDropdown(pos, rightEdge, player, players);
|
||||
}
|
||||
onShowAudioDevicesDropdown: (pos, screen, rightEdge) => {
|
||||
root.__showAudioDevicesDropdown(pos, rightEdge);
|
||||
}
|
||||
onShowPlayersDropdown: (pos, screen, rightEdge, player, players) => {
|
||||
root.__showPlayersDropdown(pos, rightEdge, player, players);
|
||||
}
|
||||
onHideDropdowns: root.__hideDropdowns()
|
||||
onVolumeButtonExited: root.__startCloseTimer()
|
||||
}
|
||||
|
||||
WallpaperTab {
|
||||
|
||||
491
quickshell/Modules/DankDash/MediaDropdownOverlay.qml
Normal file
491
quickshell/Modules/DankDash/MediaDropdownOverlay.qml
Normal file
@@ -0,0 +1,491 @@
|
||||
import QtQuick
|
||||
import QtQuick.Effects
|
||||
import Quickshell.Services.Pipewire
|
||||
import qs.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property int dropdownType: 0
|
||||
property var activePlayer: null
|
||||
property var allPlayers: []
|
||||
property point anchorPos: Qt.point(0, 0)
|
||||
property bool isRightEdge: false
|
||||
|
||||
property bool __isChromeBrowser: {
|
||||
if (!activePlayer?.identity)
|
||||
return false;
|
||||
const id = activePlayer.identity.toLowerCase();
|
||||
return id.includes("chrome") || id.includes("chromium");
|
||||
}
|
||||
property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
|
||||
property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
|
||||
property bool volumeAvailable: (activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio)
|
||||
property var availableDevices: Pipewire.nodes.values.filter(node => node.audio && node.isSink && !node.isStream)
|
||||
|
||||
signal closeRequested
|
||||
signal deviceSelected(var device)
|
||||
signal playerSelected(var player)
|
||||
signal volumeChanged(real volume)
|
||||
signal panelEntered
|
||||
signal panelExited
|
||||
|
||||
property int __volumeHoverCount: 0
|
||||
|
||||
function volumeAreaEntered() {
|
||||
__volumeHoverCount++;
|
||||
panelEntered();
|
||||
}
|
||||
|
||||
function volumeAreaExited() {
|
||||
__volumeHoverCount--;
|
||||
Qt.callLater(() => {
|
||||
if (__volumeHoverCount <= 0)
|
||||
panelExited();
|
||||
});
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: volumePanel
|
||||
visible: dropdownType === 1 && volumeAvailable
|
||||
width: 60
|
||||
height: 180
|
||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
||||
y: anchorPos.y - height / 2
|
||||
radius: Theme.cornerRadius * 2
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
border.width: 1
|
||||
|
||||
opacity: dropdownType === 1 ? 1 : 0
|
||||
scale: dropdownType === 1 ? 1 : 0.96
|
||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 8
|
||||
shadowBlur: 1.0
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.4)
|
||||
shadowOpacity: 0.7
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -12
|
||||
hoverEnabled: true
|
||||
onEntered: volumeAreaEntered()
|
||||
onExited: volumeAreaExited()
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
|
||||
Item {
|
||||
id: volumeSlider
|
||||
width: parent.width * 0.5
|
||||
height: parent.height - Theme.spacingXL * 2
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
anchors.centerIn: parent
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: volumeAvailable ? (Math.min(1.0, currentVolume) * parent.height) : 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.primary
|
||||
bottomLeftRadius: Theme.cornerRadius
|
||||
bottomRightRadius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width + 8
|
||||
height: 8
|
||||
radius: Theme.cornerRadius
|
||||
y: {
|
||||
const ratio = volumeAvailable ? Math.min(1.0, currentVolume) : 0;
|
||||
const travel = parent.height - height;
|
||||
return Math.max(0, Math.min(travel, travel * (1 - ratio)));
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.primary
|
||||
border.width: 3
|
||||
border.color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1.0)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -12
|
||||
enabled: volumeAvailable
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
preventStealing: true
|
||||
|
||||
onEntered: volumeAreaEntered()
|
||||
onExited: volumeAreaExited()
|
||||
onPressed: mouse => updateVolume(mouse)
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed)
|
||||
updateVolume(mouse);
|
||||
}
|
||||
onClicked: mouse => updateVolume(mouse)
|
||||
|
||||
function updateVolume(mouse) {
|
||||
if (!volumeAvailable)
|
||||
return;
|
||||
const ratio = 1.0 - (mouse.y / height);
|
||||
const volume = Math.max(0, Math.min(1, ratio));
|
||||
root.volumeChanged(volume);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottomMargin: Theme.spacingL
|
||||
text: volumeAvailable ? Math.round(currentVolume * 100) + "%" : "0%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: audioDevicesPanel
|
||||
visible: dropdownType === 2
|
||||
width: 280
|
||||
height: Math.max(200, Math.min(280, availableDevices.length * 50 + 100))
|
||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
||||
y: anchorPos.y - height / 2
|
||||
radius: Theme.cornerRadius * 2
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
||||
border.width: 2
|
||||
|
||||
opacity: dropdownType === 2 ? 1 : 0
|
||||
scale: dropdownType === 2 ? 1 : 0.96
|
||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 8
|
||||
shadowBlur: 1.0
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.4)
|
||||
shadowOpacity: 0.7
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Audio Output Devices (") + availableDevices.length + ")"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
bottomPadding: Theme.spacingM
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
width: parent.width
|
||||
height: parent.height - 40
|
||||
contentHeight: deviceColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: deviceColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: availableDevices
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: modelData === AudioService.sink ? 2 : 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
|
||||
DankIcon {
|
||||
name: getAudioDeviceIcon(modelData)
|
||||
size: 20
|
||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
function getAudioDeviceIcon(device) {
|
||||
if (!device?.name)
|
||||
return "speaker";
|
||||
const name = device.name.toLowerCase();
|
||||
if (name.includes("bluez") || name.includes("bluetooth"))
|
||||
return "headset";
|
||||
if (name.includes("hdmi"))
|
||||
return "tv";
|
||||
if (name.includes("usb"))
|
||||
return "headset";
|
||||
return "speaker";
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 20 - Theme.spacingM * 2
|
||||
|
||||
StyledText {
|
||||
text: AudioService.displayName(modelData)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData === AudioService.sink ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData === AudioService.sink ? "Active" : "Available"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deviceMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
Pipewire.preferredDefaultAudioSink = modelData;
|
||||
root.deviceSelected(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: playersPanel
|
||||
visible: dropdownType === 3
|
||||
width: 240
|
||||
height: Math.max(180, Math.min(240, (allPlayers?.length || 0) * 50 + 80))
|
||||
x: isRightEdge ? anchorPos.x : anchorPos.x - width
|
||||
y: anchorPos.y - height / 2
|
||||
radius: Theme.cornerRadius * 2
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
||||
border.width: 2
|
||||
|
||||
opacity: dropdownType === 3 ? 1 : 0
|
||||
scale: dropdownType === 3 ? 1 : 0.96
|
||||
transformOrigin: isRightEdge ? Item.Left : Item.Right
|
||||
|
||||
Behavior on opacity {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Theme.expressiveDurations.expressiveDefaultSpatial
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Theme.expressiveCurves.expressiveDefaultSpatial
|
||||
}
|
||||
}
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 8
|
||||
shadowBlur: 1.0
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.4)
|
||||
shadowOpacity: 0.7
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Media Players (") + (allPlayers?.length || 0) + ")"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
bottomPadding: Theme.spacingM
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
width: parent.width
|
||||
height: parent.height - 40
|
||||
contentHeight: playerColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: playerColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: allPlayers || []
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: playerMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: modelData === activePlayer ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: modelData === activePlayer ? 2 : 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
|
||||
DankIcon {
|
||||
name: "music_note"
|
||||
size: 20
|
||||
color: modelData === activePlayer ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 20 - Theme.spacingM * 2
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!modelData)
|
||||
return "Unknown Player";
|
||||
const identity = modelData.identity || "Unknown Player";
|
||||
const trackTitle = modelData.trackTitle || "";
|
||||
return trackTitle.length > 0 ? identity + " - " + trackTitle : identity;
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData === activePlayer ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
width: parent.width
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!modelData)
|
||||
return "";
|
||||
const artist = modelData.trackArtist || "";
|
||||
const isActive = modelData === activePlayer;
|
||||
if (artist.length > 0)
|
||||
return artist + (isActive ? " (Active)" : "");
|
||||
return isActive ? "Active" : "Available";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
width: parent.width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: playerMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData?.identity) {
|
||||
root.playerSelected(modelData);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
z: -1
|
||||
enabled: dropdownType !== 0
|
||||
onClicked: closeRequested()
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Effects
|
||||
import QtQuick.Layouts
|
||||
import Quickshell.Services.Mpris
|
||||
import Quickshell.Services.Pipewire
|
||||
import Quickshell.Io
|
||||
import Quickshell
|
||||
import qs.Common
|
||||
@@ -15,14 +13,42 @@ Item {
|
||||
|
||||
property MprisPlayer activePlayer: MprisController.activePlayer
|
||||
property var allPlayers: MprisController.availablePlayers
|
||||
property var targetScreen: null
|
||||
property real popoutX: 0
|
||||
property real popoutY: 0
|
||||
property real popoutWidth: 0
|
||||
property real popoutHeight: 0
|
||||
property real contentOffsetY: 0
|
||||
|
||||
signal showVolumeDropdown(point pos, var screen, bool rightEdge, var player, var players)
|
||||
signal showAudioDevicesDropdown(point pos, var screen, bool rightEdge)
|
||||
signal showPlayersDropdown(point pos, var screen, bool rightEdge, var player, var players)
|
||||
signal hideDropdowns
|
||||
signal volumeButtonExited
|
||||
|
||||
property bool volumeExpanded: false
|
||||
property bool devicesExpanded: false
|
||||
property bool playersExpanded: false
|
||||
|
||||
function resetDropdownStates() {
|
||||
volumeExpanded = false;
|
||||
devicesExpanded = false;
|
||||
playersExpanded = false;
|
||||
}
|
||||
|
||||
DankTooltipV2 {
|
||||
id: sharedTooltip
|
||||
}
|
||||
|
||||
readonly property bool isRightEdge: (SettingsData.barConfigs[0]?.position ?? SettingsData.Position.Top) === SettingsData.Position.Right
|
||||
readonly property bool volumeAvailable: (activePlayer && activePlayer.volumeSupported) || (AudioService.sink && AudioService.sink.audio)
|
||||
readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported
|
||||
readonly property bool __isChromeBrowser: {
|
||||
if (!activePlayer?.identity)
|
||||
return false;
|
||||
const id = activePlayer.identity.toLowerCase();
|
||||
return id.includes("chrome") || id.includes("chromium");
|
||||
}
|
||||
readonly property bool volumeAvailable: (activePlayer && activePlayer.volumeSupported && !__isChromeBrowser) || (AudioService.sink && AudioService.sink.audio)
|
||||
readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
|
||||
readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
|
||||
|
||||
// Palette that stays stable across track switches until new colors are ready
|
||||
@@ -334,383 +360,6 @@ Item {
|
||||
anchors.fill: parent
|
||||
clip: false
|
||||
visible: !_noneAvailable && (!showNoPlayerNow)
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
enabled: audioDevicesButton.devicesExpanded || volumeButton.volumeExpanded || playerSelectorButton.playersExpanded
|
||||
onClicked: function (mouse) {
|
||||
const clickOutside = item => {
|
||||
return mouse.x < item.x || mouse.x > item.x + item.width || mouse.y < item.y || mouse.y > item.y + item.height;
|
||||
};
|
||||
|
||||
if (playerSelectorButton.playersExpanded && clickOutside(playerSelectorDropdown)) {
|
||||
playerSelectorButton.playersExpanded = false;
|
||||
}
|
||||
if (audioDevicesButton.devicesExpanded && clickOutside(audioDevicesDropdown)) {
|
||||
audioDevicesButton.devicesExpanded = false;
|
||||
}
|
||||
if (volumeButton.volumeExpanded && clickOutside(volumeSliderPanel) && clickOutside(volumeButton)) {
|
||||
volumeButton.volumeExpanded = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: audioDevicesDropdown
|
||||
width: 280
|
||||
height: audioDevicesButton.devicesExpanded ? Math.max(200, Math.min(280, audioDevicesDropdown.availableDevices.length * 50 + 100)) : 0
|
||||
x: isRightEdge ? audioDevicesButton.x + audioDevicesButton.width + Theme.spacingS : audioDevicesButton.x - width - Theme.spacingS
|
||||
y: audioDevicesButton.y - 50
|
||||
visible: audioDevicesButton.devicesExpanded
|
||||
closePolicy: Popup.NoAutoClose
|
||||
modal: false
|
||||
dim: false
|
||||
padding: 0
|
||||
|
||||
property var availableDevices: Pipewire.nodes.values.filter(node => {
|
||||
return node.audio && node.isSink && !node.isStream;
|
||||
})
|
||||
|
||||
background: Rectangle {
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
||||
border.width: 2
|
||||
radius: Theme.cornerRadius * 2
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 8
|
||||
shadowBlur: 1.0
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.4)
|
||||
shadowOpacity: 0.7
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedDecel
|
||||
}
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Audio Output Devices (") + audioDevicesDropdown.availableDevices.length + ")"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
bottomPadding: Theme.spacingM
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
width: parent.width
|
||||
height: parent.height - 40
|
||||
contentHeight: deviceColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: deviceColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: audioDevicesDropdown.availableDevices
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: deviceMouseAreaLeft.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: modelData === AudioService.sink ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: modelData === AudioService.sink ? 2 : 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
|
||||
DankIcon {
|
||||
name: getAudioDeviceIcon(modelData)
|
||||
size: 20
|
||||
color: modelData === AudioService.sink ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 20 - Theme.spacingM * 2
|
||||
|
||||
StyledText {
|
||||
text: AudioService.displayName(modelData)
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData === AudioService.sink ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData === AudioService.sink ? "Active" : "Available"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: deviceMouseAreaLeft
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData) {
|
||||
Pipewire.preferredDefaultAudioSink = modelData;
|
||||
}
|
||||
audioDevicesButton.devicesExpanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Anims.durShort
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: playerSelectorDropdown
|
||||
width: 240
|
||||
height: playerSelectorButton.playersExpanded ? Math.max(180, Math.min(240, (root.allPlayers?.length || 0) * 50 + 80)) : 0
|
||||
x: isRightEdge ? playerSelectorButton.x + playerSelectorButton.width + Theme.spacingS : playerSelectorButton.x - width - Theme.spacingS
|
||||
y: playerSelectorButton.y - 50
|
||||
visible: playerSelectorButton.playersExpanded
|
||||
closePolicy: Popup.NoAutoClose
|
||||
modal: false
|
||||
dim: false
|
||||
padding: 0
|
||||
|
||||
background: Rectangle {
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.98)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.6)
|
||||
border.width: 2
|
||||
radius: Theme.cornerRadius * 2
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 8
|
||||
shadowBlur: 1.0
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.4)
|
||||
shadowOpacity: 0.7
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on height {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.emphasizedDecel
|
||||
}
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Media Players (") + (allPlayers?.length || 0) + ")"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
font.weight: Font.Medium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
bottomPadding: Theme.spacingM
|
||||
}
|
||||
|
||||
DankFlickable {
|
||||
width: parent.width
|
||||
height: parent.height - 40
|
||||
contentHeight: playerColumn.height
|
||||
clip: true
|
||||
|
||||
Column {
|
||||
id: playerColumn
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: allPlayers || []
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
width: parent.width
|
||||
height: 48
|
||||
radius: Theme.cornerRadius
|
||||
color: playerMouseArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
border.color: modelData === activePlayer ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.2)
|
||||
border.width: modelData === activePlayer ? 2 : 1
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingM
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingM
|
||||
width: parent.width - Theme.spacingM * 2
|
||||
|
||||
DankIcon {
|
||||
name: "music_note"
|
||||
size: 20
|
||||
color: modelData === activePlayer ? Theme.primary : Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 20 - Theme.spacingM * 2
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!modelData)
|
||||
return "Unknown Player";
|
||||
|
||||
const identity = modelData.identity || "Unknown Player";
|
||||
const trackTitle = modelData.trackTitle || "";
|
||||
|
||||
if (trackTitle.length > 0) {
|
||||
return identity + " - " + trackTitle;
|
||||
}
|
||||
|
||||
return identity;
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
font.weight: modelData === activePlayer ? Font.Medium : Font.Normal
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: {
|
||||
if (!modelData)
|
||||
return "";
|
||||
|
||||
const artist = modelData.trackArtist || "";
|
||||
const isActive = modelData === activePlayer;
|
||||
|
||||
if (artist.length > 0) {
|
||||
return artist + (isActive ? " (Active)" : "");
|
||||
}
|
||||
|
||||
return isActive ? "Active" : "Available";
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
elide: Text.ElideRight
|
||||
width: parent.width
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: playerMouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (modelData && modelData.identity) {
|
||||
if (activePlayer && activePlayer !== modelData && activePlayer.canPause) {
|
||||
activePlayer.pause();
|
||||
}
|
||||
|
||||
MprisController.activePlayer = modelData;
|
||||
}
|
||||
playerSelectorButton.playersExpanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
Behavior on border.color {
|
||||
ColorAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Center Column: Main Media Content
|
||||
ColumnLayout {
|
||||
x: 72
|
||||
y: 20
|
||||
@@ -1051,14 +700,12 @@ Item {
|
||||
radius: 20
|
||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||
y: 185
|
||||
color: playerSelectorArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||
color: playerSelectorArea.containsMouse || playersExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
border.width: 1
|
||||
z: 100
|
||||
visible: (allPlayers?.length || 0) >= 1
|
||||
|
||||
property bool playersExpanded: false
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: "assistant_device"
|
||||
@@ -1072,14 +719,20 @@ Item {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
parent.playersExpanded = !parent.playersExpanded;
|
||||
}
|
||||
onEntered: {
|
||||
sharedTooltip.show("Media Players", playerSelectorButton, 0, 0, isRightEdge ? "right" : "left");
|
||||
}
|
||||
onExited: {
|
||||
sharedTooltip.hide();
|
||||
if (playersExpanded) {
|
||||
hideDropdowns();
|
||||
return;
|
||||
}
|
||||
hideDropdowns();
|
||||
playersExpanded = true;
|
||||
const buttonsOnRight = !isRightEdge;
|
||||
const btnY = playerSelectorButton.y + playerSelectorButton.height / 2;
|
||||
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
||||
const screenY = popoutY + contentOffsetY + btnY;
|
||||
showPlayersDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
|
||||
}
|
||||
onEntered: sharedTooltip.show("Media Players", playerSelectorButton, 0, 0, isRightEdge ? "right" : "left")
|
||||
onExited: sharedTooltip.hide()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1090,21 +743,14 @@ Item {
|
||||
radius: 20
|
||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||
y: 130
|
||||
color: volumeButtonArea.containsMouse && volumeAvailable ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||
color: volumeButtonArea.containsMouse && volumeAvailable || volumeExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, volumeAvailable ? 0.3 : 0.15)
|
||||
border.width: 1
|
||||
z: 101
|
||||
enabled: volumeAvailable
|
||||
|
||||
property bool volumeExpanded: false
|
||||
property real previousVolume: 0.0
|
||||
|
||||
Timer {
|
||||
id: volumeHideTimer
|
||||
interval: 500
|
||||
onTriggered: volumeButton.volumeExpanded = false
|
||||
}
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: getVolumeIcon()
|
||||
@@ -1118,11 +764,19 @@ Item {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onEntered: {
|
||||
volumeButton.volumeExpanded = true;
|
||||
volumeHideTimer.stop();
|
||||
if (volumeExpanded)
|
||||
return;
|
||||
hideDropdowns();
|
||||
volumeExpanded = true;
|
||||
const buttonsOnRight = !isRightEdge;
|
||||
const btnY = volumeButton.y + volumeButton.height / 2;
|
||||
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
||||
const screenY = popoutY + contentOffsetY + btnY;
|
||||
showVolumeDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight, activePlayer, allPlayers);
|
||||
}
|
||||
onExited: {
|
||||
volumeHideTimer.restart();
|
||||
if (volumeExpanded)
|
||||
volumeButtonExited();
|
||||
}
|
||||
onClicked: {
|
||||
if (currentVolume > 0) {
|
||||
@@ -1142,22 +796,15 @@ Item {
|
||||
}
|
||||
}
|
||||
onWheel: wheelEvent => {
|
||||
let delta = wheelEvent.angleDelta.y;
|
||||
let current = (currentVolume * 100) || 0;
|
||||
let newVolume;
|
||||
|
||||
if (delta > 0) {
|
||||
newVolume = Math.min(100, current + 5);
|
||||
} else {
|
||||
newVolume = Math.max(0, current - 5);
|
||||
}
|
||||
const delta = wheelEvent.angleDelta.y;
|
||||
const current = (currentVolume * 100) || 0;
|
||||
const newVolume = delta > 0 ? Math.min(100, current + 5) : Math.max(0, current - 5);
|
||||
|
||||
if (usePlayerVolume) {
|
||||
activePlayer.volume = newVolume / 100;
|
||||
} else if (AudioService.sink?.audio) {
|
||||
AudioService.sink.audio.volume = newVolume / 100;
|
||||
}
|
||||
volumeButton.volumeExpanded = true;
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
}
|
||||
@@ -1170,16 +817,14 @@ Item {
|
||||
radius: 20
|
||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||
y: 240
|
||||
color: audioDevicesArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||
color: audioDevicesArea.containsMouse || devicesExpanded ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.2) : "transparent"
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
border.width: 1
|
||||
z: 100
|
||||
|
||||
property bool devicesExpanded: false
|
||||
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: parent.devicesExpanded ? "expand_less" : "speaker"
|
||||
name: devicesExpanded ? "expand_less" : "speaker"
|
||||
size: 18
|
||||
color: Theme.surfaceText
|
||||
}
|
||||
@@ -1190,247 +835,20 @@ Item {
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
parent.devicesExpanded = !parent.devicesExpanded;
|
||||
if (devicesExpanded) {
|
||||
hideDropdowns();
|
||||
return;
|
||||
}
|
||||
hideDropdowns();
|
||||
devicesExpanded = true;
|
||||
const buttonsOnRight = !isRightEdge;
|
||||
const btnY = audioDevicesButton.y + audioDevicesButton.height / 2;
|
||||
const screenX = buttonsOnRight ? (popoutX + popoutWidth) : popoutX;
|
||||
const screenY = popoutY + contentOffsetY + btnY;
|
||||
showAudioDevicesDropdown(Qt.point(screenX, screenY), targetScreen, buttonsOnRight);
|
||||
}
|
||||
onEntered: {
|
||||
sharedTooltip.show("Output Device", audioDevicesButton, 0, 0, isRightEdge ? "right" : "left");
|
||||
}
|
||||
onExited: {
|
||||
sharedTooltip.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: volumeSliderPanel
|
||||
width: 60
|
||||
height: 180
|
||||
x: isRightEdge ? volumeButton.x + volumeButton.width + Theme.spacingS : volumeButton.x - width - Theme.spacingS
|
||||
y: volumeButton.y - 50
|
||||
visible: volumeButton.volumeExpanded && volumeAvailable
|
||||
closePolicy: Popup.NoAutoClose
|
||||
modal: false
|
||||
dim: false
|
||||
padding: 0
|
||||
|
||||
background: Rectangle {
|
||||
radius: Theme.cornerRadius * 2
|
||||
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95)
|
||||
border.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||
border.width: 1
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: MultiEffect {
|
||||
shadowEnabled: true
|
||||
shadowHorizontalOffset: 0
|
||||
shadowVerticalOffset: 8
|
||||
shadowBlur: 1.0
|
||||
shadowColor: Qt.rgba(0, 0, 0, 0.4)
|
||||
shadowOpacity: 0.7
|
||||
}
|
||||
}
|
||||
|
||||
enter: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 0
|
||||
to: 1
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
|
||||
exit: Transition {
|
||||
NumberAnimation {
|
||||
property: "opacity"
|
||||
from: 1
|
||||
to: 0
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
|
||||
Item {
|
||||
id: volumeSlider
|
||||
width: parent.width * 0.5
|
||||
height: parent.height - Theme.spacingXL * 2
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: Theme.spacingS
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
property bool dragging: false
|
||||
property bool containsMouse: volumeSliderArea.containsMouse
|
||||
property bool active: volumeSliderArea.containsMouse || volumeSliderArea.pressed || dragging
|
||||
|
||||
Rectangle {
|
||||
id: sliderTrack
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
anchors.centerIn: parent
|
||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: sliderFill
|
||||
width: parent.width
|
||||
height: volumeAvailable ? (Math.min(1.0, currentVolume) * parent.height) : 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.primary
|
||||
bottomLeftRadius: Theme.cornerRadius
|
||||
bottomRightRadius: Theme.cornerRadius
|
||||
topLeftRadius: 0
|
||||
topRightRadius: 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: sliderHandle
|
||||
width: parent.width + 8
|
||||
height: 8
|
||||
radius: Theme.cornerRadius
|
||||
y: {
|
||||
const ratio = volumeAvailable ? Math.min(1.0, currentVolume) : 0;
|
||||
const travel = parent.height - height;
|
||||
return Math.max(0, Math.min(travel, travel * (1 - ratio)));
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.primary
|
||||
border.width: 3
|
||||
border.color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 1.0)
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.onPrimary
|
||||
opacity: volumeSliderArea.pressed ? 0.16 : (volumeSliderArea.containsMouse ? 0.08 : 0)
|
||||
visible: opacity > 0
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: ripple
|
||||
anchors.centerIn: parent
|
||||
width: 0
|
||||
height: 0
|
||||
radius: width / 2
|
||||
color: Theme.onPrimary
|
||||
opacity: 0
|
||||
|
||||
function start() {
|
||||
opacity = 0.16;
|
||||
width = 0;
|
||||
height = 0;
|
||||
rippleAnimation.start();
|
||||
}
|
||||
|
||||
SequentialAnimation {
|
||||
id: rippleAnimation
|
||||
NumberAnimation {
|
||||
target: ripple
|
||||
properties: "width,height"
|
||||
to: 28
|
||||
duration: 180
|
||||
}
|
||||
NumberAnimation {
|
||||
target: ripple
|
||||
property: "opacity"
|
||||
to: 0
|
||||
duration: 150
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
acceptedButtons: Qt.LeftButton
|
||||
onPressedChanged: {
|
||||
if (pressed) {
|
||||
ripple.start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scale: volumeSlider.active ? 1.05 : 1.0
|
||||
|
||||
Behavior on scale {
|
||||
NumberAnimation {
|
||||
duration: Anims.durShort
|
||||
easing.type: Easing.BezierSpline
|
||||
easing.bezierCurve: Anims.standard
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: volumeSliderArea
|
||||
anchors.fill: parent
|
||||
anchors.margins: -12
|
||||
enabled: volumeAvailable
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
preventStealing: true
|
||||
|
||||
onEntered: {
|
||||
volumeHideTimer.stop();
|
||||
}
|
||||
|
||||
onExited: {
|
||||
volumeHideTimer.restart();
|
||||
}
|
||||
|
||||
onPressed: function (mouse) {
|
||||
parent.dragging = true;
|
||||
updateVolume(mouse);
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
parent.dragging = false;
|
||||
}
|
||||
|
||||
onPositionChanged: function (mouse) {
|
||||
if (pressed) {
|
||||
updateVolume(mouse);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: function (mouse) {
|
||||
updateVolume(mouse);
|
||||
}
|
||||
|
||||
onWheel: wheelEvent => {
|
||||
const step = 1;
|
||||
adjustVolume(wheelEvent.angleDelta.y > 0 ? step : -step);
|
||||
wheelEvent.accepted = true;
|
||||
}
|
||||
|
||||
function updateVolume(mouse) {
|
||||
if (volumeAvailable) {
|
||||
const ratio = 1.0 - (mouse.y / height);
|
||||
const volume = Math.max(0, Math.min(1, ratio));
|
||||
if (usePlayerVolume) {
|
||||
activePlayer.volume = volume;
|
||||
} else if (AudioService.sink?.audio) {
|
||||
AudioService.sink.audio.volume = volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottomMargin: Theme.spacingL
|
||||
text: volumeAvailable ? Math.round(currentVolume * 100) + "%" : "0%"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
onEntered: sharedTooltip.show("Output Device", audioDevicesButton, 0, 0, isRightEdge ? "right" : "left")
|
||||
onExited: sharedTooltip.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,10 @@ DankOSD {
|
||||
id: root
|
||||
|
||||
readonly property bool useVertical: isVerticalLayout
|
||||
property int targetBrightness: {
|
||||
DisplayService.brightnessVersion;
|
||||
return DisplayService.brightnessLevel;
|
||||
}
|
||||
|
||||
osdWidth: useVertical ? (40 + Theme.spacingS * 2) : Math.min(260, Screen.width - Theme.spacingM * 2)
|
||||
osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2)
|
||||
@@ -17,7 +21,7 @@ DankOSD {
|
||||
target: DisplayService
|
||||
function onBrightnessChanged(showOsd) {
|
||||
if (showOsd && SettingsData.osdBrightnessEnabled) {
|
||||
root.show()
|
||||
root.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,13 +52,13 @@ DankOSD {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
|
||||
return "brightness_medium"
|
||||
return "brightness_medium";
|
||||
} else if (deviceInfo.name.includes("kbd")) {
|
||||
return "keyboard"
|
||||
return "keyboard";
|
||||
} else {
|
||||
return "lightbulb"
|
||||
return "lightbulb";
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize
|
||||
@@ -70,74 +74,50 @@ DankOSD {
|
||||
x: parent.gap * 2 + Theme.iconSize
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
minimum: {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
||||
if (!deviceInfo) return 1
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo)
|
||||
return 1;
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||
if (isExponential) {
|
||||
return 1
|
||||
return 1;
|
||||
}
|
||||
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0
|
||||
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0;
|
||||
}
|
||||
maximum: {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
||||
if (!deviceInfo) return 100
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo)
|
||||
return 100;
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||
if (isExponential) {
|
||||
return 100
|
||||
return 100;
|
||||
}
|
||||
return deviceInfo.displayMax || 100
|
||||
return deviceInfo.displayMax || 100;
|
||||
}
|
||||
enabled: DisplayService.brightnessAvailable
|
||||
showValue: true
|
||||
unit: {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
||||
if (!deviceInfo) return "%"
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo)
|
||||
return "%";
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||
if (isExponential) {
|
||||
return "%"
|
||||
return "%";
|
||||
}
|
||||
return deviceInfo.class === "ddc" ? "" : "%"
|
||||
return deviceInfo.class === "ddc" ? "" : "%";
|
||||
}
|
||||
thumbOutlineColor: Theme.surfaceContainer
|
||||
alwaysShowValue: SettingsData.osdAlwaysShowValue
|
||||
|
||||
Component.onCompleted: {
|
||||
if (DisplayService.brightnessAvailable) {
|
||||
value = DisplayService.brightnessLevel
|
||||
}
|
||||
}
|
||||
value: !isDragging ? root.targetBrightness : value
|
||||
|
||||
onSliderValueChanged: newValue => {
|
||||
if (DisplayService.brightnessAvailable) {
|
||||
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true)
|
||||
resetHideTimer()
|
||||
}
|
||||
}
|
||||
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse)
|
||||
if (DisplayService.brightnessAvailable) {
|
||||
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true);
|
||||
resetHideTimer();
|
||||
}
|
||||
}
|
||||
|
||||
onSliderDragFinished: finalValue => {
|
||||
if (DisplayService.brightnessAvailable) {
|
||||
DisplayService.setBrightness(finalValue, DisplayService.lastIpcDevice, true)
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DisplayService
|
||||
|
||||
function onBrightnessChanged(showOsd) {
|
||||
if (!brightnessSlider.pressed && brightnessSlider.value !== DisplayService.brightnessLevel) {
|
||||
brightnessSlider.value = DisplayService.brightnessLevel
|
||||
}
|
||||
}
|
||||
|
||||
function onDeviceSwitched() {
|
||||
if (!brightnessSlider.pressed && brightnessSlider.value !== DisplayService.brightnessLevel) {
|
||||
brightnessSlider.value = DisplayService.brightnessLevel
|
||||
}
|
||||
}
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,13 +141,13 @@ DankOSD {
|
||||
DankIcon {
|
||||
anchors.centerIn: parent
|
||||
name: {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
|
||||
return "brightness_medium"
|
||||
return "brightness_medium";
|
||||
} else if (deviceInfo.name.includes("kbd")) {
|
||||
return "keyboard"
|
||||
return "keyboard";
|
||||
} else {
|
||||
return "lightbulb"
|
||||
return "lightbulb";
|
||||
}
|
||||
}
|
||||
size: Theme.iconSize
|
||||
@@ -183,22 +163,26 @@ DankOSD {
|
||||
y: gap * 2 + Theme.iconSize
|
||||
|
||||
property bool dragging: false
|
||||
property int value: DisplayService.brightnessAvailable ? DisplayService.brightnessLevel : 0
|
||||
property int value: !dragging ? root.targetBrightness : value
|
||||
|
||||
readonly property int minimum: {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
||||
if (!deviceInfo) return 1
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
|
||||
if (isExponential) return 1
|
||||
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo)
|
||||
return 1;
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||
if (isExponential)
|
||||
return 1;
|
||||
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0;
|
||||
}
|
||||
|
||||
readonly property int maximum: {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
||||
if (!deviceInfo) return 100
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
|
||||
if (isExponential) return 100
|
||||
return deviceInfo.displayMax || 100
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
if (!deviceInfo)
|
||||
return 100;
|
||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||
if (isExponential)
|
||||
return 100;
|
||||
return deviceInfo.displayMax || 100;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
@@ -214,8 +198,8 @@ DankOSD {
|
||||
id: vertFill
|
||||
width: parent.width
|
||||
height: {
|
||||
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum)
|
||||
return ratio * parent.height
|
||||
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum);
|
||||
return ratio * parent.height;
|
||||
}
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
@@ -229,9 +213,9 @@ DankOSD {
|
||||
height: 8
|
||||
radius: Theme.cornerRadius
|
||||
y: {
|
||||
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum)
|
||||
const travel = parent.height - height
|
||||
return Math.max(0, Math.min(travel, travel * (1 - ratio)))
|
||||
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum);
|
||||
const travel = parent.height - height;
|
||||
return Math.max(0, Math.min(travel, travel * (1 - ratio)));
|
||||
}
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: Theme.primary
|
||||
@@ -248,50 +232,34 @@ DankOSD {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onContainsMouseChanged: {
|
||||
setChildHovered(containsMouse)
|
||||
setChildHovered(containsMouse);
|
||||
}
|
||||
|
||||
onPressed: mouse => {
|
||||
vertSlider.dragging = true
|
||||
updateBrightness(mouse)
|
||||
vertSlider.dragging = true;
|
||||
updateBrightness(mouse);
|
||||
}
|
||||
|
||||
onReleased: {
|
||||
vertSlider.dragging = false
|
||||
vertSlider.dragging = false;
|
||||
}
|
||||
|
||||
onPositionChanged: mouse => {
|
||||
if (pressed) {
|
||||
updateBrightness(mouse)
|
||||
updateBrightness(mouse);
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: mouse => {
|
||||
updateBrightness(mouse)
|
||||
updateBrightness(mouse);
|
||||
}
|
||||
|
||||
function updateBrightness(mouse) {
|
||||
if (DisplayService.brightnessAvailable) {
|
||||
const ratio = 1.0 - (mouse.y / height)
|
||||
const newValue = Math.round(vertSlider.minimum + ratio * (vertSlider.maximum - vertSlider.minimum))
|
||||
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true)
|
||||
resetHideTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: DisplayService
|
||||
|
||||
function onBrightnessChanged(showOsd) {
|
||||
if (!vertSlider.dragging && vertSlider.value !== DisplayService.brightnessLevel) {
|
||||
vertSlider.value = DisplayService.brightnessLevel
|
||||
}
|
||||
}
|
||||
|
||||
function onDeviceSwitched() {
|
||||
if (!vertSlider.dragging && vertSlider.value !== DisplayService.brightnessLevel) {
|
||||
vertSlider.value = DisplayService.brightnessLevel
|
||||
const ratio = 1.0 - (mouse.y / height);
|
||||
const newValue = Math.round(vertSlider.minimum + ratio * (vertSlider.maximum - vertSlider.minimum));
|
||||
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true);
|
||||
resetHideTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,10 +270,10 @@ DankOSD {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottomMargin: gap
|
||||
text: {
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
||||
const isExponential = deviceInfo ? SessionData.getBrightnessExponential(deviceInfo.id) : false
|
||||
const unit = (deviceInfo && deviceInfo.class === "ddc" && !isExponential) ? "" : "%"
|
||||
return vertSlider.value + unit
|
||||
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||
const isExponential = deviceInfo ? SessionData.getBrightnessExponential(deviceInfo.id) : false;
|
||||
const unit = (deviceInfo && deviceInfo.class === "ddc" && !isExponential) ? "" : "%";
|
||||
return vertSlider.value + unit;
|
||||
}
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
|
||||
@@ -186,6 +186,32 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
function triggerPopout() {
|
||||
if (pillClickAction) {
|
||||
if (pillClickAction.length === 0) {
|
||||
pillClickAction();
|
||||
return;
|
||||
}
|
||||
const pill = isVertical ? verticalPill : horizontalPill;
|
||||
const globalPos = pill.mapToGlobal(0, 0);
|
||||
const currentScreen = parentScreen || Screen;
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, pill.width);
|
||||
pillClickAction(pos.x, pos.y, pos.width, section, currentScreen);
|
||||
return;
|
||||
}
|
||||
if (!hasPopout)
|
||||
return;
|
||||
|
||||
const pill = isVertical ? verticalPill : horizontalPill;
|
||||
const globalPos = pill.visualContent.mapToGlobal(0, 0);
|
||||
const currentScreen = parentScreen || Screen;
|
||||
const barPosition = axis?.edge === "left" ? 2 : (axis?.edge === "right" ? 3 : (axis?.edge === "top" ? 0 : 1));
|
||||
const pos = SettingsData.getPopupTriggerPosition(globalPos, currentScreen, barThickness, pill.visualWidth, barSpacing, barPosition, barConfig);
|
||||
|
||||
pluginPopout.setTriggerPosition(pos.x, pos.y, pos.width, section, currentScreen, barPosition, barThickness, barSpacing, barConfig);
|
||||
pluginPopout.toggle();
|
||||
}
|
||||
|
||||
PluginPopout {
|
||||
id: pluginPopout
|
||||
contentWidth: root.popoutWidth
|
||||
|
||||
@@ -55,7 +55,7 @@ Column {
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
if (root.closePopout) {
|
||||
root.closePopout()
|
||||
root.closePopout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,13 +784,31 @@ Item {
|
||||
}
|
||||
|
||||
function handleControlCenterSettingChanged(sectionId, widgetIndex, settingName, value) {
|
||||
// Control Center settings are global, not per-widget instance
|
||||
if (settingName === "showNetworkIcon") {
|
||||
SettingsData.set("controlCenterShowNetworkIcon", value);
|
||||
} else if (settingName === "showBluetoothIcon") {
|
||||
SettingsData.set("controlCenterShowBluetoothIcon", value);
|
||||
} else if (settingName === "showAudioIcon") {
|
||||
SettingsData.set("controlCenterShowAudioIcon", value);
|
||||
switch (settingName) {
|
||||
case "showNetworkIcon":
|
||||
SettingsData.set("controlCenterShowNetworkIcon", value)
|
||||
break
|
||||
case "showBluetoothIcon":
|
||||
SettingsData.set("controlCenterShowBluetoothIcon", value)
|
||||
break
|
||||
case "showAudioIcon":
|
||||
SettingsData.set("controlCenterShowAudioIcon", value)
|
||||
break
|
||||
case "showVpnIcon":
|
||||
SettingsData.set("controlCenterShowVpnIcon", value)
|
||||
break
|
||||
case "showBrightnessIcon":
|
||||
SettingsData.set("controlCenterShowBrightnessIcon", value)
|
||||
break
|
||||
case "showMicIcon":
|
||||
SettingsData.set("controlCenterShowMicIcon", value)
|
||||
break
|
||||
case "showBatteryIcon":
|
||||
SettingsData.set("controlCenterShowBatteryIcon", value)
|
||||
break
|
||||
case "showPrinterIcon":
|
||||
SettingsData.set("controlCenterShowPrinterIcon", value)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -497,39 +497,69 @@ Column {
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: ccMenuButton
|
||||
visible: modelData.id === "controlCenterButton"
|
||||
buttonSize: 32
|
||||
iconName: "more_vert"
|
||||
iconSize: 18
|
||||
iconColor: Theme.outline
|
||||
onClicked: {
|
||||
console.log("Control Center three-dot button clicked for widget:", modelData.id);
|
||||
controlCenterContextMenu.widgetData = modelData;
|
||||
controlCenterContextMenu.sectionId = root.sectionId;
|
||||
controlCenterContextMenu.widgetIndex = index;
|
||||
// Position relative to the action buttons row, not the specific button
|
||||
var parentPos = parent.mapToItem(root, 0, 0);
|
||||
controlCenterContextMenu.x = parentPos.x - 210; // Position to the left with margin
|
||||
controlCenterContextMenu.y = parentPos.y - 10; // Slightly above
|
||||
|
||||
var buttonPos = ccMenuButton.mapToItem(root, 0, 0);
|
||||
var popupWidth = controlCenterContextMenu.width;
|
||||
var popupHeight = controlCenterContextMenu.height;
|
||||
|
||||
var xPos = buttonPos.x - popupWidth - Theme.spacingS;
|
||||
if (xPos < 0) {
|
||||
xPos = buttonPos.x + ccMenuButton.width + Theme.spacingS;
|
||||
}
|
||||
|
||||
var yPos = buttonPos.y - popupHeight / 2 + ccMenuButton.height / 2;
|
||||
if (yPos < 0) {
|
||||
yPos = Theme.spacingS;
|
||||
} else if (yPos + popupHeight > root.height) {
|
||||
yPos = root.height - popupHeight - Theme.spacingS;
|
||||
}
|
||||
|
||||
controlCenterContextMenu.x = xPos;
|
||||
controlCenterContextMenu.y = yPos;
|
||||
controlCenterContextMenu.open();
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
id: privacyMenuButton
|
||||
visible: modelData.id === "privacyIndicator"
|
||||
buttonSize: 32
|
||||
iconName: "more_vert"
|
||||
iconSize: 18
|
||||
iconColor: Theme.outline
|
||||
onClicked: {
|
||||
console.log("Privacy three-dot button clicked for widget:", modelData.id);
|
||||
privacyContextMenu.widgetData = modelData;
|
||||
privacyContextMenu.sectionId = root.sectionId;
|
||||
privacyContextMenu.widgetIndex = index;
|
||||
// Position relative to the action buttons row, not the specific button
|
||||
var parentPos = parent.mapToItem(root, 0, 0);
|
||||
privacyContextMenu.x = parentPos.x - 210; // Position to the left with margin
|
||||
privacyContextMenu.y = parentPos.y - 10; // Slightly above
|
||||
|
||||
var buttonPos = privacyMenuButton.mapToItem(root, 0, 0);
|
||||
var popupWidth = privacyContextMenu.width;
|
||||
var popupHeight = privacyContextMenu.height;
|
||||
|
||||
var xPos = buttonPos.x - popupWidth - Theme.spacingS;
|
||||
if (xPos < 0) {
|
||||
xPos = buttonPos.x + privacyMenuButton.width + Theme.spacingS;
|
||||
}
|
||||
|
||||
var yPos = buttonPos.y - popupHeight / 2 + privacyMenuButton.height / 2;
|
||||
if (yPos < 0) {
|
||||
yPos = Theme.spacingS;
|
||||
} else if (yPos + popupHeight > root.height) {
|
||||
yPos = root.height - popupHeight - Theme.spacingS;
|
||||
}
|
||||
|
||||
privacyContextMenu.x = xPos;
|
||||
privacyContextMenu.y = yPos;
|
||||
privacyContextMenu.open();
|
||||
}
|
||||
}
|
||||
@@ -699,21 +729,13 @@ Column {
|
||||
property string sectionId: ""
|
||||
property int widgetIndex: -1
|
||||
|
||||
width: 200
|
||||
height: 120
|
||||
width: 220
|
||||
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||
padding: 0
|
||||
modal: true
|
||||
focus: true
|
||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||
|
||||
onOpened: {
|
||||
console.log("Control Center context menu opened");
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
console.log("Control Center context menu closed");
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||
radius: Theme.cornerRadius
|
||||
@@ -722,168 +744,117 @@ Column {
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
|
||||
Column {
|
||||
id: menuColumn
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingS
|
||||
spacing: 2
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: networkToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
Repeater {
|
||||
model: [
|
||||
{
|
||||
icon: "lan",
|
||||
label: I18n.tr("Network"),
|
||||
setting: "showNetworkIcon",
|
||||
checked: SettingsData.controlCenterShowNetworkIcon
|
||||
},
|
||||
{
|
||||
icon: "vpn_lock",
|
||||
label: I18n.tr("VPN"),
|
||||
setting: "showVpnIcon",
|
||||
checked: SettingsData.controlCenterShowVpnIcon
|
||||
},
|
||||
{
|
||||
icon: "bluetooth",
|
||||
label: I18n.tr("Bluetooth"),
|
||||
setting: "showBluetoothIcon",
|
||||
checked: SettingsData.controlCenterShowBluetoothIcon
|
||||
},
|
||||
{
|
||||
icon: "volume_up",
|
||||
label: I18n.tr("Audio"),
|
||||
setting: "showAudioIcon",
|
||||
checked: SettingsData.controlCenterShowAudioIcon
|
||||
},
|
||||
{
|
||||
icon: "mic",
|
||||
label: I18n.tr("Microphone"),
|
||||
setting: "showMicIcon",
|
||||
checked: SettingsData.controlCenterShowMicIcon
|
||||
},
|
||||
{
|
||||
icon: "brightness_high",
|
||||
label: I18n.tr("Brightness"),
|
||||
setting: "showBrightnessIcon",
|
||||
checked: SettingsData.controlCenterShowBrightnessIcon
|
||||
},
|
||||
{
|
||||
icon: "battery_full",
|
||||
label: I18n.tr("Battery"),
|
||||
setting: "showBatteryIcon",
|
||||
checked: SettingsData.controlCenterShowBatteryIcon
|
||||
},
|
||||
{
|
||||
icon: "print",
|
||||
label: I18n.tr("Printer"),
|
||||
setting: "showPrinterIcon",
|
||||
checked: SettingsData.controlCenterShowPrinterIcon
|
||||
}
|
||||
]
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
delegate: Rectangle {
|
||||
required property var modelData
|
||||
required property int index
|
||||
|
||||
DankIcon {
|
||||
name: "lan"
|
||||
size: 16
|
||||
color: Theme.surfaceText
|
||||
width: menuColumn.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: toggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: modelData.icon
|
||||
size: 16
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.label
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Network Icon")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: networkToggle
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40
|
||||
height: 20
|
||||
checked: SettingsData.controlCenterShowNetworkIcon
|
||||
onToggled: {
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showNetworkIcon", toggled);
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: networkToggleArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
networkToggle.checked = !networkToggle.checked;
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showNetworkIcon", networkToggle.checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: bluetoothToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "bluetooth"
|
||||
size: 16
|
||||
color: Theme.surfaceText
|
||||
DankToggle {
|
||||
id: toggle
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40
|
||||
height: 20
|
||||
checked: modelData.checked
|
||||
onToggled: {
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggled);
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Bluetooth Icon")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: bluetoothToggle
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40
|
||||
height: 20
|
||||
checked: SettingsData.controlCenterShowBluetoothIcon
|
||||
onToggled: {
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showBluetoothIcon", toggled);
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: bluetoothToggleArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
bluetoothToggle.checked = !bluetoothToggle.checked;
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showBluetoothIcon", bluetoothToggle.checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: audioToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: Theme.spacingS
|
||||
|
||||
DankIcon {
|
||||
name: "volume_up"
|
||||
size: 16
|
||||
color: Theme.surfaceText
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Audio Icon")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Normal
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
DankToggle {
|
||||
id: audioToggle
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: Theme.spacingS
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 40
|
||||
height: 20
|
||||
checked: SettingsData.controlCenterShowAudioIcon
|
||||
onToggled: {
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showAudioIcon", toggled);
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: audioToggleArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
audioToggle.checked = !audioToggle.checked;
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, "showAudioIcon", audioToggle.checked);
|
||||
MouseArea {
|
||||
id: toggleArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onPressed: {
|
||||
toggle.checked = !toggle.checked;
|
||||
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggle.checked);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -932,7 +903,7 @@ Column {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: networkToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
color: "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -954,7 +925,7 @@ Column {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: networkToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
color: micToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -1006,7 +977,7 @@ Column {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: networkToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
color: cameraToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
@@ -1058,7 +1029,7 @@ Column {
|
||||
width: parent.width
|
||||
height: 32
|
||||
radius: Theme.cornerRadius
|
||||
color: networkToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
color: screenshareToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
||||
|
||||
Row {
|
||||
anchors.left: parent.left
|
||||
|
||||
166
quickshell/Services/BarWidgetService.qml
Normal file
166
quickshell/Services/BarWidgetService.qml
Normal file
@@ -0,0 +1,166 @@
|
||||
pragma Singleton
|
||||
pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Hyprland
|
||||
import Quickshell.I3
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property var widgetRegistry: ({})
|
||||
property var dankBarRepeater: null
|
||||
|
||||
signal widgetRegistered(string widgetId, string screenName)
|
||||
signal widgetUnregistered(string widgetId, string screenName)
|
||||
|
||||
function registerWidget(widgetId, screenName, widgetRef) {
|
||||
if (!widgetId || !screenName || !widgetRef)
|
||||
return;
|
||||
|
||||
if (!widgetRegistry[widgetId])
|
||||
widgetRegistry[widgetId] = {};
|
||||
|
||||
widgetRegistry[widgetId][screenName] = widgetRef;
|
||||
widgetRegistered(widgetId, screenName);
|
||||
}
|
||||
|
||||
function unregisterWidget(widgetId, screenName) {
|
||||
if (!widgetId || !screenName)
|
||||
return;
|
||||
if (!widgetRegistry[widgetId])
|
||||
return;
|
||||
|
||||
delete widgetRegistry[widgetId][screenName];
|
||||
if (Object.keys(widgetRegistry[widgetId]).length === 0)
|
||||
delete widgetRegistry[widgetId];
|
||||
|
||||
widgetUnregistered(widgetId, screenName);
|
||||
}
|
||||
|
||||
function getWidget(widgetId, screenName) {
|
||||
if (!widgetRegistry[widgetId])
|
||||
return null;
|
||||
if (screenName)
|
||||
return widgetRegistry[widgetId][screenName] || null;
|
||||
|
||||
const screens = Object.keys(widgetRegistry[widgetId]);
|
||||
return screens.length > 0 ? widgetRegistry[widgetId][screens[0]] : null;
|
||||
}
|
||||
|
||||
function getWidgetOnFocusedScreen(widgetId) {
|
||||
if (!widgetRegistry[widgetId])
|
||||
return null;
|
||||
|
||||
const focusedScreen = getFocusedScreenName();
|
||||
if (focusedScreen && widgetRegistry[widgetId][focusedScreen])
|
||||
return widgetRegistry[widgetId][focusedScreen];
|
||||
|
||||
const screens = Object.keys(widgetRegistry[widgetId]);
|
||||
return screens.length > 0 ? widgetRegistry[widgetId][screens[0]] : null;
|
||||
}
|
||||
|
||||
function getFocusedScreenName() {
|
||||
if (CompositorService.isHyprland && Hyprland.focusedWorkspace?.monitor)
|
||||
return Hyprland.focusedWorkspace.monitor.name;
|
||||
if (CompositorService.isNiri && NiriService.currentOutput)
|
||||
return NiriService.currentOutput;
|
||||
if (CompositorService.isSway) {
|
||||
const focusedWs = I3.workspaces?.values?.find(ws => ws.focused === true);
|
||||
return focusedWs?.monitor?.name || "";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function getRegisteredWidgetIds() {
|
||||
return Object.keys(widgetRegistry);
|
||||
}
|
||||
|
||||
function hasWidget(widgetId) {
|
||||
return widgetRegistry[widgetId] && Object.keys(widgetRegistry[widgetId]).length > 0;
|
||||
}
|
||||
|
||||
function triggerWidgetPopout(widgetId) {
|
||||
const widget = getWidgetOnFocusedScreen(widgetId);
|
||||
if (!widget)
|
||||
return false;
|
||||
|
||||
if (typeof widget.triggerPopout === "function") {
|
||||
widget.triggerPopout();
|
||||
return true;
|
||||
}
|
||||
|
||||
const signalMap = {
|
||||
"battery": "toggleBatteryPopup",
|
||||
"vpn": "toggleVpnPopup",
|
||||
"layout": "toggleLayoutPopup",
|
||||
"clock": "clockClicked",
|
||||
"cpuUsage": "cpuClicked",
|
||||
"memUsage": "ramClicked",
|
||||
"cpuTemp": "cpuTempClicked",
|
||||
"gpuTemp": "gpuTempClicked"
|
||||
};
|
||||
|
||||
const signalName = signalMap[widgetId];
|
||||
if (signalName && typeof widget[signalName] === "function") {
|
||||
widget[signalName]();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof widget.clicked === "function") {
|
||||
widget.clicked();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (widget.popoutTarget?.toggle) {
|
||||
widget.popoutTarget.toggle();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getBarWindowForScreen(screenName) {
|
||||
if (!dankBarRepeater)
|
||||
return null;
|
||||
|
||||
for (var i = 0; i < dankBarRepeater.count; i++) {
|
||||
const loader = dankBarRepeater.itemAt(i);
|
||||
if (!loader?.item)
|
||||
continue;
|
||||
|
||||
const barItem = loader.item;
|
||||
if (!barItem.barVariants?.instances)
|
||||
continue;
|
||||
|
||||
for (var j = 0; j < barItem.barVariants.instances.length; j++) {
|
||||
const barInstance = barItem.barVariants.instances[j];
|
||||
if (barInstance.modelData?.name === screenName)
|
||||
return barInstance;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getBarWindowOnFocusedScreen() {
|
||||
const focusedScreen = getFocusedScreenName();
|
||||
if (!focusedScreen)
|
||||
return getFirstBarWindow();
|
||||
return getBarWindowForScreen(focusedScreen) || getFirstBarWindow();
|
||||
}
|
||||
|
||||
function getFirstBarWindow() {
|
||||
if (!dankBarRepeater || dankBarRepeater.count === 0)
|
||||
return null;
|
||||
|
||||
const loader = dankBarRepeater.itemAt(0);
|
||||
if (!loader?.item)
|
||||
return null;
|
||||
|
||||
const barItem = loader.item;
|
||||
if (!barItem.barVariants?.instances || barItem.barVariants.instances.length === 0)
|
||||
return null;
|
||||
|
||||
return barItem.barVariants.instances[0];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,8 @@
|
||||
pragma Singleton
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
@@ -29,6 +26,9 @@ Singleton {
|
||||
property string wifiConnectionUuid: activeService?.wifiConnectionUuid ?? ""
|
||||
property string wifiDevicePath: activeService?.wifiDevicePath ?? ""
|
||||
property string activeAccessPointPath: activeService?.activeAccessPointPath ?? ""
|
||||
property var wifiDevices: activeService?.wifiDevices ?? []
|
||||
property string wifiDeviceOverride: activeService?.wifiDeviceOverride ?? ""
|
||||
property string connectingDevice: activeService?.connectingDevice ?? ""
|
||||
|
||||
property string currentWifiSSID: activeService?.currentWifiSSID ?? ""
|
||||
property int wifiSignalStrength: activeService?.wifiSignalStrength ?? 0
|
||||
@@ -70,6 +70,14 @@ Singleton {
|
||||
|
||||
property bool subscriptionConnected: activeService?.subscriptionConnected ?? false
|
||||
|
||||
property var vpnProfiles: activeService?.vpnProfiles ?? []
|
||||
property var vpnActive: activeService?.vpnActive ?? []
|
||||
property bool vpnAvailable: activeService?.vpnAvailable ?? false
|
||||
property bool vpnIsBusy: activeService?.vpnIsBusy ?? false
|
||||
property bool vpnConnected: activeService?.vpnConnected ?? false
|
||||
property string vpnActiveUuid: activeService?.activeUuid ?? ""
|
||||
property string vpnActiveName: activeService?.activeName ?? ""
|
||||
|
||||
property string credentialsToken: activeService?.credentialsToken ?? ""
|
||||
property string credentialsSSID: activeService?.credentialsSSID ?? ""
|
||||
property string credentialsSetting: activeService?.credentialsSetting ?? ""
|
||||
@@ -88,12 +96,12 @@ Singleton {
|
||||
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
|
||||
|
||||
Component.onCompleted: {
|
||||
console.info("NetworkService: Initializing...")
|
||||
console.info("NetworkService: Initializing...");
|
||||
if (!socketPath || socketPath.length === 0) {
|
||||
console.info("NetworkService: DMS_SOCKET not set, using LegacyNetworkService")
|
||||
useLegacyService()
|
||||
console.info("NetworkService: DMS_SOCKET not set, using LegacyNetworkService");
|
||||
useLegacyService();
|
||||
} else {
|
||||
console.log("NetworkService: DMS_SOCKET found, waiting for capabilities...")
|
||||
console.log("NetworkService: DMS_SOCKET found, waiting for capabilities...");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,191 +110,197 @@ Singleton {
|
||||
|
||||
function onNetworkAvailableChanged() {
|
||||
if (!activeService && DMSNetworkService.networkAvailable) {
|
||||
console.info("NetworkService: Network capability detected, using DMSNetworkService")
|
||||
activeService = DMSNetworkService
|
||||
usingLegacy = false
|
||||
console.info("NetworkService: Switched to DMSNetworkService, networkAvailable:", networkAvailable)
|
||||
connectSignals()
|
||||
console.info("NetworkService: Network capability detected, using DMSNetworkService");
|
||||
activeService = DMSNetworkService;
|
||||
usingLegacy = false;
|
||||
console.info("NetworkService: Switched to DMSNetworkService, networkAvailable:", networkAvailable);
|
||||
connectSignals();
|
||||
} else if (!activeService && !DMSNetworkService.networkAvailable && socketPath && socketPath.length > 0) {
|
||||
console.info("NetworkService: Network capability not available in DMS, using LegacyNetworkService")
|
||||
useLegacyService()
|
||||
console.info("NetworkService: Network capability not available in DMS, using LegacyNetworkService");
|
||||
useLegacyService();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function useLegacyService() {
|
||||
activeService = LegacyNetworkService
|
||||
usingLegacy = true
|
||||
console.info("NetworkService: Switched to LegacyNetworkService, networkAvailable:", networkAvailable)
|
||||
activeService = LegacyNetworkService;
|
||||
usingLegacy = true;
|
||||
console.info("NetworkService: Switched to LegacyNetworkService, networkAvailable:", networkAvailable);
|
||||
if (LegacyNetworkService.activate) {
|
||||
LegacyNetworkService.activate()
|
||||
LegacyNetworkService.activate();
|
||||
}
|
||||
connectSignals()
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
function connectSignals() {
|
||||
if (activeService) {
|
||||
if (activeService.networksUpdated) {
|
||||
activeService.networksUpdated.connect(root.networksUpdated)
|
||||
activeService.networksUpdated.connect(root.networksUpdated);
|
||||
}
|
||||
if (activeService.connectionChanged) {
|
||||
activeService.connectionChanged.connect(root.connectionChanged)
|
||||
activeService.connectionChanged.connect(root.connectionChanged);
|
||||
}
|
||||
if (activeService.credentialsNeeded) {
|
||||
activeService.credentialsNeeded.connect(root.credentialsNeeded)
|
||||
activeService.credentialsNeeded.connect(root.credentialsNeeded);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addRef() {
|
||||
if (activeService && activeService.addRef) {
|
||||
activeService.addRef()
|
||||
activeService.addRef();
|
||||
}
|
||||
}
|
||||
|
||||
function removeRef() {
|
||||
if (activeService && activeService.removeRef) {
|
||||
activeService.removeRef()
|
||||
activeService.removeRef();
|
||||
}
|
||||
}
|
||||
|
||||
function getState() {
|
||||
if (activeService && activeService.getState) {
|
||||
activeService.getState()
|
||||
activeService.getState();
|
||||
}
|
||||
}
|
||||
|
||||
function scanWifi() {
|
||||
if (activeService && activeService.scanWifi) {
|
||||
activeService.scanWifi()
|
||||
activeService.scanWifi();
|
||||
}
|
||||
}
|
||||
|
||||
function scanWifiNetworks() {
|
||||
if (activeService && activeService.scanWifiNetworks) {
|
||||
activeService.scanWifiNetworks()
|
||||
activeService.scanWifiNetworks();
|
||||
}
|
||||
}
|
||||
|
||||
function connectToWifi(ssid, password = "", username = "", anonymousIdentity = "", domainSuffixMatch = "") {
|
||||
if (activeService && activeService.connectToWifi) {
|
||||
activeService.connectToWifi(ssid, password, username, anonymousIdentity, domainSuffixMatch)
|
||||
activeService.connectToWifi(ssid, password, username, anonymousIdentity, domainSuffixMatch);
|
||||
}
|
||||
}
|
||||
|
||||
function disconnectWifi() {
|
||||
if (activeService && activeService.disconnectWifi) {
|
||||
activeService.disconnectWifi()
|
||||
activeService.disconnectWifi();
|
||||
}
|
||||
}
|
||||
|
||||
function forgetWifiNetwork(ssid) {
|
||||
if (activeService && activeService.forgetWifiNetwork) {
|
||||
activeService.forgetWifiNetwork(ssid)
|
||||
activeService.forgetWifiNetwork(ssid);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleWifiRadio() {
|
||||
if (activeService && activeService.toggleWifiRadio) {
|
||||
activeService.toggleWifiRadio()
|
||||
activeService.toggleWifiRadio();
|
||||
}
|
||||
}
|
||||
|
||||
function enableWifiDevice() {
|
||||
if (activeService && activeService.enableWifiDevice) {
|
||||
activeService.enableWifiDevice()
|
||||
activeService.enableWifiDevice();
|
||||
}
|
||||
}
|
||||
|
||||
function setNetworkPreference(preference) {
|
||||
if (activeService && activeService.setNetworkPreference) {
|
||||
activeService.setNetworkPreference(preference)
|
||||
activeService.setNetworkPreference(preference);
|
||||
}
|
||||
}
|
||||
|
||||
function setConnectionPriority(type) {
|
||||
if (activeService && activeService.setConnectionPriority) {
|
||||
activeService.setConnectionPriority(type)
|
||||
activeService.setConnectionPriority(type);
|
||||
}
|
||||
}
|
||||
|
||||
function connectToWifiAndSetPreference(ssid, password, username = "", anonymousIdentity = "", domainSuffixMatch = "") {
|
||||
if (activeService && activeService.connectToWifiAndSetPreference) {
|
||||
activeService.connectToWifiAndSetPreference(ssid, password, username, anonymousIdentity, domainSuffixMatch)
|
||||
activeService.connectToWifiAndSetPreference(ssid, password, username, anonymousIdentity, domainSuffixMatch);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleNetworkConnection(type) {
|
||||
if (activeService && activeService.toggleNetworkConnection) {
|
||||
activeService.toggleNetworkConnection(type)
|
||||
activeService.toggleNetworkConnection(type);
|
||||
}
|
||||
}
|
||||
|
||||
function startAutoScan() {
|
||||
if (activeService && activeService.startAutoScan) {
|
||||
activeService.startAutoScan()
|
||||
activeService.startAutoScan();
|
||||
}
|
||||
}
|
||||
|
||||
function stopAutoScan() {
|
||||
if (activeService && activeService.stopAutoScan) {
|
||||
activeService.stopAutoScan()
|
||||
activeService.stopAutoScan();
|
||||
}
|
||||
}
|
||||
|
||||
function fetchNetworkInfo(ssid) {
|
||||
if (activeService && activeService.fetchNetworkInfo) {
|
||||
activeService.fetchNetworkInfo(ssid)
|
||||
activeService.fetchNetworkInfo(ssid);
|
||||
}
|
||||
}
|
||||
|
||||
function fetchWiredNetworkInfo(uuid) {
|
||||
if (activeService && activeService.fetchWiredNetworkInfo) {
|
||||
activeService.fetchWiredNetworkInfo(uuid)
|
||||
activeService.fetchWiredNetworkInfo(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
function getNetworkInfo(ssid) {
|
||||
if (activeService && activeService.getNetworkInfo) {
|
||||
return activeService.getNetworkInfo(ssid)
|
||||
return activeService.getNetworkInfo(ssid);
|
||||
}
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
function getWiredNetworkInfo(uuid) {
|
||||
if (activeService && activeService.getWiredNetworkInfo) {
|
||||
return activeService.getWiredNetworkInfo(uuid)
|
||||
return activeService.getWiredNetworkInfo(uuid);
|
||||
}
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
||||
function refreshNetworkState() {
|
||||
if (activeService && activeService.refreshNetworkState) {
|
||||
activeService.refreshNetworkState()
|
||||
activeService.refreshNetworkState();
|
||||
}
|
||||
}
|
||||
|
||||
function connectToSpecificWiredConfig(uuid) {
|
||||
if (activeService && activeService.connectToSpecificWiredConfig) {
|
||||
activeService.connectToSpecificWiredConfig(uuid)
|
||||
activeService.connectToSpecificWiredConfig(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
function submitCredentials(token, secrets, save) {
|
||||
if (activeService && activeService.submitCredentials) {
|
||||
activeService.submitCredentials(token, secrets, save)
|
||||
activeService.submitCredentials(token, secrets, save);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelCredentials(token) {
|
||||
if (activeService && activeService.cancelCredentials) {
|
||||
activeService.cancelCredentials(token)
|
||||
activeService.cancelCredentials(token);
|
||||
}
|
||||
}
|
||||
|
||||
function setWifiAutoconnect(ssid, autoconnect) {
|
||||
if (activeService && activeService.setWifiAutoconnect) {
|
||||
activeService.setWifiAutoconnect(ssid, autoconnect)
|
||||
activeService.setWifiAutoconnect(ssid, autoconnect);
|
||||
}
|
||||
}
|
||||
|
||||
function setWifiDeviceOverride(deviceName) {
|
||||
if (activeService && activeService.setWifiDeviceOverride) {
|
||||
activeService.setWifiDeviceOverride(deviceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,12 +4,21 @@ pragma ComponentBehavior: Bound
|
||||
import QtQuick
|
||||
import Quickshell
|
||||
import Quickshell.Io
|
||||
import qs.Common
|
||||
import Quickshell.Wayland
|
||||
|
||||
Singleton {
|
||||
id: root
|
||||
|
||||
property bool cyclingActive: false
|
||||
readonly property bool anyFullscreen: {
|
||||
if (!ToplevelManager.toplevels?.values)
|
||||
return false;
|
||||
for (const toplevel of ToplevelManager.toplevels.values) {
|
||||
if (toplevel.fullscreen)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
property string cachedCyclingTime: SessionData.wallpaperCyclingTime
|
||||
property int cachedCyclingInterval: SessionData.wallpaperCyclingInterval
|
||||
property string lastTimeCheck: ""
|
||||
@@ -24,8 +33,8 @@ Singleton {
|
||||
running: false
|
||||
repeat: true
|
||||
onTriggered: {
|
||||
if (typeof WallpaperCyclingService !== "undefined" && targetScreen !== "") {
|
||||
WallpaperCyclingService.cycleNextForMonitor(targetScreen)
|
||||
if (typeof WallpaperCyclingService !== "undefined" && targetScreen !== "" && !WallpaperCyclingService.anyFullscreen) {
|
||||
WallpaperCyclingService.cycleNextForMonitor(targetScreen);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,24 +50,26 @@ Singleton {
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text && text.trim()) {
|
||||
const files = text.trim().split('\n').filter(file => file.length > 0)
|
||||
if (files.length <= 1) return
|
||||
const wallpaperList = files.sort()
|
||||
const currentPath = currentWallpaper
|
||||
let currentIndex = wallpaperList.findIndex(path => path === currentPath)
|
||||
if (currentIndex === -1) currentIndex = 0
|
||||
let targetIndex
|
||||
const files = text.trim().split('\n').filter(file => file.length > 0);
|
||||
if (files.length <= 1)
|
||||
return;
|
||||
const wallpaperList = files.sort();
|
||||
const currentPath = currentWallpaper;
|
||||
let currentIndex = wallpaperList.findIndex(path => path === currentPath);
|
||||
if (currentIndex === -1)
|
||||
currentIndex = 0;
|
||||
let targetIndex;
|
||||
if (goToPrevious) {
|
||||
targetIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1
|
||||
targetIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1;
|
||||
} else {
|
||||
targetIndex = (currentIndex + 1) % wallpaperList.length
|
||||
targetIndex = (currentIndex + 1) % wallpaperList.length;
|
||||
}
|
||||
const targetWallpaper = wallpaperList[targetIndex]
|
||||
const targetWallpaper = wallpaperList[targetIndex];
|
||||
if (targetWallpaper && targetWallpaper !== currentPath) {
|
||||
if (targetScreenName) {
|
||||
SessionData.setMonitorWallpaper(targetScreenName, targetWallpaper)
|
||||
SessionData.setMonitorWallpaper(targetScreenName, targetWallpaper);
|
||||
} else {
|
||||
SessionData.setWallpaper(targetWallpaper)
|
||||
SessionData.setWallpaper(targetWallpaper);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,205 +82,205 @@ Singleton {
|
||||
target: SessionData
|
||||
|
||||
function onWallpaperCyclingEnabledChanged() {
|
||||
updateCyclingState()
|
||||
updateCyclingState();
|
||||
}
|
||||
|
||||
function onWallpaperCyclingModeChanged() {
|
||||
updateCyclingState()
|
||||
updateCyclingState();
|
||||
}
|
||||
|
||||
function onWallpaperCyclingIntervalChanged() {
|
||||
cachedCyclingInterval = SessionData.wallpaperCyclingInterval
|
||||
cachedCyclingInterval = SessionData.wallpaperCyclingInterval;
|
||||
if (SessionData.wallpaperCyclingMode === "interval") {
|
||||
updateCyclingState()
|
||||
updateCyclingState();
|
||||
}
|
||||
}
|
||||
|
||||
function onWallpaperCyclingTimeChanged() {
|
||||
cachedCyclingTime = SessionData.wallpaperCyclingTime
|
||||
cachedCyclingTime = SessionData.wallpaperCyclingTime;
|
||||
if (SessionData.wallpaperCyclingMode === "time") {
|
||||
updateCyclingState()
|
||||
updateCyclingState();
|
||||
}
|
||||
}
|
||||
|
||||
function onPerMonitorWallpaperChanged() {
|
||||
updateCyclingState()
|
||||
updateCyclingState();
|
||||
}
|
||||
|
||||
function onMonitorCyclingSettingsChanged() {
|
||||
updateCyclingState()
|
||||
updateCyclingState();
|
||||
}
|
||||
}
|
||||
|
||||
function updateCyclingState() {
|
||||
if (SessionData.perMonitorWallpaper) {
|
||||
stopCycling()
|
||||
updatePerMonitorCycling()
|
||||
stopCycling();
|
||||
updatePerMonitorCycling();
|
||||
} else if (SessionData.wallpaperCyclingEnabled && SessionData.wallpaperPath) {
|
||||
startCycling()
|
||||
stopAllMonitorCycling()
|
||||
startCycling();
|
||||
stopAllMonitorCycling();
|
||||
} else {
|
||||
stopCycling()
|
||||
stopAllMonitorCycling()
|
||||
stopCycling();
|
||||
stopAllMonitorCycling();
|
||||
}
|
||||
}
|
||||
|
||||
function updatePerMonitorCycling() {
|
||||
if (typeof Quickshell === "undefined") return
|
||||
|
||||
var screens = Quickshell.screens
|
||||
if (typeof Quickshell === "undefined")
|
||||
return;
|
||||
var screens = Quickshell.screens;
|
||||
for (var i = 0; i < screens.length; i++) {
|
||||
var screenName = screens[i].name
|
||||
var settings = SessionData.getMonitorCyclingSettings(screenName)
|
||||
var wallpaper = SessionData.getMonitorWallpaper(screenName)
|
||||
var screenName = screens[i].name;
|
||||
var settings = SessionData.getMonitorCyclingSettings(screenName);
|
||||
var wallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||
|
||||
if (settings.enabled && wallpaper && !wallpaper.startsWith("#")) {
|
||||
startMonitorCycling(screenName, settings)
|
||||
startMonitorCycling(screenName, settings);
|
||||
} else {
|
||||
stopMonitorCycling(screenName)
|
||||
stopMonitorCycling(screenName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function stopAllMonitorCycling() {
|
||||
var screenNames = Object.keys(monitorTimers)
|
||||
var screenNames = Object.keys(monitorTimers);
|
||||
for (var i = 0; i < screenNames.length; i++) {
|
||||
stopMonitorCycling(screenNames[i])
|
||||
stopMonitorCycling(screenNames[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function startCycling() {
|
||||
if (SessionData.wallpaperCyclingMode === "interval") {
|
||||
intervalTimer.interval = cachedCyclingInterval * 1000
|
||||
intervalTimer.start()
|
||||
cyclingActive = true
|
||||
intervalTimer.interval = cachedCyclingInterval * 1000;
|
||||
intervalTimer.start();
|
||||
cyclingActive = true;
|
||||
} else if (SessionData.wallpaperCyclingMode === "time") {
|
||||
cyclingActive = true
|
||||
checkTimeBasedCycling()
|
||||
cyclingActive = true;
|
||||
checkTimeBasedCycling();
|
||||
}
|
||||
}
|
||||
|
||||
function stopCycling() {
|
||||
intervalTimer.stop()
|
||||
cyclingActive = false
|
||||
intervalTimer.stop();
|
||||
cyclingActive = false;
|
||||
}
|
||||
|
||||
function startMonitorCycling(screenName, settings) {
|
||||
if (settings.mode === "interval") {
|
||||
var timer = monitorTimers[screenName]
|
||||
var timer = monitorTimers[screenName];
|
||||
if (!timer && monitorTimerComponent && monitorTimerComponent.status === Component.Ready) {
|
||||
var newTimers = Object.assign({}, monitorTimers)
|
||||
newTimers[screenName] = monitorTimerComponent.createObject(root)
|
||||
newTimers[screenName].targetScreen = screenName
|
||||
monitorTimers = newTimers
|
||||
timer = monitorTimers[screenName]
|
||||
var newTimers = Object.assign({}, monitorTimers);
|
||||
newTimers[screenName] = monitorTimerComponent.createObject(root);
|
||||
newTimers[screenName].targetScreen = screenName;
|
||||
monitorTimers = newTimers;
|
||||
timer = monitorTimers[screenName];
|
||||
}
|
||||
if (timer) {
|
||||
timer.interval = settings.interval * 1000
|
||||
timer.start()
|
||||
timer.interval = settings.interval * 1000;
|
||||
timer.start();
|
||||
}
|
||||
} else if (settings.mode === "time") {
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
||||
newChecks[screenName] = ""
|
||||
monitorLastTimeChecks = newChecks
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks);
|
||||
newChecks[screenName] = "";
|
||||
monitorLastTimeChecks = newChecks;
|
||||
}
|
||||
}
|
||||
|
||||
function stopMonitorCycling(screenName) {
|
||||
var timer = monitorTimers[screenName]
|
||||
var timer = monitorTimers[screenName];
|
||||
if (timer) {
|
||||
timer.stop()
|
||||
timer.destroy()
|
||||
var newTimers = Object.assign({}, monitorTimers)
|
||||
delete newTimers[screenName]
|
||||
monitorTimers = newTimers
|
||||
timer.stop();
|
||||
timer.destroy();
|
||||
var newTimers = Object.assign({}, monitorTimers);
|
||||
delete newTimers[screenName];
|
||||
monitorTimers = newTimers;
|
||||
}
|
||||
|
||||
var process = monitorProcesses[screenName]
|
||||
var process = monitorProcesses[screenName];
|
||||
if (process) {
|
||||
process.destroy()
|
||||
var newProcesses = Object.assign({}, monitorProcesses)
|
||||
delete newProcesses[screenName]
|
||||
monitorProcesses = newProcesses
|
||||
process.destroy();
|
||||
var newProcesses = Object.assign({}, monitorProcesses);
|
||||
delete newProcesses[screenName];
|
||||
monitorProcesses = newProcesses;
|
||||
}
|
||||
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
||||
delete newChecks[screenName]
|
||||
monitorLastTimeChecks = newChecks
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks);
|
||||
delete newChecks[screenName];
|
||||
monitorLastTimeChecks = newChecks;
|
||||
}
|
||||
|
||||
function cycleToNextWallpaper(screenName, wallpaperPath) {
|
||||
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath
|
||||
if (!currentWallpaper) return
|
||||
|
||||
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'))
|
||||
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath;
|
||||
if (!currentWallpaper)
|
||||
return;
|
||||
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'));
|
||||
|
||||
if (screenName && monitorProcessComponent && monitorProcessComponent.status === Component.Ready) {
|
||||
// Use per-monitor process
|
||||
var process = monitorProcesses[screenName]
|
||||
var process = monitorProcesses[screenName];
|
||||
if (!process) {
|
||||
var newProcesses = Object.assign({}, monitorProcesses)
|
||||
newProcesses[screenName] = monitorProcessComponent.createObject(root)
|
||||
monitorProcesses = newProcesses
|
||||
process = monitorProcesses[screenName]
|
||||
var newProcesses = Object.assign({}, monitorProcesses);
|
||||
newProcesses[screenName] = monitorProcessComponent.createObject(root);
|
||||
monitorProcesses = newProcesses;
|
||||
process = monitorProcesses[screenName];
|
||||
}
|
||||
|
||||
if (process) {
|
||||
process.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
|
||||
process.targetScreenName = screenName
|
||||
process.currentWallpaper = currentWallpaper
|
||||
process.goToPrevious = false
|
||||
process.running = true
|
||||
process.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`];
|
||||
process.targetScreenName = screenName;
|
||||
process.currentWallpaper = currentWallpaper;
|
||||
process.goToPrevious = false;
|
||||
process.running = true;
|
||||
}
|
||||
} else {
|
||||
// Use global process for fallback
|
||||
cyclingProcess.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
|
||||
cyclingProcess.targetScreenName = screenName || ""
|
||||
cyclingProcess.currentWallpaper = currentWallpaper
|
||||
cyclingProcess.running = true
|
||||
cyclingProcess.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`];
|
||||
cyclingProcess.targetScreenName = screenName || "";
|
||||
cyclingProcess.currentWallpaper = currentWallpaper;
|
||||
cyclingProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
function cycleToPrevWallpaper(screenName, wallpaperPath) {
|
||||
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath
|
||||
if (!currentWallpaper) return
|
||||
|
||||
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'))
|
||||
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath;
|
||||
if (!currentWallpaper)
|
||||
return;
|
||||
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'));
|
||||
|
||||
if (screenName && monitorProcessComponent && monitorProcessComponent.status === Component.Ready) {
|
||||
// Use per-monitor process (same as next, but with prev flag)
|
||||
var process = monitorProcesses[screenName]
|
||||
var process = monitorProcesses[screenName];
|
||||
if (!process) {
|
||||
var newProcesses = Object.assign({}, monitorProcesses)
|
||||
newProcesses[screenName] = monitorProcessComponent.createObject(root)
|
||||
monitorProcesses = newProcesses
|
||||
process = monitorProcesses[screenName]
|
||||
var newProcesses = Object.assign({}, monitorProcesses);
|
||||
newProcesses[screenName] = monitorProcessComponent.createObject(root);
|
||||
monitorProcesses = newProcesses;
|
||||
process = monitorProcesses[screenName];
|
||||
}
|
||||
|
||||
if (process) {
|
||||
process.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
|
||||
process.targetScreenName = screenName
|
||||
process.currentWallpaper = currentWallpaper
|
||||
process.goToPrevious = true
|
||||
process.running = true
|
||||
process.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`];
|
||||
process.targetScreenName = screenName;
|
||||
process.currentWallpaper = currentWallpaper;
|
||||
process.goToPrevious = true;
|
||||
process.running = true;
|
||||
}
|
||||
} else {
|
||||
// Use global process for fallback
|
||||
prevCyclingProcess.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`]
|
||||
prevCyclingProcess.targetScreenName = screenName || ""
|
||||
prevCyclingProcess.currentWallpaper = currentWallpaper
|
||||
prevCyclingProcess.running = true
|
||||
prevCyclingProcess.command = ["sh", "-c", `ls -1 "${wallpaperDir}"/*.jpg "${wallpaperDir}"/*.jpeg "${wallpaperDir}"/*.png "${wallpaperDir}"/*.bmp "${wallpaperDir}"/*.gif "${wallpaperDir}"/*.webp 2>/dev/null | sort`];
|
||||
prevCyclingProcess.targetScreenName = screenName || "";
|
||||
prevCyclingProcess.currentWallpaper = currentWallpaper;
|
||||
prevCyclingProcess.running = true;
|
||||
}
|
||||
}
|
||||
|
||||
function cycleNextManually() {
|
||||
if (SessionData.wallpaperPath) {
|
||||
cycleToNextWallpaper()
|
||||
cycleToNextWallpaper();
|
||||
// Restart timers if cycling is active
|
||||
if (cyclingActive && SessionData.wallpaperCyclingEnabled) {
|
||||
if (SessionData.wallpaperCyclingMode === "interval") {
|
||||
intervalTimer.interval = cachedCyclingInterval * 1000
|
||||
intervalTimer.restart()
|
||||
intervalTimer.interval = cachedCyclingInterval * 1000;
|
||||
intervalTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -277,71 +288,73 @@ Singleton {
|
||||
|
||||
function cyclePrevManually() {
|
||||
if (SessionData.wallpaperPath) {
|
||||
cycleToPrevWallpaper()
|
||||
cycleToPrevWallpaper();
|
||||
// Restart timers if cycling is active
|
||||
if (cyclingActive && SessionData.wallpaperCyclingEnabled) {
|
||||
if (SessionData.wallpaperCyclingMode === "interval") {
|
||||
intervalTimer.interval = cachedCyclingInterval * 1000
|
||||
intervalTimer.restart()
|
||||
intervalTimer.interval = cachedCyclingInterval * 1000;
|
||||
intervalTimer.restart();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cycleNextForMonitor(screenName) {
|
||||
if (!screenName) return
|
||||
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName)
|
||||
if (!screenName)
|
||||
return;
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||
if (currentWallpaper) {
|
||||
cycleToNextWallpaper(screenName, currentWallpaper)
|
||||
cycleToNextWallpaper(screenName, currentWallpaper);
|
||||
}
|
||||
}
|
||||
|
||||
function cyclePrevForMonitor(screenName) {
|
||||
if (!screenName) return
|
||||
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName)
|
||||
if (!screenName)
|
||||
return;
|
||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||
if (currentWallpaper) {
|
||||
cycleToPrevWallpaper(screenName, currentWallpaper)
|
||||
cycleToPrevWallpaper(screenName, currentWallpaper);
|
||||
}
|
||||
}
|
||||
|
||||
function checkTimeBasedCycling() {
|
||||
const currentTime = Qt.formatTime(systemClock.date, "hh:mm")
|
||||
if (anyFullscreen)
|
||||
return;
|
||||
const currentTime = Qt.formatTime(systemClock.date, "hh:mm");
|
||||
|
||||
if (!SessionData.perMonitorWallpaper) {
|
||||
if (currentTime === cachedCyclingTime && currentTime !== lastTimeCheck) {
|
||||
lastTimeCheck = currentTime
|
||||
cycleToNextWallpaper()
|
||||
lastTimeCheck = currentTime;
|
||||
cycleToNextWallpaper();
|
||||
} else if (currentTime !== cachedCyclingTime) {
|
||||
lastTimeCheck = ""
|
||||
lastTimeCheck = "";
|
||||
}
|
||||
} else {
|
||||
checkPerMonitorTimeBasedCycling(currentTime)
|
||||
checkPerMonitorTimeBasedCycling(currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
function checkPerMonitorTimeBasedCycling(currentTime) {
|
||||
if (typeof Quickshell === "undefined") return
|
||||
|
||||
var screens = Quickshell.screens
|
||||
if (typeof Quickshell === "undefined")
|
||||
return;
|
||||
var screens = Quickshell.screens;
|
||||
for (var i = 0; i < screens.length; i++) {
|
||||
var screenName = screens[i].name
|
||||
var settings = SessionData.getMonitorCyclingSettings(screenName)
|
||||
var wallpaper = SessionData.getMonitorWallpaper(screenName)
|
||||
var screenName = screens[i].name;
|
||||
var settings = SessionData.getMonitorCyclingSettings(screenName);
|
||||
var wallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||
|
||||
if (settings.enabled && settings.mode === "time" && wallpaper && !wallpaper.startsWith("#")) {
|
||||
var lastCheck = monitorLastTimeChecks[screenName] || ""
|
||||
var lastCheck = monitorLastTimeChecks[screenName] || "";
|
||||
|
||||
if (currentTime === settings.time && currentTime !== lastCheck) {
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
||||
newChecks[screenName] = currentTime
|
||||
monitorLastTimeChecks = newChecks
|
||||
cycleNextForMonitor(screenName)
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks);
|
||||
newChecks[screenName] = currentTime;
|
||||
monitorLastTimeChecks = newChecks;
|
||||
cycleNextForMonitor(screenName);
|
||||
} else if (currentTime !== settings.time) {
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
||||
newChecks[screenName] = ""
|
||||
monitorLastTimeChecks = newChecks
|
||||
var newChecks = Object.assign({}, monitorLastTimeChecks);
|
||||
newChecks[screenName] = "";
|
||||
monitorLastTimeChecks = newChecks;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -352,7 +365,11 @@ Singleton {
|
||||
interval: cachedCyclingInterval * 1000
|
||||
running: false
|
||||
repeat: true
|
||||
onTriggered: cycleToNextWallpaper()
|
||||
onTriggered: {
|
||||
if (anyFullscreen)
|
||||
return;
|
||||
cycleToNextWallpaper();
|
||||
}
|
||||
}
|
||||
|
||||
SystemClock {
|
||||
@@ -360,7 +377,7 @@ Singleton {
|
||||
precision: SystemClock.Minutes
|
||||
onDateChanged: {
|
||||
if ((SessionData.wallpaperCyclingMode === "time" && cyclingActive) || SessionData.perMonitorWallpaper) {
|
||||
checkTimeBasedCycling()
|
||||
checkTimeBasedCycling();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,22 +393,23 @@ Singleton {
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text && text.trim()) {
|
||||
const files = text.trim().split('\n').filter(file => file.length > 0)
|
||||
if (files.length <= 1) return
|
||||
const files = text.trim().split('\n').filter(file => file.length > 0);
|
||||
if (files.length <= 1)
|
||||
return;
|
||||
const wallpaperList = files.sort();
|
||||
const currentPath = cyclingProcess.currentWallpaper;
|
||||
let currentIndex = wallpaperList.findIndex(path => path === currentPath);
|
||||
if (currentIndex === -1)
|
||||
currentIndex = 0;
|
||||
|
||||
const wallpaperList = files.sort()
|
||||
const currentPath = cyclingProcess.currentWallpaper
|
||||
let currentIndex = wallpaperList.findIndex(path => path === currentPath)
|
||||
if (currentIndex === -1) currentIndex = 0
|
||||
|
||||
const nextIndex = (currentIndex + 1) % wallpaperList.length
|
||||
const nextWallpaper = wallpaperList[nextIndex]
|
||||
const nextIndex = (currentIndex + 1) % wallpaperList.length;
|
||||
const nextWallpaper = wallpaperList[nextIndex];
|
||||
|
||||
if (nextWallpaper && nextWallpaper !== currentPath) {
|
||||
if (cyclingProcess.targetScreenName) {
|
||||
SessionData.setMonitorWallpaper(cyclingProcess.targetScreenName, nextWallpaper)
|
||||
SessionData.setMonitorWallpaper(cyclingProcess.targetScreenName, nextWallpaper);
|
||||
} else {
|
||||
SessionData.setWallpaper(nextWallpaper)
|
||||
SessionData.setWallpaper(nextWallpaper);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -410,27 +428,27 @@ Singleton {
|
||||
stdout: StdioCollector {
|
||||
onStreamFinished: {
|
||||
if (text && text.trim()) {
|
||||
const files = text.trim().split('\n').filter(file => file.length > 0)
|
||||
if (files.length <= 1) return
|
||||
const files = text.trim().split('\n').filter(file => file.length > 0);
|
||||
if (files.length <= 1)
|
||||
return;
|
||||
const wallpaperList = files.sort();
|
||||
const currentPath = prevCyclingProcess.currentWallpaper;
|
||||
let currentIndex = wallpaperList.findIndex(path => path === currentPath);
|
||||
if (currentIndex === -1)
|
||||
currentIndex = 0;
|
||||
|
||||
const wallpaperList = files.sort()
|
||||
const currentPath = prevCyclingProcess.currentWallpaper
|
||||
let currentIndex = wallpaperList.findIndex(path => path === currentPath)
|
||||
if (currentIndex === -1) currentIndex = 0
|
||||
|
||||
const prevIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1
|
||||
const prevWallpaper = wallpaperList[prevIndex]
|
||||
const prevIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1;
|
||||
const prevWallpaper = wallpaperList[prevIndex];
|
||||
|
||||
if (prevWallpaper && prevWallpaper !== currentPath) {
|
||||
if (prevCyclingProcess.targetScreenName) {
|
||||
SessionData.setMonitorWallpaper(prevCyclingProcess.targetScreenName, prevWallpaper)
|
||||
SessionData.setMonitorWallpaper(prevCyclingProcess.targetScreenName, prevWallpaper);
|
||||
} else {
|
||||
SessionData.setWallpaper(prevWallpaper)
|
||||
SessionData.setWallpaper(prevWallpaper);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ Item {
|
||||
property string layerNamespace: "dms:popout"
|
||||
property alias content: contentLoader.sourceComponent
|
||||
property alias contentLoader: contentLoader
|
||||
property Component overlayContent: null
|
||||
property alias overlayLoader: overlayLoader
|
||||
property real popupWidth: 400
|
||||
property real popupHeight: 300
|
||||
property real triggerX: 0
|
||||
@@ -243,6 +245,13 @@ Item {
|
||||
backgroundClicked();
|
||||
}
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: overlayLoader
|
||||
anchors.fill: parent
|
||||
active: root.overlayContent !== null && backgroundWindow.visible
|
||||
sourceComponent: root.overlayContent
|
||||
}
|
||||
}
|
||||
|
||||
PanelWindow {
|
||||
|
||||
Reference in New Issue
Block a user