mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-31 08:52:49 -05:00
Compare commits
5 Commits
7f15227de1
...
c2787f1282
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2787f1282 | ||
|
|
df940124b1 | ||
|
|
5288d042ca | ||
|
|
fa98a27c90 | ||
|
|
d341a5a60b |
@@ -28,7 +28,7 @@ packages:
|
|||||||
outpkg: mocks_brightness
|
outpkg: mocks_brightness
|
||||||
interfaces:
|
interfaces:
|
||||||
DBusConn:
|
DBusConn:
|
||||||
github.com/AvengeMedia/danklinux/internal/server/network:
|
github.com/AvengeMedia/DankMaterialShell/core/internal/server/network:
|
||||||
config:
|
config:
|
||||||
dir: "internal/mocks/network"
|
dir: "internal/mocks/network"
|
||||||
outpkg: mocks_network
|
outpkg: mocks_network
|
||||||
|
|||||||
@@ -509,6 +509,52 @@ func (_c *MockBackend_DisconnectWiFi_Call) RunAndReturn(run func() error) *MockB
|
|||||||
return _c
|
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
|
// ForgetWiFiNetwork provides a mock function with given fields: ssid
|
||||||
func (_m *MockBackend) ForgetWiFiNetwork(ssid string) error {
|
func (_m *MockBackend) ForgetWiFiNetwork(ssid string) error {
|
||||||
ret := _m.Called(ssid)
|
ret := _m.Called(ssid)
|
||||||
@@ -659,6 +705,53 @@ func (_c *MockBackend_GetPromptBroker_Call) RunAndReturn(run func() network.Prom
|
|||||||
return _c
|
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
|
// GetWiFiEnabled provides a mock function with no fields
|
||||||
func (_m *MockBackend) GetWiFiEnabled() (bool, error) {
|
func (_m *MockBackend) GetWiFiEnabled() (bool, error) {
|
||||||
ret := _m.Called()
|
ret := _m.Called()
|
||||||
@@ -1091,6 +1184,52 @@ func (_c *MockBackend_ScanWiFi_Call) RunAndReturn(run func() error) *MockBackend
|
|||||||
return _c
|
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
|
// SetPromptBroker provides a mock function with given fields: broker
|
||||||
func (_m *MockBackend) SetPromptBroker(broker network.PromptBroker) error {
|
func (_m *MockBackend) SetPromptBroker(broker network.PromptBroker) error {
|
||||||
ret := _m.Called(broker)
|
ret := _m.Called(broker)
|
||||||
|
|||||||
@@ -8,10 +8,13 @@ type Backend interface {
|
|||||||
SetWiFiEnabled(enabled bool) error
|
SetWiFiEnabled(enabled bool) error
|
||||||
|
|
||||||
ScanWiFi() error
|
ScanWiFi() error
|
||||||
|
ScanWiFiDevice(device string) error
|
||||||
GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error)
|
GetWiFiNetworkDetails(ssid string) (*NetworkInfoResponse, error)
|
||||||
|
GetWiFiDevices() []WiFiDevice
|
||||||
|
|
||||||
ConnectWiFi(req ConnectionRequest) error
|
ConnectWiFi(req ConnectionRequest) error
|
||||||
DisconnectWiFi() error
|
DisconnectWiFi() error
|
||||||
|
DisconnectWiFiDevice(device string) error
|
||||||
ForgetWiFiNetwork(ssid string) error
|
ForgetWiFiNetwork(ssid string) error
|
||||||
SetWiFiAutoconnect(ssid string, autoconnect bool) error
|
SetWiFiAutoconnect(ssid string, autoconnect bool) error
|
||||||
|
|
||||||
@@ -54,11 +57,13 @@ type BackendState struct {
|
|||||||
WiFiBSSID string
|
WiFiBSSID string
|
||||||
WiFiSignal uint8
|
WiFiSignal uint8
|
||||||
WiFiNetworks []WiFiNetwork
|
WiFiNetworks []WiFiNetwork
|
||||||
|
WiFiDevices []WiFiDevice
|
||||||
WiredConnections []WiredConnection
|
WiredConnections []WiredConnection
|
||||||
VPNProfiles []VPNProfile
|
VPNProfiles []VPNProfile
|
||||||
VPNActive []VPNActive
|
VPNActive []VPNActive
|
||||||
IsConnecting bool
|
IsConnecting bool
|
||||||
ConnectingSSID string
|
ConnectingSSID string
|
||||||
|
ConnectingDevice string
|
||||||
IsConnectingVPN bool
|
IsConnectingVPN bool
|
||||||
ConnectingVPNUUID string
|
ConnectingVPNUUID string
|
||||||
LastError string
|
LastError string
|
||||||
|
|||||||
@@ -196,3 +196,15 @@ func (b *HybridIwdNetworkdBackend) CancelCredentials(token string) error {
|
|||||||
func (b *HybridIwdNetworkdBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
func (b *HybridIwdNetworkdBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||||
return b.wifi.SetWiFiAutoconnect(ssid, autoconnect)
|
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) {
|
func (b *IWDBackend) GetCurrentState() (*BackendState, error) {
|
||||||
|
b.stateMutex.RLock()
|
||||||
|
defer b.stateMutex.RUnlock()
|
||||||
|
|
||||||
state := *b.state
|
state := *b.state
|
||||||
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
||||||
state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
|
state.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
|
||||||
|
state.WiFiDevices = b.getWiFiDevicesLocked()
|
||||||
|
|
||||||
return &state, nil
|
return &state, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,3 +45,38 @@ func (b *IWDBackend) DisconnectAllVPN() error {
|
|||||||
func (b *IWDBackend) ClearVPNCredentials(uuidOrName string) error {
|
func (b *IWDBackend) ClearVPNCredentials(uuidOrName string) error {
|
||||||
return fmt.Errorf("VPN not supported by iwd backend")
|
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 {
|
func (b *SystemdNetworkdBackend) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||||
return fmt.Errorf("WiFi autoconnect not supported by networkd backend")
|
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
|
NmDeviceStateReasonNewActivation = 60
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type wifiDeviceInfo struct {
|
||||||
|
device gonetworkmanager.Device
|
||||||
|
wireless gonetworkmanager.DeviceWireless
|
||||||
|
name string
|
||||||
|
hwAddress string
|
||||||
|
}
|
||||||
|
|
||||||
type NetworkManagerBackend struct {
|
type NetworkManagerBackend struct {
|
||||||
nmConn interface{}
|
nmConn interface{}
|
||||||
ethernetDevice interface{}
|
ethernetDevice interface{}
|
||||||
wifiDevice interface{}
|
wifiDevice interface{}
|
||||||
settings interface{}
|
settings interface{}
|
||||||
wifiDev interface{}
|
wifiDev interface{}
|
||||||
|
wifiDevices map[string]*wifiDeviceInfo
|
||||||
|
|
||||||
dbusConn *dbus.Conn
|
dbusConn *dbus.Conn
|
||||||
signals chan *dbus.Signal
|
signals chan *dbus.Signal
|
||||||
@@ -71,8 +79,9 @@ func NewNetworkManagerBackend(nmConn ...gonetworkmanager.NetworkManager) (*Netwo
|
|||||||
}
|
}
|
||||||
|
|
||||||
backend := &NetworkManagerBackend{
|
backend := &NetworkManagerBackend{
|
||||||
nmConn: nm,
|
nmConn: nm,
|
||||||
stopChan: make(chan struct{}),
|
stopChan: make(chan struct{}),
|
||||||
|
wifiDevices: make(map[string]*wifiDeviceInfo),
|
||||||
state: &BackendState{
|
state: &BackendState{
|
||||||
Backend: "networkmanager",
|
Backend: "networkmanager",
|
||||||
},
|
},
|
||||||
@@ -114,27 +123,48 @@ func (b *NetworkManagerBackend) Initialize() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case gonetworkmanager.NmDeviceTypeWifi:
|
case gonetworkmanager.NmDeviceTypeWifi:
|
||||||
b.wifiDevice = dev
|
iface, err := dev.GetPropertyInterface()
|
||||||
if w, err := gonetworkmanager.NewDeviceWireless(dev.GetPath()); err == nil {
|
if 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 {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if wifiEnabled {
|
w, err := gonetworkmanager.NewDeviceWireless(dev.GetPath())
|
||||||
if _, err := b.updateWiFiNetworks(); err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to get initial networks: %v", err)
|
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 {
|
if err := b.updatePrimaryConnection(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -165,6 +195,7 @@ func (b *NetworkManagerBackend) GetCurrentState() (*BackendState, error) {
|
|||||||
|
|
||||||
state := *b.state
|
state := *b.state
|
||||||
state.WiFiNetworks = append([]WiFiNetwork(nil), b.state.WiFiNetworks...)
|
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.WiredConnections = append([]WiredConnection(nil), b.state.WiredConnections...)
|
||||||
state.VPNProfiles = append([]VPNProfile(nil), b.state.VPNProfiles...)
|
state.VPNProfiles = append([]VPNProfile(nil), b.state.VPNProfiles...)
|
||||||
state.VPNActive = append([]VPNActive(nil), b.state.VPNActive...)
|
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 {
|
func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
||||||
if b.wifiDevice == nil {
|
devInfo, err := b.getWifiDeviceForConnection(req.Device)
|
||||||
return fmt.Errorf("no WiFi device available")
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b.stateMutex.RLock()
|
b.stateMutex.RLock()
|
||||||
alreadyConnected := b.state.WiFiConnected && b.state.WiFiSSID == req.SSID
|
alreadyConnected := b.state.WiFiConnected && b.state.WiFiSSID == req.SSID
|
||||||
b.stateMutex.RUnlock()
|
b.stateMutex.RUnlock()
|
||||||
|
|
||||||
if alreadyConnected && !req.Interactive {
|
if alreadyConnected && !req.Interactive && req.Device == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
b.stateMutex.Lock()
|
b.stateMutex.Lock()
|
||||||
b.state.IsConnecting = true
|
b.state.IsConnecting = true
|
||||||
b.state.ConnectingSSID = req.SSID
|
b.state.ConnectingSSID = req.SSID
|
||||||
|
b.state.ConnectingDevice = req.Device
|
||||||
b.state.LastError = ""
|
b.state.LastError = ""
|
||||||
b.stateMutex.Unlock()
|
b.stateMutex.Unlock()
|
||||||
|
|
||||||
@@ -223,14 +225,13 @@ func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
|||||||
|
|
||||||
existingConn, err := b.findConnection(req.SSID)
|
existingConn, err := b.findConnection(req.SSID)
|
||||||
if err == nil && existingConn != nil {
|
if err == nil && existingConn != nil {
|
||||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
_, err := nm.ActivateConnection(existingConn, devInfo.device, nil)
|
||||||
|
|
||||||
_, err := nm.ActivateConnection(existingConn, dev, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("[ConnectWiFi] Failed to activate existing connection: %v", err)
|
log.Warnf("[ConnectWiFi] Failed to activate existing connection: %v", err)
|
||||||
b.stateMutex.Lock()
|
b.stateMutex.Lock()
|
||||||
b.state.IsConnecting = false
|
b.state.IsConnecting = false
|
||||||
b.state.ConnectingSSID = ""
|
b.state.ConnectingSSID = ""
|
||||||
|
b.state.ConnectingDevice = ""
|
||||||
b.state.LastError = fmt.Sprintf("failed to activate connection: %v", err)
|
b.state.LastError = fmt.Sprintf("failed to activate connection: %v", err)
|
||||||
b.stateMutex.Unlock()
|
b.stateMutex.Unlock()
|
||||||
if b.onStateChange != nil {
|
if b.onStateChange != nil {
|
||||||
@@ -242,11 +243,12 @@ func (b *NetworkManagerBackend) ConnectWiFi(req ConnectionRequest) error {
|
|||||||
return nil
|
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)
|
log.Warnf("[ConnectWiFi] Failed to create and connect: %v", err)
|
||||||
b.stateMutex.Lock()
|
b.stateMutex.Lock()
|
||||||
b.state.IsConnecting = false
|
b.state.IsConnecting = false
|
||||||
b.state.ConnectingSSID = ""
|
b.state.ConnectingSSID = ""
|
||||||
|
b.state.ConnectingDevice = ""
|
||||||
b.state.LastError = err.Error()
|
b.state.LastError = err.Error()
|
||||||
b.stateMutex.Unlock()
|
b.stateMutex.Unlock()
|
||||||
if b.onStateChange != nil {
|
if b.onStateChange != nil {
|
||||||
@@ -502,19 +504,17 @@ func (b *NetworkManagerBackend) findConnection(ssid string) (gonetworkmanager.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (b *NetworkManagerBackend) createAndConnectWiFi(req ConnectionRequest) error {
|
func (b *NetworkManagerBackend) createAndConnectWiFi(req ConnectionRequest) error {
|
||||||
if b.wifiDevice == nil {
|
devInfo, err := b.getWifiDeviceForConnection(req.Device)
|
||||||
return fmt.Errorf("no WiFi device available")
|
if err != nil {
|
||||||
}
|
|
||||||
|
|
||||||
nm := b.nmConn.(gonetworkmanager.NetworkManager)
|
|
||||||
dev := b.wifiDevice.(gonetworkmanager.Device)
|
|
||||||
|
|
||||||
if err := b.ensureWiFiDevice(); err != nil {
|
|
||||||
return err
|
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()
|
apPaths, err := w.GetAccessPoints()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get access points: %w", err)
|
return fmt.Errorf("failed to get access points: %w", err)
|
||||||
@@ -716,3 +716,254 @@ func (b *NetworkManagerBackend) SetWiFiAutoconnect(ssid string, autoconnect bool
|
|||||||
|
|
||||||
return nil
|
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.wifiDevice = mockDeviceWireless
|
||||||
backend.wifiDev = 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.stateMutex.Lock()
|
||||||
backend.state.WiFiConnected = true
|
backend.state.WiFiConnected = true
|
||||||
backend.state.WiFiSSID = "TestNetwork"
|
backend.state.WiFiSSID = "TestNetwork"
|
||||||
|
backend.state.WiFiDevice = "wlan0"
|
||||||
backend.stateMutex.Unlock()
|
backend.stateMutex.Unlock()
|
||||||
|
|
||||||
req := ConnectionRequest{SSID: "TestNetwork", Password: "password"}
|
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) {
|
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())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -163,6 +170,9 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
|||||||
if username, ok := req.Params["username"].(string); ok {
|
if username, ok := req.Params["username"].(string); ok {
|
||||||
connReq.Username = username
|
connReq.Username = username
|
||||||
}
|
}
|
||||||
|
if device, ok := req.Params["device"].(string); ok {
|
||||||
|
connReq.Device = device
|
||||||
|
}
|
||||||
|
|
||||||
if interactive, ok := req.Params["interactive"].(bool); ok {
|
if interactive, ok := req.Params["interactive"].(bool); ok {
|
||||||
connReq.Interactive = interactive
|
connReq.Interactive = interactive
|
||||||
@@ -170,7 +180,7 @@ func handleConnectWiFi(conn net.Conn, req Request, manager *Manager) {
|
|||||||
state := manager.GetState()
|
state := manager.GetState()
|
||||||
alreadyConnected := state.WiFiConnected && state.WiFiSSID == ssid
|
alreadyConnected := state.WiFiConnected && state.WiFiSSID == ssid
|
||||||
|
|
||||||
if alreadyConnected {
|
if alreadyConnected && connReq.Device == "" {
|
||||||
connReq.Interactive = false
|
connReq.Interactive = false
|
||||||
} else {
|
} else {
|
||||||
networkInfo, err := manager.GetNetworkInfo(ssid)
|
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) {
|
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())
|
models.RespondError(conn, req.ID, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -117,11 +117,13 @@ func (m *Manager) syncStateFromBackend() error {
|
|||||||
m.state.WiFiBSSID = backendState.WiFiBSSID
|
m.state.WiFiBSSID = backendState.WiFiBSSID
|
||||||
m.state.WiFiSignal = backendState.WiFiSignal
|
m.state.WiFiSignal = backendState.WiFiSignal
|
||||||
m.state.WiFiNetworks = backendState.WiFiNetworks
|
m.state.WiFiNetworks = backendState.WiFiNetworks
|
||||||
|
m.state.WiFiDevices = backendState.WiFiDevices
|
||||||
m.state.WiredConnections = backendState.WiredConnections
|
m.state.WiredConnections = backendState.WiredConnections
|
||||||
m.state.VPNProfiles = backendState.VPNProfiles
|
m.state.VPNProfiles = backendState.VPNProfiles
|
||||||
m.state.VPNActive = backendState.VPNActive
|
m.state.VPNActive = backendState.VPNActive
|
||||||
m.state.IsConnecting = backendState.IsConnecting
|
m.state.IsConnecting = backendState.IsConnecting
|
||||||
m.state.ConnectingSSID = backendState.ConnectingSSID
|
m.state.ConnectingSSID = backendState.ConnectingSSID
|
||||||
|
m.state.ConnectingDevice = backendState.ConnectingDevice
|
||||||
m.state.LastError = backendState.LastError
|
m.state.LastError = backendState.LastError
|
||||||
m.stateMutex.Unlock()
|
m.stateMutex.Unlock()
|
||||||
|
|
||||||
@@ -151,6 +153,7 @@ func (m *Manager) snapshotState() NetworkState {
|
|||||||
defer m.stateMutex.RUnlock()
|
defer m.stateMutex.RUnlock()
|
||||||
s := *m.state
|
s := *m.state
|
||||||
s.WiFiNetworks = append([]WiFiNetwork(nil), m.state.WiFiNetworks...)
|
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.WiredConnections = append([]WiredConnection(nil), m.state.WiredConnections...)
|
||||||
s.VPNProfiles = append([]VPNProfile(nil), m.state.VPNProfiles...)
|
s.VPNProfiles = append([]VPNProfile(nil), m.state.VPNProfiles...)
|
||||||
s.VPNActive = append([]VPNActive(nil), m.state.VPNActive...)
|
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) {
|
if len(old.WiFiNetworks) != len(new.WiFiNetworks) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if len(old.WiFiDevices) != len(new.WiFiDevices) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
if len(old.WiredConnections) != len(new.WiredConnections) {
|
if len(old.WiredConnections) != len(new.WiredConnections) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -505,3 +511,19 @@ func (m *Manager) ClearVPNCredentials(uuidOrName string) error {
|
|||||||
func (m *Manager) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
func (m *Manager) SetWiFiAutoconnect(ssid string, autoconnect bool) error {
|
||||||
return m.backend.SetWiFiAutoconnect(ssid, autoconnect)
|
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"`
|
Mode string `json:"mode"`
|
||||||
Rate uint32 `json:"rate"`
|
Rate uint32 `json:"rate"`
|
||||||
Channel uint32 `json:"channel"`
|
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 {
|
type VPNProfile struct {
|
||||||
@@ -76,11 +89,13 @@ type NetworkState struct {
|
|||||||
WiFiBSSID string `json:"wifiBSSID"`
|
WiFiBSSID string `json:"wifiBSSID"`
|
||||||
WiFiSignal uint8 `json:"wifiSignal"`
|
WiFiSignal uint8 `json:"wifiSignal"`
|
||||||
WiFiNetworks []WiFiNetwork `json:"wifiNetworks"`
|
WiFiNetworks []WiFiNetwork `json:"wifiNetworks"`
|
||||||
|
WiFiDevices []WiFiDevice `json:"wifiDevices"`
|
||||||
WiredConnections []WiredConnection `json:"wiredConnections"`
|
WiredConnections []WiredConnection `json:"wiredConnections"`
|
||||||
VPNProfiles []VPNProfile `json:"vpnProfiles"`
|
VPNProfiles []VPNProfile `json:"vpnProfiles"`
|
||||||
VPNActive []VPNActive `json:"vpnActive"`
|
VPNActive []VPNActive `json:"vpnActive"`
|
||||||
IsConnecting bool `json:"isConnecting"`
|
IsConnecting bool `json:"isConnecting"`
|
||||||
ConnectingSSID string `json:"connectingSSID"`
|
ConnectingSSID string `json:"connectingSSID"`
|
||||||
|
ConnectingDevice string `json:"connectingDevice,omitempty"`
|
||||||
LastError string `json:"lastError"`
|
LastError string `json:"lastError"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,6 +106,7 @@ type ConnectionRequest struct {
|
|||||||
AnonymousIdentity string `json:"anonymousIdentity,omitempty"`
|
AnonymousIdentity string `json:"anonymousIdentity,omitempty"`
|
||||||
DomainSuffixMatch string `json:"domainSuffixMatch,omitempty"`
|
DomainSuffixMatch string `json:"domainSuffixMatch,omitempty"`
|
||||||
Interactive bool `json:"interactive,omitempty"`
|
Interactive bool `json:"interactive,omitempty"`
|
||||||
|
Device string `json:"device,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WiredConnection struct {
|
type WiredConnection struct {
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap"
|
||||||
)
|
)
|
||||||
|
|
||||||
const APIVersion = 19
|
const APIVersion = 20
|
||||||
|
|
||||||
type Capabilities struct {
|
type Capabilities struct {
|
||||||
Capabilities []string `json:"capabilities"`
|
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(" plugins.search - Search plugins (params: query, category?, compositor?, capability?)")
|
||||||
log.Info("Network:")
|
log.Info("Network:")
|
||||||
log.Info(" network.getState - Get current network state")
|
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.networks - Get WiFi network list")
|
||||||
log.Info(" network.wifi.connect - Connect to WiFi (params: ssid, password?, username?)")
|
log.Info(" network.wifi.connect - Connect to WiFi (params: ssid, password?, username?, device?)")
|
||||||
log.Info(" network.wifi.disconnect - Disconnect WiFi")
|
log.Info(" network.wifi.disconnect - Disconnect WiFi (params: device?)")
|
||||||
log.Info(" network.wifi.forget - Forget network (params: ssid)")
|
log.Info(" network.wifi.forget - Forget network (params: ssid)")
|
||||||
log.Info(" network.wifi.toggle - Toggle WiFi radio")
|
log.Info(" network.wifi.toggle - Toggle WiFi radio")
|
||||||
log.Info(" network.wifi.enable - Enable WiFi")
|
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 controlCenterShowNetworkIcon: true
|
||||||
property bool controlCenterShowBluetoothIcon: true
|
property bool controlCenterShowBluetoothIcon: true
|
||||||
property bool controlCenterShowAudioIcon: 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 showPrivacyButton: true
|
||||||
property bool privacyShowMicIcon: false
|
property bool privacyShowMicIcon: false
|
||||||
property bool privacyShowCameraIcon: false
|
property bool privacyShowCameraIcon: false
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ var SPEC = {
|
|||||||
controlCenterShowNetworkIcon: { def: true },
|
controlCenterShowNetworkIcon: { def: true },
|
||||||
controlCenterShowBluetoothIcon: { def: true },
|
controlCenterShowBluetoothIcon: { def: true },
|
||||||
controlCenterShowAudioIcon: { def: true },
|
controlCenterShowAudioIcon: { def: true },
|
||||||
|
controlCenterShowVpnIcon: { def: false },
|
||||||
|
controlCenterShowBrightnessIcon: { def: false },
|
||||||
|
controlCenterShowMicIcon: { def: false },
|
||||||
|
controlCenterShowBatteryIcon: { def: false },
|
||||||
|
controlCenterShowPrinterIcon: { def: false },
|
||||||
|
|
||||||
showPrivacyButton: { def: true },
|
showPrivacyButton: { def: true },
|
||||||
privacyShowMicIcon: { def: false },
|
privacyShowMicIcon: { def: false },
|
||||||
|
|||||||
@@ -563,4 +563,42 @@ Item {
|
|||||||
|
|
||||||
target: "file"
|
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.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Modules.ControlCenter.Details
|
import qs.Modules.ControlCenter.Details
|
||||||
import qs.Modules.ControlCenter.Models
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -11,17 +10,21 @@ Item {
|
|||||||
property var expandedWidgetData: null
|
property var expandedWidgetData: null
|
||||||
property var bluetoothCodecSelector: null
|
property var bluetoothCodecSelector: null
|
||||||
property string screenName: ""
|
property string screenName: ""
|
||||||
|
property string screenModel: ""
|
||||||
|
|
||||||
property var pluginDetailInstance: null
|
property var pluginDetailInstance: null
|
||||||
property var widgetModel: null
|
property var widgetModel: null
|
||||||
property var collapseCallback: null
|
property var collapseCallback: null
|
||||||
|
|
||||||
function getDetailHeight(section) {
|
function getDetailHeight(section) {
|
||||||
const maxAvailable = parent ? parent.height - Theme.spacingS : 9999
|
const maxAvailable = parent ? parent.height - Theme.spacingS : 9999;
|
||||||
if (section === "wifi") return Math.min(350, maxAvailable)
|
if (section === "wifi")
|
||||||
if (section === "bluetooth") return Math.min(350, maxAvailable)
|
return Math.min(350, maxAvailable);
|
||||||
if (section.startsWith("brightnessSlider_")) return Math.min(400, maxAvailable)
|
if (section === "bluetooth")
|
||||||
return Math.min(250, maxAvailable)
|
return Math.min(350, maxAvailable);
|
||||||
|
if (section.startsWith("brightnessSlider_"))
|
||||||
|
return Math.min(400, maxAvailable);
|
||||||
|
return Math.min(250, maxAvailable);
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
@@ -49,18 +52,18 @@ Item {
|
|||||||
|
|
||||||
function onDeviceNameChanged(newDeviceName) {
|
function onDeviceNameChanged(newDeviceName) {
|
||||||
if (root.expandedWidgetData && root.expandedWidgetData.id === "brightnessSlider") {
|
if (root.expandedWidgetData && root.expandedWidgetData.id === "brightnessSlider") {
|
||||||
const widgets = SettingsData.controlCenterWidgets || []
|
const widgets = SettingsData.controlCenterWidgets || [];
|
||||||
const newWidgets = widgets.map(w => {
|
const newWidgets = widgets.map(w => {
|
||||||
if (w.id === "brightnessSlider" && w.instanceId === root.expandedWidgetData.instanceId) {
|
if (w.id === "brightnessSlider" && w.instanceId === root.expandedWidgetData.instanceId) {
|
||||||
const updatedWidget = Object.assign({}, w)
|
const updatedWidget = Object.assign({}, w);
|
||||||
updatedWidget.deviceName = newDeviceName
|
updatedWidget.deviceName = newDeviceName;
|
||||||
return updatedWidget
|
return updatedWidget;
|
||||||
}
|
}
|
||||||
return w
|
return w;
|
||||||
})
|
});
|
||||||
SettingsData.set("controlCenterWidgets", newWidgets)
|
SettingsData.set("controlCenterWidgets", newWidgets);
|
||||||
if (root.collapseCallback) {
|
if (root.collapseCallback) {
|
||||||
root.collapseCallback()
|
root.collapseCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,18 +76,18 @@ Item {
|
|||||||
|
|
||||||
function onMountPathChanged(newMountPath) {
|
function onMountPathChanged(newMountPath) {
|
||||||
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
|
if (root.expandedWidgetData && root.expandedWidgetData.id === "diskUsage") {
|
||||||
const widgets = SettingsData.controlCenterWidgets || []
|
const widgets = SettingsData.controlCenterWidgets || [];
|
||||||
const newWidgets = widgets.map(w => {
|
const newWidgets = widgets.map(w => {
|
||||||
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
|
if (w.id === "diskUsage" && w.instanceId === root.expandedWidgetData.instanceId) {
|
||||||
const updatedWidget = Object.assign({}, w)
|
const updatedWidget = Object.assign({}, w);
|
||||||
updatedWidget.mountPath = newMountPath
|
updatedWidget.mountPath = newMountPath;
|
||||||
return updatedWidget
|
return updatedWidget;
|
||||||
}
|
}
|
||||||
return w
|
return w;
|
||||||
})
|
});
|
||||||
SettingsData.set("controlCenterWidgets", newWidgets)
|
SettingsData.set("controlCenterWidgets", newWidgets);
|
||||||
if (root.collapseCallback) {
|
if (root.collapseCallback) {
|
||||||
root.collapseCallback()
|
root.collapseCallback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,86 +95,97 @@ Item {
|
|||||||
|
|
||||||
onExpandedSectionChanged: {
|
onExpandedSectionChanged: {
|
||||||
if (pluginDetailInstance) {
|
if (pluginDetailInstance) {
|
||||||
pluginDetailInstance.destroy()
|
pluginDetailInstance.destroy();
|
||||||
pluginDetailInstance = null
|
pluginDetailInstance = null;
|
||||||
}
|
}
|
||||||
pluginDetailLoader.active = false
|
pluginDetailLoader.active = false;
|
||||||
coreDetailLoader.active = false
|
coreDetailLoader.active = false;
|
||||||
|
|
||||||
if (!root.expandedSection) {
|
if (!root.expandedSection) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.expandedSection.startsWith("builtin_")) {
|
if (root.expandedSection.startsWith("builtin_")) {
|
||||||
const builtinId = root.expandedSection
|
const builtinId = root.expandedSection;
|
||||||
let builtinInstance = null
|
let builtinInstance = null;
|
||||||
|
|
||||||
if (builtinId === "builtin_vpn") {
|
if (builtinId === "builtin_vpn") {
|
||||||
if (widgetModel?.vpnLoader) {
|
if (widgetModel?.vpnLoader) {
|
||||||
widgetModel.vpnLoader.active = true
|
widgetModel.vpnLoader.active = true;
|
||||||
}
|
}
|
||||||
builtinInstance = widgetModel.vpnBuiltinInstance
|
builtinInstance = widgetModel.vpnBuiltinInstance;
|
||||||
}
|
}
|
||||||
if (builtinId === "builtin_cups") {
|
if (builtinId === "builtin_cups") {
|
||||||
if (widgetModel?.cupsLoader) {
|
if (widgetModel?.cupsLoader) {
|
||||||
widgetModel.cupsLoader.active = true
|
widgetModel.cupsLoader.active = true;
|
||||||
}
|
}
|
||||||
builtinInstance = widgetModel.cupsBuiltinInstance
|
builtinInstance = widgetModel.cupsBuiltinInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!builtinInstance || !builtinInstance.ccDetailContent) {
|
if (!builtinInstance || !builtinInstance.ccDetailContent) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginDetailLoader.sourceComponent = builtinInstance.ccDetailContent
|
pluginDetailLoader.sourceComponent = builtinInstance.ccDetailContent;
|
||||||
pluginDetailLoader.active = parent.height > 0
|
pluginDetailLoader.active = parent.height > 0;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.expandedSection.startsWith("plugin_")) {
|
if (root.expandedSection.startsWith("plugin_")) {
|
||||||
const pluginId = root.expandedSection.replace("plugin_", "")
|
const pluginId = root.expandedSection.replace("plugin_", "");
|
||||||
const pluginComponent = PluginService.pluginWidgetComponents[pluginId]
|
const pluginComponent = PluginService.pluginWidgetComponents[pluginId];
|
||||||
if (!pluginComponent) {
|
if (!pluginComponent) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginDetailInstance = pluginComponent.createObject(null)
|
pluginDetailInstance = pluginComponent.createObject(null);
|
||||||
if (!pluginDetailInstance || !pluginDetailInstance.ccDetailContent) {
|
if (!pluginDetailInstance || !pluginDetailInstance.ccDetailContent) {
|
||||||
if (pluginDetailInstance) {
|
if (pluginDetailInstance) {
|
||||||
pluginDetailInstance.destroy()
|
pluginDetailInstance.destroy();
|
||||||
pluginDetailInstance = null
|
pluginDetailInstance = null;
|
||||||
}
|
}
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginDetailLoader.sourceComponent = pluginDetailInstance.ccDetailContent
|
pluginDetailLoader.sourceComponent = pluginDetailInstance.ccDetailContent;
|
||||||
pluginDetailLoader.active = parent.height > 0
|
pluginDetailLoader.active = parent.height > 0;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.expandedSection.startsWith("diskUsage_")) {
|
if (root.expandedSection.startsWith("diskUsage_")) {
|
||||||
coreDetailLoader.sourceComponent = diskUsageDetailComponent
|
coreDetailLoader.sourceComponent = diskUsageDetailComponent;
|
||||||
coreDetailLoader.active = parent.height > 0
|
coreDetailLoader.active = parent.height > 0;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.expandedSection.startsWith("brightnessSlider_")) {
|
if (root.expandedSection.startsWith("brightnessSlider_")) {
|
||||||
coreDetailLoader.sourceComponent = brightnessDetailComponent
|
coreDetailLoader.sourceComponent = brightnessDetailComponent;
|
||||||
coreDetailLoader.active = parent.height > 0
|
coreDetailLoader.active = parent.height > 0;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (root.expandedSection) {
|
switch (root.expandedSection) {
|
||||||
case "network":
|
case "network":
|
||||||
case "wifi": coreDetailLoader.sourceComponent = networkDetailComponent; break
|
case "wifi":
|
||||||
case "bluetooth": coreDetailLoader.sourceComponent = bluetoothDetailComponent; break
|
coreDetailLoader.sourceComponent = networkDetailComponent;
|
||||||
case "audioOutput": coreDetailLoader.sourceComponent = audioOutputDetailComponent; break
|
break;
|
||||||
case "audioInput": coreDetailLoader.sourceComponent = audioInputDetailComponent; break
|
case "bluetooth":
|
||||||
case "battery": coreDetailLoader.sourceComponent = batteryDetailComponent; break
|
coreDetailLoader.sourceComponent = bluetoothDetailComponent;
|
||||||
default: return
|
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 {
|
Component {
|
||||||
@@ -183,12 +197,12 @@ Item {
|
|||||||
id: bluetoothDetailComponent
|
id: bluetoothDetailComponent
|
||||||
BluetoothDetail {
|
BluetoothDetail {
|
||||||
id: bluetoothDetail
|
id: bluetoothDetail
|
||||||
onShowCodecSelector: function(device) {
|
onShowCodecSelector: function (device) {
|
||||||
if (root.bluetoothCodecSelector) {
|
if (root.bluetoothCodecSelector) {
|
||||||
root.bluetoothCodecSelector.show(device)
|
root.bluetoothCodecSelector.show(device);
|
||||||
root.bluetoothCodecSelector.codecSelected.connect(function(deviceAddress, codecName) {
|
root.bluetoothCodecSelector.codecSelected.connect(function (deviceAddress, codecName) {
|
||||||
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName)
|
bluetoothDetail.updateDeviceCodecDisplay(deviceAddress, codecName);
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,6 +237,7 @@ Item {
|
|||||||
initialDeviceName: root.expandedWidgetData?.deviceName || ""
|
initialDeviceName: root.expandedWidgetData?.deviceName || ""
|
||||||
instanceId: root.expandedWidgetData?.instanceId || ""
|
instanceId: root.expandedWidgetData?.instanceId || ""
|
||||||
screenName: root.screenName
|
screenName: root.screenName
|
||||||
|
screenModel: root.screenModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -82,6 +82,7 @@ DankPopout {
|
|||||||
|
|
||||||
onShouldBeVisibleChanged: {
|
onShouldBeVisibleChanged: {
|
||||||
if (shouldBeVisible) {
|
if (shouldBeVisible) {
|
||||||
|
collapseAll();
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
if (NetworkService.activeService) {
|
if (NetworkService.activeService) {
|
||||||
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled;
|
NetworkService.activeService.autoRefreshEnabled = NetworkService.wifiEnabled;
|
||||||
@@ -179,6 +180,7 @@ DankPopout {
|
|||||||
bluetoothCodecSelector: bluetoothCodecSelector
|
bluetoothCodecSelector: bluetoothCodecSelector
|
||||||
colorPickerModal: root.colorPickerModal
|
colorPickerModal: root.colorPickerModal
|
||||||
screenName: root.triggerScreen?.name || ""
|
screenName: root.triggerScreen?.name || ""
|
||||||
|
screenModel: root.triggerScreen?.model || ""
|
||||||
parentScreen: root.triggerScreen
|
parentScreen: root.triggerScreen
|
||||||
onExpandClicked: (widgetData, globalIndex) => {
|
onExpandClicked: (widgetData, globalIndex) => {
|
||||||
root.expandedWidgetIndex = globalIndex;
|
root.expandedWidgetIndex = globalIndex;
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.Pipewire
|
import Quickshell.Services.Pipewire
|
||||||
import qs.Common
|
import qs.Common
|
||||||
@@ -10,8 +9,8 @@ Rectangle {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool hasInputVolumeSliderInCC: {
|
property bool hasInputVolumeSliderInCC: {
|
||||||
const widgets = SettingsData.controlCenterWidgets || []
|
const widgets = SettingsData.controlCenterWidgets || [];
|
||||||
return widgets.some(widget => widget.id === "inputVolumeSlider")
|
return widgets.some(widget => widget.id === "inputVolumeSlider");
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitHeight: headerRow.height + (hasInputVolumeSliderInCC ? 0 : volumeSlider.height) + audioContent.height + Theme.spacingM
|
implicitHeight: headerRow.height + (hasInputVolumeSliderInCC ? 0 : volumeSlider.height) + audioContent.height + Theme.spacingM
|
||||||
@@ -66,7 +65,7 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (AudioService.source && AudioService.source.audio) {
|
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 {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: {
|
name: {
|
||||||
if (!AudioService.source || !AudioService.source.audio) return "mic_off"
|
if (!AudioService.source || !AudioService.source.audio)
|
||||||
let muted = AudioService.source.audio.muted
|
return "mic_off";
|
||||||
return muted ? "mic_off" : "mic"
|
let muted = AudioService.source.audio.muted;
|
||||||
|
return muted ? "mic_off" : "mic";
|
||||||
}
|
}
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
color: AudioService.source && AudioService.source.audio && !AudioService.source.audio.muted && AudioService.source.audio.volume > 0 ? Theme.primary : Theme.surfaceText
|
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
|
valueOverride: actualVolumePercent
|
||||||
thumbOutlineColor: Theme.surfaceVariant
|
thumbOutlineColor: Theme.surfaceVariant
|
||||||
|
|
||||||
onSliderValueChanged: function(newValue) {
|
onSliderValueChanged: function (newValue) {
|
||||||
if (AudioService.source && AudioService.source.audio) {
|
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) {
|
if (newValue > 0 && AudioService.source.audio.muted) {
|
||||||
AudioService.source.audio.muted = false
|
AudioService.source.audio.muted = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,22 +128,26 @@ Rectangle {
|
|||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: {
|
values: {
|
||||||
const nodes = Pipewire.nodes.values.filter(node => {
|
const nodes = Pipewire.nodes.values.filter(node => {
|
||||||
return node.audio && !node.isSink && !node.isStream
|
return node.audio && !node.isSink && !node.isStream;
|
||||||
})
|
});
|
||||||
const pins = SettingsData.audioInputDevicePins || {}
|
const pins = SettingsData.audioInputDevicePins || {};
|
||||||
const pinnedName = pins["preferredInput"]
|
const pinnedName = pins["preferredInput"];
|
||||||
|
|
||||||
let sorted = [...nodes]
|
let sorted = [...nodes];
|
||||||
sorted.sort((a, b) => {
|
sorted.sort((a, b) => {
|
||||||
// Pinned device first
|
// Pinned device first
|
||||||
if (a.name === pinnedName && b.name !== pinnedName) return -1
|
if (a.name === pinnedName && b.name !== pinnedName)
|
||||||
if (b.name === pinnedName && a.name !== pinnedName) return 1
|
return -1;
|
||||||
|
if (b.name === pinnedName && a.name !== pinnedName)
|
||||||
|
return 1;
|
||||||
// Then active device
|
// Then active device
|
||||||
if (a === AudioService.source && b !== AudioService.source) return -1
|
if (a === AudioService.source && b !== AudioService.source)
|
||||||
if (b === AudioService.source && a !== AudioService.source) return 1
|
return -1;
|
||||||
return 0
|
if (b === AudioService.source && a !== AudioService.source)
|
||||||
})
|
return 1;
|
||||||
return sorted
|
return 0;
|
||||||
|
});
|
||||||
|
return sorted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,11 +171,11 @@ Rectangle {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
name: {
|
name: {
|
||||||
if (modelData.name.includes("bluez"))
|
if (modelData.name.includes("bluez"))
|
||||||
return "headset"
|
return "headset";
|
||||||
else if (modelData.name.includes("usb"))
|
else if (modelData.name.includes("usb"))
|
||||||
return "headset"
|
return "headset";
|
||||||
else
|
else
|
||||||
return "mic"
|
return "mic";
|
||||||
}
|
}
|
||||||
size: Theme.iconSize - 4
|
size: Theme.iconSize - 4
|
||||||
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
color: modelData === AudioService.source ? Theme.primary : Theme.surfaceText
|
||||||
@@ -181,9 +185,9 @@ Rectangle {
|
|||||||
Column {
|
Column {
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: {
|
width: {
|
||||||
const iconWidth = Theme.iconSize
|
const iconWidth = Theme.iconSize;
|
||||||
const pinButtonWidth = pinInputRow.width + Theme.spacingS * 4 + Theme.spacingM
|
const pinButtonWidth = pinInputRow.width + Theme.spacingS * 4 + Theme.spacingM;
|
||||||
return parent.parent.width - iconWidth - parent.spacing - pinButtonWidth - Theme.spacingM * 2
|
return parent.parent.width - iconWidth - parent.spacing - pinButtonWidth - Theme.spacingM * 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
@@ -215,8 +219,8 @@ Rectangle {
|
|||||||
height: 28
|
height: 28
|
||||||
radius: height / 2
|
radius: height / 2
|
||||||
color: {
|
color: {
|
||||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name
|
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)
|
return isThisDevicePinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -228,21 +232,21 @@ Rectangle {
|
|||||||
name: "push_pin"
|
name: "push_pin"
|
||||||
size: 16
|
size: 16
|
||||||
color: {
|
color: {
|
||||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name
|
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
|
||||||
return isThisDevicePinned ? Theme.primary : Theme.surfaceText
|
return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
|
||||||
}
|
}
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name
|
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
|
||||||
return isThisDevicePinned ? "Pinned" : "Pin"
|
return isThisDevicePinned ? "Pinned" : "Pin";
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: {
|
color: {
|
||||||
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name
|
const isThisDevicePinned = (SettingsData.audioInputDevicePins || {})["preferredInput"] === modelData.name;
|
||||||
return isThisDevicePinned ? Theme.primary : Theme.surfaceText
|
return isThisDevicePinned ? Theme.primary : Theme.surfaceText;
|
||||||
}
|
}
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
@@ -252,16 +256,16 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {}))
|
const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {}));
|
||||||
const isCurrentlyPinned = pins["preferredInput"] === modelData.name
|
const isCurrentlyPinned = pins["preferredInput"] === modelData.name;
|
||||||
|
|
||||||
if (isCurrentlyPinned) {
|
if (isCurrentlyPinned) {
|
||||||
delete pins["preferredInput"]
|
delete pins["preferredInput"];
|
||||||
} else {
|
} 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
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (modelData) {
|
if (modelData) {
|
||||||
Pipewire.preferredDefaultAudioSource = modelData
|
Pipewire.preferredDefaultAudioSource = modelData;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import Quickshell
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -11,88 +9,91 @@ Rectangle {
|
|||||||
property string initialDeviceName: ""
|
property string initialDeviceName: ""
|
||||||
property string instanceId: ""
|
property string instanceId: ""
|
||||||
property string screenName: ""
|
property string screenName: ""
|
||||||
|
property string screenModel: ""
|
||||||
|
|
||||||
signal deviceNameChanged(string newDeviceName)
|
signal deviceNameChanged(string newDeviceName)
|
||||||
|
|
||||||
property string currentDeviceName: ""
|
property string currentDeviceName: ""
|
||||||
|
|
||||||
|
function getScreenPinKey() {
|
||||||
|
if (SettingsData.displayNameMode === "model" && screenModel && screenModel.length > 0) {
|
||||||
|
return screenModel;
|
||||||
|
}
|
||||||
|
return screenName || "";
|
||||||
|
}
|
||||||
|
|
||||||
function resolveDeviceName() {
|
function resolveDeviceName() {
|
||||||
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
|
if (!DisplayService.brightnessAvailable || !DisplayService.devices || DisplayService.devices.length === 0) {
|
||||||
return ""
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (screenName && screenName.length > 0) {
|
const pinKey = getScreenPinKey();
|
||||||
const pins = SettingsData.brightnessDevicePins || {}
|
if (pinKey.length > 0) {
|
||||||
const pinnedDevice = pins[screenName]
|
const pins = SettingsData.brightnessDevicePins || {};
|
||||||
|
const pinnedDevice = pins[pinKey];
|
||||||
if (pinnedDevice && pinnedDevice.length > 0) {
|
if (pinnedDevice && pinnedDevice.length > 0) {
|
||||||
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice)
|
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice);
|
||||||
if (found) {
|
if (found)
|
||||||
return found.name
|
return found.name;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (initialDeviceName && initialDeviceName.length > 0) {
|
if (initialDeviceName && initialDeviceName.length > 0) {
|
||||||
const found = DisplayService.devices.find(dev => dev.name === initialDeviceName)
|
const found = DisplayService.devices.find(dev => dev.name === initialDeviceName);
|
||||||
if (found) {
|
if (found)
|
||||||
return found.name
|
return found.name;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentDeviceNameFromService = DisplayService.currentDevice
|
const currentDeviceNameFromService = DisplayService.currentDevice;
|
||||||
if (currentDeviceNameFromService) {
|
if (currentDeviceNameFromService) {
|
||||||
const found = DisplayService.devices.find(dev => dev.name === currentDeviceNameFromService)
|
const found = DisplayService.devices.find(dev => dev.name === currentDeviceNameFromService);
|
||||||
if (found) {
|
if (found)
|
||||||
return found.name
|
return found.name;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const backlight = DisplayService.devices.find(d => d.class === "backlight")
|
const backlight = DisplayService.devices.find(d => d.class === "backlight");
|
||||||
if (backlight) {
|
if (backlight)
|
||||||
return backlight.name
|
return backlight.name;
|
||||||
}
|
|
||||||
|
|
||||||
const ddc = DisplayService.devices.find(d => d.class === "ddc")
|
const ddc = DisplayService.devices.find(d => d.class === "ddc");
|
||||||
if (ddc) {
|
if (ddc)
|
||||||
return ddc.name
|
return ddc.name;
|
||||||
}
|
|
||||||
|
|
||||||
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : ""
|
return DisplayService.devices.length > 0 ? DisplayService.devices[0].name : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
currentDeviceName = resolveDeviceName()
|
currentDeviceName = resolveDeviceName();
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool isPinnedToScreen: {
|
property bool isPinnedToScreen: {
|
||||||
if (!screenName || screenName.length === 0) {
|
const pinKey = getScreenPinKey();
|
||||||
return false
|
if (!pinKey || pinKey.length === 0)
|
||||||
}
|
return false;
|
||||||
const pins = SettingsData.brightnessDevicePins || {}
|
const pins = SettingsData.brightnessDevicePins || {};
|
||||||
return pins[screenName] === currentDeviceName
|
return pins[pinKey] === currentDeviceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
function togglePinToScreen() {
|
function togglePinToScreen() {
|
||||||
if (!screenName || screenName.length === 0 || !currentDeviceName || currentDeviceName.length === 0) {
|
const pinKey = getScreenPinKey();
|
||||||
return
|
if (!pinKey || pinKey.length === 0 || !currentDeviceName || currentDeviceName.length === 0)
|
||||||
}
|
return;
|
||||||
|
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}));
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}))
|
|
||||||
|
|
||||||
if (isPinnedToScreen) {
|
if (isPinnedToScreen) {
|
||||||
delete pins[screenName]
|
delete pins[pinKey];
|
||||||
} else {
|
} else {
|
||||||
pins[screenName] = currentDeviceName
|
pins[pinKey] = currentDeviceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsData.set("brightnessDevicePins", pins)
|
SettingsData.set("brightnessDevicePins", pins);
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitHeight: {
|
implicitHeight: {
|
||||||
if (height > 0) {
|
if (height > 0) {
|
||||||
return height
|
return height;
|
||||||
}
|
}
|
||||||
return brightnessContent.height + Theme.spacingM
|
return brightnessContent.height + Theme.spacingM;
|
||||||
}
|
}
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
@@ -165,7 +166,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: screenName || "Unknown Monitor"
|
text: root.getScreenPinKey() || "Unknown Monitor"
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -216,8 +217,8 @@ Rectangle {
|
|||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
property real deviceBrightness: {
|
property real deviceBrightness: {
|
||||||
DisplayService.brightnessVersion
|
DisplayService.brightnessVersion;
|
||||||
return DisplayService.getDeviceBrightness(modelData.name)
|
return DisplayService.getDeviceBrightness(modelData.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -248,19 +249,19 @@ Rectangle {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: {
|
name: {
|
||||||
const deviceClass = modelData.class || ""
|
const deviceClass = modelData.class || "";
|
||||||
const deviceName = modelData.name || ""
|
const deviceName = modelData.name || "";
|
||||||
|
|
||||||
if (deviceClass === "backlight" || deviceClass === "ddc") {
|
if (deviceClass === "backlight" || deviceClass === "ddc") {
|
||||||
if (deviceBrightness <= 33)
|
if (deviceBrightness <= 33)
|
||||||
return "brightness_low"
|
return "brightness_low";
|
||||||
if (deviceBrightness <= 66)
|
if (deviceBrightness <= 66)
|
||||||
return "brightness_medium"
|
return "brightness_medium";
|
||||||
return "brightness_high"
|
return "brightness_high";
|
||||||
} else if (deviceName.includes("kbd")) {
|
} else if (deviceName.includes("kbd")) {
|
||||||
return "keyboard"
|
return "keyboard";
|
||||||
} else {
|
} else {
|
||||||
return "lightbulb"
|
return "lightbulb";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
@@ -283,12 +284,12 @@ Rectangle {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
const name = modelData.name || ""
|
const name = modelData.name || "";
|
||||||
const deviceClass = modelData.class || ""
|
const deviceClass = modelData.class || "";
|
||||||
if (deviceClass === "backlight") {
|
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
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
@@ -307,14 +308,14 @@ Rectangle {
|
|||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
const deviceClass = modelData.class || ""
|
const deviceClass = modelData.class || "";
|
||||||
if (deviceClass === "backlight")
|
if (deviceClass === "backlight")
|
||||||
return "Backlight device"
|
return "Backlight device";
|
||||||
if (deviceClass === "ddc")
|
if (deviceClass === "ddc")
|
||||||
return "DDC/CI monitor"
|
return "DDC/CI monitor";
|
||||||
if (deviceClass === "leds")
|
if (deviceClass === "leds")
|
||||||
return "LED device"
|
return "LED device";
|
||||||
return deviceClass
|
return deviceClass;
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceVariantText
|
color: Theme.surfaceVariantText
|
||||||
@@ -353,9 +354,9 @@ Rectangle {
|
|||||||
cornerRadius: parent.radius
|
cornerRadius: parent.radius
|
||||||
enabled: SessionData.getBrightnessExponent(modelData.name) > 1.0
|
enabled: SessionData.getBrightnessExponent(modelData.name) > 1.0
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const current = SessionData.getBrightnessExponent(modelData.name)
|
const current = SessionData.getBrightnessExponent(modelData.name);
|
||||||
const newValue = Math.max(1.0, Math.round((current - 0.1) * 10) / 10)
|
const newValue = Math.max(1.0, Math.round((current - 0.1) * 10) / 10);
|
||||||
SessionData.setBrightnessExponent(modelData.name, newValue)
|
SessionData.setBrightnessExponent(modelData.name, newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -395,9 +396,9 @@ Rectangle {
|
|||||||
cornerRadius: parent.radius
|
cornerRadius: parent.radius
|
||||||
enabled: SessionData.getBrightnessExponent(modelData.name) < 2.5
|
enabled: SessionData.getBrightnessExponent(modelData.name) < 2.5
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const current = SessionData.getBrightnessExponent(modelData.name)
|
const current = SessionData.getBrightnessExponent(modelData.name);
|
||||||
const newValue = Math.min(2.5, Math.round((current + 0.1) * 10) / 10)
|
const newValue = Math.min(2.5, Math.round((current + 0.1) * 10) / 10);
|
||||||
SessionData.setBrightnessExponent(modelData.name, newValue)
|
SessionData.setBrightnessExponent(modelData.name, newValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -433,8 +434,8 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const currentState = SessionData.getBrightnessExponential(modelData.name)
|
const currentState = SessionData.getBrightnessExponential(modelData.name);
|
||||||
SessionData.setBrightnessExponential(modelData.name, !currentState)
|
SessionData.setBrightnessExponential(modelData.name, !currentState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -447,15 +448,16 @@ Rectangle {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (screenName && screenName.length > 0 && modelData.name !== currentDeviceName) {
|
const pinKey = root.getScreenPinKey();
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}))
|
if (pinKey.length > 0 && modelData.name !== currentDeviceName) {
|
||||||
if (pins[screenName]) {
|
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}));
|
||||||
delete pins[screenName]
|
if (pins[pinKey]) {
|
||||||
SettingsData.set("brightnessDevicePins", pins)
|
delete pins[pinKey];
|
||||||
|
SettingsData.set("brightnessDevicePins", pins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
currentDeviceName = modelData.name
|
currentDeviceName = modelData.name;
|
||||||
deviceNameChanged(modelData.name)
|
deviceNameChanged(modelData.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,15 @@ Rectangle {
|
|||||||
|
|
||||||
implicitHeight: {
|
implicitHeight: {
|
||||||
if (height > 0) {
|
if (height > 0) {
|
||||||
return height
|
return height;
|
||||||
}
|
}
|
||||||
if (NetworkService.wifiToggling) {
|
if (NetworkService.wifiToggling) {
|
||||||
return headerRow.height + wifiToggleContent.height + Theme.spacingM
|
return headerRow.height + wifiToggleContent.height + Theme.spacingM;
|
||||||
}
|
}
|
||||||
if (NetworkService.wifiEnabled) {
|
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
|
radius: Theme.cornerRadius
|
||||||
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
color: Theme.withAlpha(Theme.surfaceContainerHigh, Theme.popupTransparency)
|
||||||
@@ -27,35 +27,35 @@ Rectangle {
|
|||||||
border.width: 0
|
border.width: 0
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
NetworkService.addRef()
|
NetworkService.addRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
NetworkService.removeRef()
|
NetworkService.removeRef();
|
||||||
}
|
}
|
||||||
|
|
||||||
property int currentPreferenceIndex: {
|
property int currentPreferenceIndex: {
|
||||||
if (DMSService.apiVersion < 5) {
|
if (DMSService.apiVersion < 5) {
|
||||||
return 1
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NetworkService.backend !== "networkmanager" || DMSService.apiVersion <= 10) {
|
if (NetworkService.backend !== "networkmanager" || DMSService.apiVersion <= 10) {
|
||||||
return 1
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pref = NetworkService.userPreference
|
const pref = NetworkService.userPreference;
|
||||||
const status = NetworkService.networkStatus
|
const status = NetworkService.networkStatus;
|
||||||
let index = 1
|
let index = 1;
|
||||||
|
|
||||||
if (pref === "ethernet") {
|
if (pref === "ethernet") {
|
||||||
index = 0
|
index = 0;
|
||||||
} else if (pref === "wifi") {
|
} else if (pref === "wifi") {
|
||||||
index = 1
|
index = 1;
|
||||||
} else {
|
} else {
|
||||||
index = status === "ethernet" ? 0 : 1
|
index = status === "ethernet" ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
return index
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -78,28 +78,56 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
width: Math.max(0, parent.width - headerText.implicitWidth - preferenceControls.width - Theme.spacingM)
|
height: 1
|
||||||
height: parent.height
|
width: parent.width - headerText.width - rightControls.width
|
||||||
}
|
}
|
||||||
|
|
||||||
DankButtonGroup {
|
Row {
|
||||||
id: preferenceControls
|
id: rightControls
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: NetworkService.backend === "networkmanager" && DMSService.apiVersion > 10
|
spacing: Theme.spacingS
|
||||||
|
|
||||||
model: ["Ethernet", "WiFi"]
|
DankDropdown {
|
||||||
currentIndex: currentPreferenceIndex
|
id: wifiDeviceDropdown
|
||||||
selectionMode: "single"
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
onSelectionChanged: (index, selected) => {
|
visible: currentPreferenceIndex === 1 && (NetworkService.wifiDevices?.length ?? 0) > 1
|
||||||
if (!selected) return
|
compactMode: true
|
||||||
console.log("NetworkDetail: Setting preference to", index === 0 ? "ethernet" : "wifi")
|
dropdownWidth: 120
|
||||||
NetworkService.setNetworkPreference(index === 0 ? "ethernet" : "wifi")
|
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 {
|
Item {
|
||||||
id: wifiToggleContent
|
id: wifiToggleContent
|
||||||
anchors.top: headerRow.bottom
|
anchors.top: headerRow.bottom
|
||||||
@@ -194,7 +222,6 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: NetworkService.toggleWifiRadio()
|
onClicked: NetworkService.toggleWifiRadio()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,15 +246,17 @@ Rectangle {
|
|||||||
Repeater {
|
Repeater {
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: {
|
values: {
|
||||||
const currentUuid = NetworkService.ethernetConnectionUuid
|
const currentUuid = NetworkService.ethernetConnectionUuid;
|
||||||
const networks = NetworkService.wiredConnections
|
const networks = NetworkService.wiredConnections;
|
||||||
let sorted = [...networks]
|
let sorted = [...networks];
|
||||||
sorted.sort((a, b) => {
|
sorted.sort((a, b) => {
|
||||||
if (a.isActive && !b.isActive) return -1
|
if (a.isActive && !b.isActive)
|
||||||
if (!a.isActive && b.isActive) return 1
|
return -1;
|
||||||
return a.id.localeCompare(b.id)
|
if (!a.isActive && b.isActive)
|
||||||
})
|
return 1;
|
||||||
return sorted
|
return a.id.localeCompare(b.id);
|
||||||
|
});
|
||||||
|
return sorted;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -279,12 +308,12 @@ Rectangle {
|
|||||||
buttonSize: 28
|
buttonSize: 28
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (wiredNetworkContextMenu.visible) {
|
if (wiredNetworkContextMenu.visible) {
|
||||||
wiredNetworkContextMenu.close()
|
wiredNetworkContextMenu.close();
|
||||||
} else {
|
} else {
|
||||||
wiredNetworkContextMenu.currentID = modelData.id
|
wiredNetworkContextMenu.currentID = modelData.id;
|
||||||
wiredNetworkContextMenu.currentUUID = modelData.uuid
|
wiredNetworkContextMenu.currentUUID = modelData.uuid;
|
||||||
wiredNetworkContextMenu.currentConnected = modelData.isActive
|
wiredNetworkContextMenu.currentConnected = modelData.isActive;
|
||||||
wiredNetworkContextMenu.popup(wiredOptionsButton, -wiredNetworkContextMenu.width + wiredOptionsButton.width, wiredOptionsButton.height + Theme.spacingXS)
|
wiredNetworkContextMenu.popup(wiredOptionsButton, -wiredNetworkContextMenu.width + wiredOptionsButton.width, wiredOptionsButton.height + Theme.spacingXS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -295,14 +324,13 @@ Rectangle {
|
|||||||
anchors.rightMargin: wiredOptionsButton.width + Theme.spacingS
|
anchors.rightMargin: wiredOptionsButton.width + Theme.spacingS
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: function(event) {
|
onClicked: function (event) {
|
||||||
if (modelData.uuid !== NetworkService.ethernetConnectionUuid) {
|
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: {
|
onTriggered: {
|
||||||
if (!networkContextMenu.currentConnected) {
|
if (!networkContextMenu.currentConnected) {
|
||||||
NetworkService.connectToSpecificWiredConfig(wiredNetworkContextMenu.currentUUID)
|
NetworkService.connectToSpecificWiredConfig(wiredNetworkContextMenu.currentUUID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -366,8 +394,8 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
let networkData = NetworkService.getWiredNetworkInfo(wiredNetworkContextMenu.currentUUID)
|
let networkData = NetworkService.getWiredNetworkInfo(wiredNetworkContextMenu.currentUUID);
|
||||||
networkWiredInfoModal.showNetworkInfo(wiredNetworkContextMenu.currentID, networkData)
|
networkWiredInfoModal.showNetworkInfo(wiredNetworkContextMenu.currentID, networkData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,26 +444,30 @@ Rectangle {
|
|||||||
Repeater {
|
Repeater {
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
values: {
|
values: {
|
||||||
const ssid = NetworkService.currentWifiSSID
|
const ssid = NetworkService.currentWifiSSID;
|
||||||
const networks = NetworkService.wifiNetworks
|
const networks = NetworkService.wifiNetworks;
|
||||||
const pins = SettingsData.wifiNetworkPins || {}
|
const pins = SettingsData.wifiNetworkPins || {};
|
||||||
const pinnedSSID = pins["preferredWifi"]
|
const pinnedSSID = pins["preferredWifi"];
|
||||||
|
|
||||||
let sorted = [...networks]
|
let sorted = [...networks];
|
||||||
sorted.sort((a, b) => {
|
sorted.sort((a, b) => {
|
||||||
// Pinned network first
|
// Pinned network first
|
||||||
if (a.ssid === pinnedSSID && b.ssid !== pinnedSSID) return -1
|
if (a.ssid === pinnedSSID && b.ssid !== pinnedSSID)
|
||||||
if (b.ssid === pinnedSSID && a.ssid !== pinnedSSID) return 1
|
return -1;
|
||||||
|
if (b.ssid === pinnedSSID && a.ssid !== pinnedSSID)
|
||||||
|
return 1;
|
||||||
// Then currently connected
|
// Then currently connected
|
||||||
if (a.ssid === ssid) return -1
|
if (a.ssid === ssid)
|
||||||
if (b.ssid === ssid) return 1
|
return -1;
|
||||||
|
if (b.ssid === ssid)
|
||||||
|
return 1;
|
||||||
// Then by signal strength
|
// Then by signal strength
|
||||||
return b.signal - a.signal
|
return b.signal - a.signal;
|
||||||
})
|
});
|
||||||
if (!wifiContent.menuOpen) {
|
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 {
|
DankIcon {
|
||||||
name: {
|
name: {
|
||||||
let strength = modelData.signal || 0
|
let strength = modelData.signal || 0;
|
||||||
if (strength >= 50) return "wifi"
|
if (strength >= 50)
|
||||||
if (strength >= 25) return "wifi_2_bar"
|
return "wifi";
|
||||||
return "wifi_1_bar"
|
if (strength >= 25)
|
||||||
|
return "wifi_2_bar";
|
||||||
|
return "wifi_1_bar";
|
||||||
}
|
}
|
||||||
size: Theme.iconSize - 4
|
size: Theme.iconSize - 4
|
||||||
color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Theme.surfaceText
|
color: modelData.ssid === NetworkService.currentWifiSSID ? Theme.primary : Theme.surfaceText
|
||||||
@@ -515,16 +549,16 @@ Rectangle {
|
|||||||
buttonSize: 28
|
buttonSize: 28
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (networkContextMenu.visible) {
|
if (networkContextMenu.visible) {
|
||||||
networkContextMenu.close()
|
networkContextMenu.close();
|
||||||
} else {
|
} else {
|
||||||
wifiContent.menuOpen = true
|
wifiContent.menuOpen = true;
|
||||||
networkContextMenu.currentSSID = modelData.ssid
|
networkContextMenu.currentSSID = modelData.ssid;
|
||||||
networkContextMenu.currentSecured = modelData.secured
|
networkContextMenu.currentSecured = modelData.secured;
|
||||||
networkContextMenu.currentConnected = modelData.ssid === NetworkService.currentWifiSSID
|
networkContextMenu.currentConnected = modelData.ssid === NetworkService.currentWifiSSID;
|
||||||
networkContextMenu.currentSaved = modelData.saved
|
networkContextMenu.currentSaved = modelData.saved;
|
||||||
networkContextMenu.currentSignal = modelData.signal
|
networkContextMenu.currentSignal = modelData.signal;
|
||||||
networkContextMenu.currentAutoconnect = modelData.autoconnect || false
|
networkContextMenu.currentAutoconnect = modelData.autoconnect || false;
|
||||||
networkContextMenu.popup(optionsButton, -networkContextMenu.width + optionsButton.width, optionsButton.height + Theme.spacingXS)
|
networkContextMenu.popup(optionsButton, -networkContextMenu.width + optionsButton.width, optionsButton.height + Theme.spacingXS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -537,8 +571,8 @@ Rectangle {
|
|||||||
height: 28
|
height: 28
|
||||||
radius: height / 2
|
radius: height / 2
|
||||||
color: {
|
color: {
|
||||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid
|
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)
|
return isThisNetworkPinned ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : Theme.withAlpha(Theme.surfaceText, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
@@ -550,21 +584,21 @@ Rectangle {
|
|||||||
name: "push_pin"
|
name: "push_pin"
|
||||||
size: 16
|
size: 16
|
||||||
color: {
|
color: {
|
||||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid
|
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
|
||||||
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText
|
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText;
|
||||||
}
|
}
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: {
|
text: {
|
||||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid
|
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
|
||||||
return isThisNetworkPinned ? "Pinned" : "Pin"
|
return isThisNetworkPinned ? "Pinned" : "Pin";
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: {
|
color: {
|
||||||
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid
|
const isThisNetworkPinned = (SettingsData.wifiNetworkPins || {})["preferredWifi"] === modelData.ssid;
|
||||||
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText
|
return isThisNetworkPinned ? Theme.primary : Theme.surfaceText;
|
||||||
}
|
}
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
@@ -574,16 +608,16 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}))
|
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}));
|
||||||
const isCurrentlyPinned = pins["preferredWifi"] === modelData.ssid
|
const isCurrentlyPinned = pins["preferredWifi"] === modelData.ssid;
|
||||||
|
|
||||||
if (isCurrentlyPinned) {
|
if (isCurrentlyPinned) {
|
||||||
delete pins["preferredWifi"]
|
delete pins["preferredWifi"];
|
||||||
} else {
|
} 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
|
anchors.rightMargin: optionsButton.width + Theme.spacingM + Theme.spacingS + pinWifiRow.width + Theme.spacingS * 4
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: function(event) {
|
onClicked: function (event) {
|
||||||
if (modelData.ssid !== NetworkService.currentWifiSSID) {
|
if (modelData.ssid !== NetworkService.currentWifiSSID) {
|
||||||
if (modelData.secured && !modelData.saved) {
|
if (modelData.secured && !modelData.saved) {
|
||||||
if (DMSService.apiVersion >= 7) {
|
if (DMSService.apiVersion >= 7) {
|
||||||
NetworkService.connectToWifi(modelData.ssid)
|
NetworkService.connectToWifi(modelData.ssid);
|
||||||
} else if (PopoutService.wifiPasswordModal) {
|
} else if (PopoutService.wifiPasswordModal) {
|
||||||
PopoutService.wifiPasswordModal.show(modelData.ssid)
|
PopoutService.wifiPasswordModal.show(modelData.ssid);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NetworkService.connectToWifi(modelData.ssid)
|
NetworkService.connectToWifi(modelData.ssid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -628,7 +661,7 @@ Rectangle {
|
|||||||
property bool currentAutoconnect: false
|
property bool currentAutoconnect: false
|
||||||
|
|
||||||
onClosed: {
|
onClosed: {
|
||||||
wifiContent.menuOpen = false
|
wifiContent.menuOpen = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
@@ -657,16 +690,16 @@ Rectangle {
|
|||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (networkContextMenu.currentConnected) {
|
if (networkContextMenu.currentConnected) {
|
||||||
NetworkService.disconnectWifi()
|
NetworkService.disconnectWifi();
|
||||||
} else {
|
} else {
|
||||||
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
|
if (networkContextMenu.currentSecured && !networkContextMenu.currentSaved) {
|
||||||
if (DMSService.apiVersion >= 7) {
|
if (DMSService.apiVersion >= 7) {
|
||||||
NetworkService.connectToWifi(networkContextMenu.currentSSID)
|
NetworkService.connectToWifi(networkContextMenu.currentSSID);
|
||||||
} else if (PopoutService.wifiPasswordModal) {
|
} else if (PopoutService.wifiPasswordModal) {
|
||||||
PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID)
|
PopoutService.wifiPasswordModal.show(networkContextMenu.currentSSID);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
NetworkService.connectToWifi(networkContextMenu.currentSSID)
|
NetworkService.connectToWifi(networkContextMenu.currentSSID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -690,8 +723,8 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID)
|
let networkData = NetworkService.getNetworkInfo(networkContextMenu.currentSSID);
|
||||||
networkInfoModal.showNetworkInfo(networkContextMenu.currentSSID, networkData)
|
networkInfoModal.showNetworkInfo(networkContextMenu.currentSSID, networkData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,7 +747,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
NetworkService.setWifiAutoconnect(networkContextMenu.currentSSID, !networkContextMenu.currentAutoconnect)
|
NetworkService.setWifiAutoconnect(networkContextMenu.currentSSID, !networkContextMenu.currentAutoconnect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -737,7 +770,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
NetworkService.forgetWifiNetwork(networkContextMenu.currentSSID)
|
NetworkService.forgetWifiNetwork(networkContextMenu.currentSSID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,17 +17,17 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onItemChanged: {
|
onItemChanged: {
|
||||||
root.vpnBuiltinInstance = item
|
root.vpnBuiltinInstance = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SettingsData
|
target: SettingsData
|
||||||
function onControlCenterWidgetsChanged() {
|
function onControlCenterWidgetsChanged() {
|
||||||
const widgets = SettingsData.controlCenterWidgets || []
|
const widgets = SettingsData.controlCenterWidgets || [];
|
||||||
const hasVpnWidget = widgets.some(w => w.id === "builtin_vpn")
|
const hasVpnWidget = widgets.some(w => w.id === "builtin_vpn");
|
||||||
if (!hasVpnWidget && vpnLoader.active) {
|
if (!hasVpnWidget && vpnLoader.active) {
|
||||||
console.log("VpnWidget: No VPN widget in control center, deactivating loader")
|
console.log("VpnWidget: No VPN widget in control center, deactivating loader");
|
||||||
vpnLoader.active = false
|
vpnLoader.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -40,35 +40,36 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onItemChanged: {
|
onItemChanged: {
|
||||||
root.cupsBuiltinInstance = item
|
root.cupsBuiltinInstance = item;
|
||||||
if (item && !DMSService.activeSubscriptions.includes("cups") && !DMSService.activeSubscriptions.includes("all")) {
|
if (item && !DMSService.activeSubscriptions.includes("cups") && !DMSService.activeSubscriptions.includes("all")) {
|
||||||
DMSService.addSubscription("cups")
|
DMSService.addSubscription("cups");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onActiveChanged: {
|
onActiveChanged: {
|
||||||
if (!active) {
|
if (!active) {
|
||||||
if (DMSService.activeSubscriptions.includes("cups")) {
|
if (DMSService.activeSubscriptions.includes("cups")) {
|
||||||
DMSService.removeSubscription("cups")
|
DMSService.removeSubscription("cups");
|
||||||
}
|
}
|
||||||
root.cupsBuiltinInstance = null
|
root.cupsBuiltinInstance = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: SettingsData
|
target: SettingsData
|
||||||
function onControlCenterWidgetsChanged() {
|
function onControlCenterWidgetsChanged() {
|
||||||
const widgets = SettingsData.controlCenterWidgets || []
|
const widgets = SettingsData.controlCenterWidgets || [];
|
||||||
const hasCupsWidget = widgets.some(w => w.id === "builtin_cups")
|
const hasCupsWidget = widgets.some(w => w.id === "builtin_cups");
|
||||||
if (!hasCupsWidget && cupsLoader.active) {
|
if (!hasCupsWidget && cupsLoader.active) {
|
||||||
console.log("CupsWidget: No CUPS widget in control center, deactivating loader")
|
console.log("CupsWidget: No CUPS widget in control center, deactivating loader");
|
||||||
cupsLoader.active = false
|
cupsLoader.active = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property var coreWidgetDefinitions: [{
|
readonly property var coreWidgetDefinitions: [
|
||||||
|
{
|
||||||
"id": "nightMode",
|
"id": "nightMode",
|
||||||
"text": "Night Mode",
|
"text": "Night Mode",
|
||||||
"description": "Blue light filter",
|
"description": "Blue light filter",
|
||||||
@@ -76,28 +77,32 @@ QtObject {
|
|||||||
"type": "toggle",
|
"type": "toggle",
|
||||||
"enabled": DisplayService.automationAvailable,
|
"enabled": DisplayService.automationAvailable,
|
||||||
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
|
"warning": !DisplayService.automationAvailable ? "Requires night mode support" : undefined
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "darkMode",
|
"id": "darkMode",
|
||||||
"text": "Dark Mode",
|
"text": "Dark Mode",
|
||||||
"description": "System theme toggle",
|
"description": "System theme toggle",
|
||||||
"icon": "contrast",
|
"icon": "contrast",
|
||||||
"type": "toggle",
|
"type": "toggle",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "doNotDisturb",
|
"id": "doNotDisturb",
|
||||||
"text": "Do Not Disturb",
|
"text": "Do Not Disturb",
|
||||||
"description": "Block notifications",
|
"description": "Block notifications",
|
||||||
"icon": "do_not_disturb_on",
|
"icon": "do_not_disturb_on",
|
||||||
"type": "toggle",
|
"type": "toggle",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "idleInhibitor",
|
"id": "idleInhibitor",
|
||||||
"text": "Keep Awake",
|
"text": "Keep Awake",
|
||||||
"description": "Prevent screen timeout",
|
"description": "Prevent screen timeout",
|
||||||
"icon": "motion_sensor_active",
|
"icon": "motion_sensor_active",
|
||||||
"type": "toggle",
|
"type": "toggle",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "wifi",
|
"id": "wifi",
|
||||||
"text": "Network",
|
"text": "Network",
|
||||||
"description": "Wi-Fi and Ethernet connection",
|
"description": "Wi-Fi and Ethernet connection",
|
||||||
@@ -105,7 +110,8 @@ QtObject {
|
|||||||
"type": "connection",
|
"type": "connection",
|
||||||
"enabled": NetworkService.wifiAvailable,
|
"enabled": NetworkService.wifiAvailable,
|
||||||
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
|
"warning": !NetworkService.wifiAvailable ? "Wi-Fi not available" : undefined
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "bluetooth",
|
"id": "bluetooth",
|
||||||
"text": "Bluetooth",
|
"text": "Bluetooth",
|
||||||
"description": "Device connections",
|
"description": "Device connections",
|
||||||
@@ -113,28 +119,32 @@ QtObject {
|
|||||||
"type": "connection",
|
"type": "connection",
|
||||||
"enabled": BluetoothService.available,
|
"enabled": BluetoothService.available,
|
||||||
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined
|
"warning": !BluetoothService.available ? "Bluetooth not available" : undefined
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "audioOutput",
|
"id": "audioOutput",
|
||||||
"text": "Audio Output",
|
"text": "Audio Output",
|
||||||
"description": "Speaker settings",
|
"description": "Speaker settings",
|
||||||
"icon": "volume_up",
|
"icon": "volume_up",
|
||||||
"type": "connection",
|
"type": "connection",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "audioInput",
|
"id": "audioInput",
|
||||||
"text": "Audio Input",
|
"text": "Audio Input",
|
||||||
"description": "Microphone settings",
|
"description": "Microphone settings",
|
||||||
"icon": "mic",
|
"icon": "mic",
|
||||||
"type": "connection",
|
"type": "connection",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "volumeSlider",
|
"id": "volumeSlider",
|
||||||
"text": "Volume Slider",
|
"text": "Volume Slider",
|
||||||
"description": "Audio volume control",
|
"description": "Audio volume control",
|
||||||
"icon": "volume_up",
|
"icon": "volume_up",
|
||||||
"type": "slider",
|
"type": "slider",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "brightnessSlider",
|
"id": "brightnessSlider",
|
||||||
"text": "Brightness Slider",
|
"text": "Brightness Slider",
|
||||||
"description": "Display brightness control",
|
"description": "Display brightness control",
|
||||||
@@ -143,21 +153,24 @@ QtObject {
|
|||||||
"enabled": DisplayService.brightnessAvailable,
|
"enabled": DisplayService.brightnessAvailable,
|
||||||
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined,
|
"warning": !DisplayService.brightnessAvailable ? "Brightness control not available" : undefined,
|
||||||
"allowMultiple": true
|
"allowMultiple": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "inputVolumeSlider",
|
"id": "inputVolumeSlider",
|
||||||
"text": "Input Volume Slider",
|
"text": "Input Volume Slider",
|
||||||
"description": "Microphone volume control",
|
"description": "Microphone volume control",
|
||||||
"icon": "mic",
|
"icon": "mic",
|
||||||
"type": "slider",
|
"type": "slider",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "battery",
|
"id": "battery",
|
||||||
"text": "Battery",
|
"text": "Battery",
|
||||||
"description": "Battery and power management",
|
"description": "Battery and power management",
|
||||||
"icon": "battery_std",
|
"icon": "battery_std",
|
||||||
"type": "action",
|
"type": "action",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "diskUsage",
|
"id": "diskUsage",
|
||||||
"text": "Disk Usage",
|
"text": "Disk Usage",
|
||||||
"description": "Filesystem usage monitoring",
|
"description": "Filesystem usage monitoring",
|
||||||
@@ -166,14 +179,16 @@ QtObject {
|
|||||||
"enabled": DgopService.dgopAvailable,
|
"enabled": DgopService.dgopAvailable,
|
||||||
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
|
"warning": !DgopService.dgopAvailable ? "Requires 'dgop' tool" : undefined,
|
||||||
"allowMultiple": true
|
"allowMultiple": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "colorPicker",
|
"id": "colorPicker",
|
||||||
"text": "Color Picker",
|
"text": "Color Picker",
|
||||||
"description": "Choose colors from palette",
|
"description": "Choose colors from palette",
|
||||||
"icon": "palette",
|
"icon": "palette",
|
||||||
"type": "action",
|
"type": "action",
|
||||||
"enabled": true
|
"enabled": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "builtin_vpn",
|
"id": "builtin_vpn",
|
||||||
"text": "VPN",
|
"text": "VPN",
|
||||||
"description": "VPN connections",
|
"description": "VPN connections",
|
||||||
@@ -182,7 +197,8 @@ QtObject {
|
|||||||
"enabled": DMSNetworkService.available,
|
"enabled": DMSNetworkService.available,
|
||||||
"warning": !DMSNetworkService.available ? "VPN not available" : undefined,
|
"warning": !DMSNetworkService.available ? "VPN not available" : undefined,
|
||||||
"isBuiltinPlugin": true
|
"isBuiltinPlugin": true
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
"id": "builtin_cups",
|
"id": "builtin_cups",
|
||||||
"text": "Printers",
|
"text": "Printers",
|
||||||
"description": "Print Server Management",
|
"description": "Print Server Management",
|
||||||
@@ -191,78 +207,79 @@ QtObject {
|
|||||||
"enabled": CupsService.available,
|
"enabled": CupsService.available,
|
||||||
"warning": !CupsService.available ? "CUPS not available" : undefined,
|
"warning": !CupsService.available ? "CUPS not available" : undefined,
|
||||||
"isBuiltinPlugin": true
|
"isBuiltinPlugin": true
|
||||||
}]
|
}
|
||||||
|
]
|
||||||
|
|
||||||
function getPluginWidgets() {
|
function getPluginWidgets() {
|
||||||
const plugins = []
|
const plugins = [];
|
||||||
const loadedPlugins = PluginService.getLoadedPlugins()
|
const loadedPlugins = PluginService.getLoadedPlugins();
|
||||||
|
|
||||||
for (var i = 0; i < loadedPlugins.length; i++) {
|
for (var i = 0; i < loadedPlugins.length; i++) {
|
||||||
const plugin = loadedPlugins[i]
|
const plugin = loadedPlugins[i];
|
||||||
|
|
||||||
if (plugin.type === "daemon") {
|
if (plugin.type === "daemon") {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const pluginComponent = PluginService.pluginWidgetComponents[plugin.id]
|
const pluginComponent = PluginService.pluginWidgetComponents[plugin.id];
|
||||||
if (!pluginComponent || typeof pluginComponent.createObject !== 'function') {
|
if (!pluginComponent || typeof pluginComponent.createObject !== 'function') {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tempInstance = pluginComponent.createObject(null)
|
const tempInstance = pluginComponent.createObject(null);
|
||||||
if (!tempInstance) {
|
if (!tempInstance) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasCCWidget = tempInstance.ccWidgetIcon && tempInstance.ccWidgetIcon.length > 0
|
const hasCCWidget = tempInstance.ccWidgetIcon && tempInstance.ccWidgetIcon.length > 0;
|
||||||
tempInstance.destroy()
|
tempInstance.destroy();
|
||||||
|
|
||||||
if (!hasCCWidget) {
|
if (!hasCCWidget) {
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins.push({
|
plugins.push({
|
||||||
"id": "plugin_" + plugin.id,
|
"id": "plugin_" + plugin.id,
|
||||||
"pluginId": plugin.id,
|
"pluginId": plugin.id,
|
||||||
"text": plugin.name || "Plugin",
|
"text": plugin.name || "Plugin",
|
||||||
"description": plugin.description || "",
|
"description": plugin.description || "",
|
||||||
"icon": plugin.icon || "extension",
|
"icon": plugin.icon || "extension",
|
||||||
"type": "plugin",
|
"type": "plugin",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"isPlugin": true
|
"isPlugin": true
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return plugins
|
return plugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property var baseWidgetDefinitions: coreWidgetDefinitions
|
readonly property var baseWidgetDefinitions: coreWidgetDefinitions
|
||||||
|
|
||||||
function getWidgetForId(widgetId) {
|
function getWidgetForId(widgetId) {
|
||||||
return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId)
|
return WidgetUtils.getWidgetForId(baseWidgetDefinitions, widgetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addWidget(widgetId) {
|
function addWidget(widgetId) {
|
||||||
WidgetUtils.addWidget(widgetId)
|
WidgetUtils.addWidget(widgetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeWidget(index) {
|
function removeWidget(index) {
|
||||||
WidgetUtils.removeWidget(index)
|
WidgetUtils.removeWidget(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleWidgetSize(index) {
|
function toggleWidgetSize(index) {
|
||||||
WidgetUtils.toggleWidgetSize(index)
|
WidgetUtils.toggleWidgetSize(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveWidget(fromIndex, toIndex) {
|
function moveWidget(fromIndex, toIndex) {
|
||||||
WidgetUtils.moveWidget(fromIndex, toIndex)
|
WidgetUtils.moveWidget(fromIndex, toIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetToDefault() {
|
function resetToDefault() {
|
||||||
WidgetUtils.resetToDefault()
|
WidgetUtils.resetToDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearAll() {
|
function clearAll() {
|
||||||
WidgetUtils.clearAll()
|
WidgetUtils.clearAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1144,6 +1144,8 @@ Item {
|
|||||||
return controlCenterLoader.item;
|
return controlCenterLoader.item;
|
||||||
}
|
}
|
||||||
parentScreen: barWindow.screen
|
parentScreen: barWindow.screen
|
||||||
|
screenName: barWindow.screen?.name || ""
|
||||||
|
screenModel: barWindow.screen?.model || ""
|
||||||
widgetData: parent.widgetData
|
widgetData: parent.widgetData
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
|||||||
@@ -155,35 +155,56 @@ Loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onLoaded: {
|
onLoaded: {
|
||||||
if (item) {
|
if (!item)
|
||||||
contentItemReady(item);
|
return;
|
||||||
if (axis && "isVertical" in item) {
|
|
||||||
try {
|
|
||||||
item.isVertical = axis.isVertical;
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.pluginService !== undefined) {
|
contentItemReady(item);
|
||||||
var parts = widgetId.split(":");
|
|
||||||
var pluginId = parts[0];
|
|
||||||
var variantId = parts.length > 1 ? parts[1] : null;
|
|
||||||
|
|
||||||
if (item.pluginId !== undefined) {
|
if (axis && "isVertical" in item) {
|
||||||
item.pluginId = pluginId;
|
try {
|
||||||
}
|
item.isVertical = axis.isVertical;
|
||||||
if (item.variantId !== undefined) {
|
} catch (e) {}
|
||||||
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 (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) {
|
function getWidgetComponent(widgetId, components) {
|
||||||
|
|||||||
@@ -10,9 +10,138 @@ BasePill {
|
|||||||
property bool isActive: false
|
property bool isActive: false
|
||||||
property var popoutTarget: null
|
property var popoutTarget: null
|
||||||
property var widgetData: null
|
property var widgetData: null
|
||||||
|
property string screenName: ""
|
||||||
|
property string screenModel: ""
|
||||||
property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon
|
property bool showNetworkIcon: SettingsData.controlCenterShowNetworkIcon
|
||||||
property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon
|
property bool showBluetoothIcon: SettingsData.controlCenterShowBluetoothIcon
|
||||||
property bool showAudioIcon: SettingsData.controlCenterShowAudioIcon
|
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 {
|
content: Component {
|
||||||
Item {
|
Item {
|
||||||
@@ -26,34 +155,21 @@ BasePill {
|
|||||||
spacing: Theme.spacingXS
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: {
|
name: root.getNetworkIconName()
|
||||||
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
|
|
||||||
}
|
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness)
|
||||||
color: {
|
color: root.getNetworkIconColor()
|
||||||
if (NetworkService.wifiToggling) {
|
|
||||||
return Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
|
|
||||||
}
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
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 {
|
DankIcon {
|
||||||
name: "bluetooth"
|
name: "bluetooth"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness)
|
||||||
@@ -71,19 +187,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
id: audioIconV
|
id: audioIconV
|
||||||
|
name: root.getVolumeIconName()
|
||||||
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"
|
|
||||||
}
|
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness)
|
||||||
color: Theme.widgetIconColor
|
color: Theme.widgetIconColor
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -92,31 +196,85 @@ BasePill {
|
|||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
onWheel: function(wheelEvent) {
|
onWheel: function (wheelEvent) {
|
||||||
let delta = wheelEvent.angleDelta.y
|
root.handleVolumeWheel(wheelEvent.angleDelta.y);
|
||||||
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0
|
wheelEvent.accepted = true;
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
DankIcon {
|
||||||
name: "settings"
|
name: "settings"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness)
|
||||||
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: !root.showNetworkIcon && !root.showBluetoothIcon && !root.showAudioIcon
|
visible: root.hasNoVisibleIcons()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,38 +286,24 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
id: networkIcon
|
id: networkIcon
|
||||||
|
name: root.getNetworkIconName()
|
||||||
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
|
|
||||||
}
|
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness)
|
||||||
color: {
|
color: root.getNetworkIconColor()
|
||||||
if (NetworkService.wifiToggling) {
|
|
||||||
return Theme.primary
|
|
||||||
}
|
|
||||||
|
|
||||||
return NetworkService.networkStatus !== "disconnected" ? Theme.primary : Theme.outlineButton
|
|
||||||
}
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
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"
|
name: "bluetooth"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness)
|
||||||
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
|
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
|
||||||
@@ -176,19 +320,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
id: audioIcon
|
id: audioIcon
|
||||||
|
name: root.getVolumeIconName()
|
||||||
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";
|
|
||||||
}
|
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness)
|
||||||
color: Theme.widgetIconColor
|
color: Theme.widgetIconColor
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
@@ -196,34 +328,83 @@ BasePill {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: audioWheelArea
|
id: audioWheelArea
|
||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.NoButton
|
acceptedButtons: Qt.NoButton
|
||||||
onWheel: function(wheelEvent) {
|
onWheel: function (wheelEvent) {
|
||||||
let delta = wheelEvent.angleDelta.y;
|
root.handleVolumeWheel(wheelEvent.angleDelta.y);
|
||||||
let currentVolume = (AudioService.sink && AudioService.sink.audio && AudioService.sink.audio.volume * 100) || 0;
|
wheelEvent.accepted = true;
|
||||||
let newVolume;
|
}
|
||||||
if (delta > 0) {
|
}
|
||||||
newVolume = Math.min(100, currentVolume + 5);
|
}
|
||||||
} else {
|
|
||||||
newVolume = Math.max(0, currentVolume - 5);
|
Rectangle {
|
||||||
}
|
width: micIcon.implicitWidth + 4
|
||||||
if (AudioService.sink && AudioService.sink.audio) {
|
height: micIcon.implicitHeight + 4
|
||||||
AudioService.sink.audio.muted = false;
|
color: "transparent"
|
||||||
AudioService.sink.audio.volume = newVolume / 100;
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
AudioService.playVolumeChangeSoundIfEnabled();
|
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;
|
wheelEvent.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
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)
|
size: Theme.barIconSize(root.barThickness)
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: false
|
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
@@ -231,9 +412,15 @@ BasePill {
|
|||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness)
|
||||||
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
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
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Effects
|
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Services.Mpris
|
|
||||||
import Quickshell.Wayland
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
import qs.Modules.DankDash
|
|
||||||
|
|
||||||
DankPopout {
|
DankPopout {
|
||||||
id: root
|
id: root
|
||||||
@@ -27,42 +22,130 @@ DankPopout {
|
|||||||
property bool __focusArmed: false
|
property bool __focusArmed: false
|
||||||
property bool __contentReady: 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() {
|
function __tryFocusOnce() {
|
||||||
if (!__focusArmed)
|
if (!__focusArmed)
|
||||||
return
|
return;
|
||||||
const win = root.window
|
const win = root.window;
|
||||||
if (!win || !win.visible)
|
if (!win || !win.visible)
|
||||||
return
|
return;
|
||||||
if (!contentLoader.item)
|
if (!contentLoader.item)
|
||||||
return
|
return;
|
||||||
|
|
||||||
if (win.requestActivate)
|
if (win.requestActivate)
|
||||||
win.requestActivate()
|
win.requestActivate();
|
||||||
contentLoader.item.forceActiveFocus(Qt.TabFocusReason)
|
contentLoader.item.forceActiveFocus(Qt.TabFocusReason);
|
||||||
|
|
||||||
if (contentLoader.item.activeFocus)
|
if (contentLoader.item.activeFocus)
|
||||||
__focusArmed = false
|
__focusArmed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onDashVisibleChanged: {
|
onDashVisibleChanged: {
|
||||||
if (dashVisible) {
|
if (dashVisible) {
|
||||||
__focusArmed = true
|
__focusArmed = true;
|
||||||
__contentReady = !!contentLoader.item
|
__contentReady = !!contentLoader.item;
|
||||||
open()
|
open();
|
||||||
__tryFocusOnce()
|
__tryFocusOnce();
|
||||||
} else {
|
} else {
|
||||||
__focusArmed = false
|
__focusArmed = false;
|
||||||
__contentReady = false
|
__contentReady = false;
|
||||||
close()
|
__hideDropdowns();
|
||||||
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: contentLoader
|
target: contentLoader
|
||||||
function onLoaded() {
|
function onLoaded() {
|
||||||
__contentReady = true
|
__contentReady = true;
|
||||||
if (__focusArmed)
|
if (__focusArmed)
|
||||||
__tryFocusOnce()
|
__tryFocusOnce();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,12 +154,12 @@ DankPopout {
|
|||||||
enabled: !!root.window
|
enabled: !!root.window
|
||||||
function onVisibleChanged() {
|
function onVisibleChanged() {
|
||||||
if (__focusArmed)
|
if (__focusArmed)
|
||||||
__tryFocusOnce()
|
__tryFocusOnce();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackgroundClicked: {
|
onBackgroundClicked: {
|
||||||
dashVisible = false
|
dashVisible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
content: Component {
|
content: Component {
|
||||||
@@ -90,7 +173,7 @@ DankPopout {
|
|||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (root.shouldBeVisible) {
|
if (root.shouldBeVisible) {
|
||||||
mainContainer.forceActiveFocus()
|
mainContainer.forceActiveFocus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,54 +182,54 @@ DankPopout {
|
|||||||
function onShouldBeVisibleChanged() {
|
function onShouldBeVisibleChanged() {
|
||||||
if (root.shouldBeVisible) {
|
if (root.shouldBeVisible) {
|
||||||
Qt.callLater(function () {
|
Qt.callLater(function () {
|
||||||
mainContainer.forceActiveFocus()
|
mainContainer.forceActiveFocus();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onPressed: function (event) {
|
Keys.onPressed: function (event) {
|
||||||
if (event.key === Qt.Key_Escape) {
|
if (event.key === Qt.Key_Escape) {
|
||||||
root.dashVisible = false
|
root.dashVisible = false;
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_Tab && !(event.modifiers & Qt.ShiftModifier)) {
|
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) {
|
while (nextIndex < tabBar.model.length && tabBar.model[nextIndex] && tabBar.model[nextIndex].isAction) {
|
||||||
nextIndex++
|
nextIndex++;
|
||||||
}
|
}
|
||||||
if (nextIndex >= tabBar.model.length) {
|
if (nextIndex >= tabBar.model.length) {
|
||||||
nextIndex = 0
|
nextIndex = 0;
|
||||||
}
|
}
|
||||||
root.currentTabIndex = nextIndex
|
root.currentTabIndex = nextIndex;
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.key === Qt.Key_Backtab || (event.key === Qt.Key_Tab && (event.modifiers & Qt.ShiftModifier))) {
|
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) {
|
while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) {
|
||||||
prevIndex--
|
prevIndex--;
|
||||||
}
|
}
|
||||||
if (prevIndex < 0) {
|
if (prevIndex < 0) {
|
||||||
prevIndex = tabBar.model.length - 1
|
prevIndex = tabBar.model.length - 1;
|
||||||
while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) {
|
while (prevIndex >= 0 && tabBar.model[prevIndex] && tabBar.model[prevIndex].isAction) {
|
||||||
prevIndex--
|
prevIndex--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (prevIndex >= 0) {
|
if (prevIndex >= 0) {
|
||||||
root.currentTabIndex = prevIndex
|
root.currentTabIndex = prevIndex;
|
||||||
}
|
}
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.currentTabIndex === 2 && wallpaperTab.handleKeyEvent) {
|
if (root.currentTabIndex === 2 && wallpaperTab.handleKeyEvent) {
|
||||||
if (wallpaperTab.handleKeyEvent(event)) {
|
if (wallpaperTab.handleKeyEvent(event)) {
|
||||||
event.accepted = true
|
event.accepted = true;
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -171,50 +254,54 @@ DankPopout {
|
|||||||
focus: false
|
focus: false
|
||||||
activeFocusOnTab: false
|
activeFocusOnTab: false
|
||||||
nextFocusTarget: {
|
nextFocusTarget: {
|
||||||
const item = pages.currentItem
|
const item = pages.currentItem;
|
||||||
if (!item)
|
if (!item)
|
||||||
return null
|
return null;
|
||||||
if (item.focusTarget)
|
if (item.focusTarget)
|
||||||
return item.focusTarget
|
return item.focusTarget;
|
||||||
return item
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
model: {
|
model: {
|
||||||
let tabs = [{
|
let tabs = [
|
||||||
"icon": "dashboard",
|
{
|
||||||
"text": I18n.tr("Overview")
|
"icon": "dashboard",
|
||||||
}, {
|
"text": I18n.tr("Overview")
|
||||||
"icon": "music_note",
|
},
|
||||||
"text": I18n.tr("Media")
|
{
|
||||||
}, {
|
"icon": "music_note",
|
||||||
"icon": "wallpaper",
|
"text": I18n.tr("Media")
|
||||||
"text": I18n.tr("Wallpapers")
|
},
|
||||||
}]
|
{
|
||||||
|
"icon": "wallpaper",
|
||||||
|
"text": I18n.tr("Wallpapers")
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
if (SettingsData.weatherEnabled) {
|
if (SettingsData.weatherEnabled) {
|
||||||
tabs.push({
|
tabs.push({
|
||||||
"icon": "wb_sunny",
|
"icon": "wb_sunny",
|
||||||
"text": I18n.tr("Weather")
|
"text": I18n.tr("Weather")
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
tabs.push({
|
tabs.push({
|
||||||
"icon": "settings",
|
"icon": "settings",
|
||||||
"text": I18n.tr("Settings"),
|
"text": I18n.tr("Settings"),
|
||||||
"isAction": true
|
"isAction": true
|
||||||
})
|
});
|
||||||
return tabs
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
onTabClicked: function (index) {
|
onTabClicked: function (index) {
|
||||||
root.currentTabIndex = index
|
root.currentTabIndex = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
onActionTriggered: function (index) {
|
onActionTriggered: function (index) {
|
||||||
let settingsIndex = SettingsData.weatherEnabled ? 4 : 3
|
let settingsIndex = SettingsData.weatherEnabled ? 4 : 3;
|
||||||
if (index === settingsIndex) {
|
if (index === settingsIndex) {
|
||||||
dashVisible = false
|
dashVisible = false;
|
||||||
settingsModal.show()
|
settingsModal.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -229,14 +316,14 @@ DankPopout {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
implicitHeight: {
|
implicitHeight: {
|
||||||
if (currentIndex === 0)
|
if (currentIndex === 0)
|
||||||
return overviewTab.implicitHeight
|
return overviewTab.implicitHeight;
|
||||||
if (currentIndex === 1)
|
if (currentIndex === 1)
|
||||||
return mediaTab.implicitHeight
|
return mediaTab.implicitHeight;
|
||||||
if (currentIndex === 2)
|
if (currentIndex === 2)
|
||||||
return wallpaperTab.implicitHeight
|
return wallpaperTab.implicitHeight;
|
||||||
if (SettingsData.weatherEnabled && currentIndex === 3)
|
if (SettingsData.weatherEnabled && currentIndex === 3)
|
||||||
return weatherTab.implicitHeight
|
return weatherTab.implicitHeight;
|
||||||
return overviewTab.implicitHeight
|
return overviewTab.implicitHeight;
|
||||||
}
|
}
|
||||||
currentIndex: root.currentTabIndex
|
currentIndex: root.currentTabIndex
|
||||||
|
|
||||||
@@ -244,24 +331,42 @@ DankPopout {
|
|||||||
id: overviewTab
|
id: overviewTab
|
||||||
|
|
||||||
onCloseDash: {
|
onCloseDash: {
|
||||||
root.dashVisible = false
|
root.dashVisible = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSwitchToWeatherTab: {
|
onSwitchToWeatherTab: {
|
||||||
if (SettingsData.weatherEnabled) {
|
if (SettingsData.weatherEnabled) {
|
||||||
tabBar.currentIndex = 3
|
tabBar.currentIndex = 3;
|
||||||
tabBar.tabClicked(3)
|
tabBar.tabClicked(3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onSwitchToMediaTab: {
|
onSwitchToMediaTab: {
|
||||||
tabBar.currentIndex = 1
|
tabBar.currentIndex = 1;
|
||||||
tabBar.tabClicked(1)
|
tabBar.tabClicked(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaPlayerTab {
|
MediaPlayerTab {
|
||||||
id: mediaTab
|
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 {
|
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
|
||||||
import QtQuick.Controls
|
|
||||||
import QtQuick.Effects
|
import QtQuick.Effects
|
||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import Quickshell.Services.Mpris
|
import Quickshell.Services.Mpris
|
||||||
import Quickshell.Services.Pipewire
|
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import qs.Common
|
import qs.Common
|
||||||
@@ -15,14 +13,42 @@ Item {
|
|||||||
|
|
||||||
property MprisPlayer activePlayer: MprisController.activePlayer
|
property MprisPlayer activePlayer: MprisController.activePlayer
|
||||||
property var allPlayers: MprisController.availablePlayers
|
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 {
|
DankTooltipV2 {
|
||||||
id: sharedTooltip
|
id: sharedTooltip
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property bool isRightEdge: (SettingsData.barConfigs[0]?.position ?? SettingsData.Position.Top) === SettingsData.Position.Right
|
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 __isChromeBrowser: {
|
||||||
readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported
|
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)
|
readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
|
||||||
|
|
||||||
// Palette that stays stable across track switches until new colors are ready
|
// Palette that stays stable across track switches until new colors are ready
|
||||||
@@ -334,383 +360,6 @@ Item {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
clip: false
|
clip: false
|
||||||
visible: !_noneAvailable && (!showNoPlayerNow)
|
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 {
|
ColumnLayout {
|
||||||
x: 72
|
x: 72
|
||||||
y: 20
|
y: 20
|
||||||
@@ -1051,14 +700,12 @@ Item {
|
|||||||
radius: 20
|
radius: 20
|
||||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||||
y: 185
|
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.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
z: 100
|
z: 100
|
||||||
visible: (allPlayers?.length || 0) >= 1
|
visible: (allPlayers?.length || 0) >= 1
|
||||||
|
|
||||||
property bool playersExpanded: false
|
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "assistant_device"
|
name: "assistant_device"
|
||||||
@@ -1072,14 +719,20 @@ Item {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
parent.playersExpanded = !parent.playersExpanded;
|
if (playersExpanded) {
|
||||||
}
|
hideDropdowns();
|
||||||
onEntered: {
|
return;
|
||||||
sharedTooltip.show("Media Players", playerSelectorButton, 0, 0, isRightEdge ? "right" : "left");
|
}
|
||||||
}
|
hideDropdowns();
|
||||||
onExited: {
|
playersExpanded = true;
|
||||||
sharedTooltip.hide();
|
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
|
radius: 20
|
||||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||||
y: 130
|
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.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, volumeAvailable ? 0.3 : 0.15)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
z: 101
|
z: 101
|
||||||
enabled: volumeAvailable
|
enabled: volumeAvailable
|
||||||
|
|
||||||
property bool volumeExpanded: false
|
|
||||||
property real previousVolume: 0.0
|
property real previousVolume: 0.0
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: volumeHideTimer
|
|
||||||
interval: 500
|
|
||||||
onTriggered: volumeButton.volumeExpanded = false
|
|
||||||
}
|
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: getVolumeIcon()
|
name: getVolumeIcon()
|
||||||
@@ -1118,11 +764,19 @@ Item {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onEntered: {
|
onEntered: {
|
||||||
volumeButton.volumeExpanded = true;
|
if (volumeExpanded)
|
||||||
volumeHideTimer.stop();
|
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: {
|
onExited: {
|
||||||
volumeHideTimer.restart();
|
if (volumeExpanded)
|
||||||
|
volumeButtonExited();
|
||||||
}
|
}
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (currentVolume > 0) {
|
if (currentVolume > 0) {
|
||||||
@@ -1142,22 +796,15 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
onWheel: wheelEvent => {
|
onWheel: wheelEvent => {
|
||||||
let delta = wheelEvent.angleDelta.y;
|
const delta = wheelEvent.angleDelta.y;
|
||||||
let current = (currentVolume * 100) || 0;
|
const current = (currentVolume * 100) || 0;
|
||||||
let newVolume;
|
const newVolume = delta > 0 ? Math.min(100, current + 5) : Math.max(0, current - 5);
|
||||||
|
|
||||||
if (delta > 0) {
|
|
||||||
newVolume = Math.min(100, current + 5);
|
|
||||||
} else {
|
|
||||||
newVolume = Math.max(0, current - 5);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usePlayerVolume) {
|
if (usePlayerVolume) {
|
||||||
activePlayer.volume = newVolume / 100;
|
activePlayer.volume = newVolume / 100;
|
||||||
} else if (AudioService.sink?.audio) {
|
} else if (AudioService.sink?.audio) {
|
||||||
AudioService.sink.audio.volume = newVolume / 100;
|
AudioService.sink.audio.volume = newVolume / 100;
|
||||||
}
|
}
|
||||||
volumeButton.volumeExpanded = true;
|
|
||||||
wheelEvent.accepted = true;
|
wheelEvent.accepted = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1170,16 +817,14 @@ Item {
|
|||||||
radius: 20
|
radius: 20
|
||||||
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
x: isRightEdge ? Theme.spacingM : parent.width - 40 - Theme.spacingM
|
||||||
y: 240
|
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.color: Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.3)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
z: 100
|
z: 100
|
||||||
|
|
||||||
property bool devicesExpanded: false
|
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: parent.devicesExpanded ? "expand_less" : "speaker"
|
name: devicesExpanded ? "expand_less" : "speaker"
|
||||||
size: 18
|
size: 18
|
||||||
color: Theme.surfaceText
|
color: Theme.surfaceText
|
||||||
}
|
}
|
||||||
@@ -1190,247 +835,20 @@ Item {
|
|||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
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: {
|
onEntered: sharedTooltip.show("Output Device", audioDevicesButton, 0, 0, isRightEdge ? "right" : "left")
|
||||||
sharedTooltip.show("Output Device", audioDevicesButton, 0, 0, isRightEdge ? "right" : "left");
|
onExited: sharedTooltip.hide()
|
||||||
}
|
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ DankOSD {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
readonly property bool useVertical: isVerticalLayout
|
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)
|
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)
|
osdHeight: useVertical ? Math.min(260, Screen.height - Theme.spacingM * 2) : (40 + Theme.spacingS * 2)
|
||||||
@@ -17,7 +21,7 @@ DankOSD {
|
|||||||
target: DisplayService
|
target: DisplayService
|
||||||
function onBrightnessChanged(showOsd) {
|
function onBrightnessChanged(showOsd) {
|
||||||
if (showOsd && SettingsData.osdBrightnessEnabled) {
|
if (showOsd && SettingsData.osdBrightnessEnabled) {
|
||||||
root.show()
|
root.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,13 +52,13 @@ DankOSD {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: {
|
name: {
|
||||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||||
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
|
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
|
||||||
return "brightness_medium"
|
return "brightness_medium";
|
||||||
} else if (deviceInfo.name.includes("kbd")) {
|
} else if (deviceInfo.name.includes("kbd")) {
|
||||||
return "keyboard"
|
return "keyboard";
|
||||||
} else {
|
} else {
|
||||||
return "lightbulb"
|
return "lightbulb";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
@@ -70,74 +74,50 @@ DankOSD {
|
|||||||
x: parent.gap * 2 + Theme.iconSize
|
x: parent.gap * 2 + Theme.iconSize
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
minimum: {
|
minimum: {
|
||||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||||
if (!deviceInfo) return 1
|
if (!deviceInfo)
|
||||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
|
return 1;
|
||||||
|
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||||
if (isExponential) {
|
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: {
|
maximum: {
|
||||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||||
if (!deviceInfo) return 100
|
if (!deviceInfo)
|
||||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
|
return 100;
|
||||||
|
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||||
if (isExponential) {
|
if (isExponential) {
|
||||||
return 100
|
return 100;
|
||||||
}
|
}
|
||||||
return deviceInfo.displayMax || 100
|
return deviceInfo.displayMax || 100;
|
||||||
}
|
}
|
||||||
enabled: DisplayService.brightnessAvailable
|
enabled: DisplayService.brightnessAvailable
|
||||||
showValue: true
|
showValue: true
|
||||||
unit: {
|
unit: {
|
||||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||||
if (!deviceInfo) return "%"
|
if (!deviceInfo)
|
||||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
|
return "%";
|
||||||
|
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||||
if (isExponential) {
|
if (isExponential) {
|
||||||
return "%"
|
return "%";
|
||||||
}
|
}
|
||||||
return deviceInfo.class === "ddc" ? "" : "%"
|
return deviceInfo.class === "ddc" ? "" : "%";
|
||||||
}
|
}
|
||||||
thumbOutlineColor: Theme.surfaceContainer
|
thumbOutlineColor: Theme.surfaceContainer
|
||||||
alwaysShowValue: SettingsData.osdAlwaysShowValue
|
alwaysShowValue: SettingsData.osdAlwaysShowValue
|
||||||
|
value: !isDragging ? root.targetBrightness : value
|
||||||
Component.onCompleted: {
|
|
||||||
if (DisplayService.brightnessAvailable) {
|
|
||||||
value = DisplayService.brightnessLevel
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onSliderValueChanged: newValue => {
|
onSliderValueChanged: newValue => {
|
||||||
if (DisplayService.brightnessAvailable) {
|
if (DisplayService.brightnessAvailable) {
|
||||||
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true)
|
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true);
|
||||||
resetHideTimer()
|
resetHideTimer();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onContainsMouseChanged: {
|
|
||||||
setChildHovered(containsMouse)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSliderDragFinished: finalValue => {
|
onContainsMouseChanged: {
|
||||||
if (DisplayService.brightnessAvailable) {
|
setChildHovered(containsMouse);
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,13 +141,13 @@ DankOSD {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: {
|
name: {
|
||||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||||
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
|
if (!deviceInfo || deviceInfo.class === "backlight" || deviceInfo.class === "ddc") {
|
||||||
return "brightness_medium"
|
return "brightness_medium";
|
||||||
} else if (deviceInfo.name.includes("kbd")) {
|
} else if (deviceInfo.name.includes("kbd")) {
|
||||||
return "keyboard"
|
return "keyboard";
|
||||||
} else {
|
} else {
|
||||||
return "lightbulb"
|
return "lightbulb";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size: Theme.iconSize
|
size: Theme.iconSize
|
||||||
@@ -183,22 +163,26 @@ DankOSD {
|
|||||||
y: gap * 2 + Theme.iconSize
|
y: gap * 2 + Theme.iconSize
|
||||||
|
|
||||||
property bool dragging: false
|
property bool dragging: false
|
||||||
property int value: DisplayService.brightnessAvailable ? DisplayService.brightnessLevel : 0
|
property int value: !dragging ? root.targetBrightness : value
|
||||||
|
|
||||||
readonly property int minimum: {
|
readonly property int minimum: {
|
||||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||||
if (!deviceInfo) return 1
|
if (!deviceInfo)
|
||||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
|
return 1;
|
||||||
if (isExponential) return 1
|
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||||
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0
|
if (isExponential)
|
||||||
|
return 1;
|
||||||
|
return (deviceInfo.class === "backlight" || deviceInfo.class === "ddc") ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property int maximum: {
|
readonly property int maximum: {
|
||||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||||
if (!deviceInfo) return 100
|
if (!deviceInfo)
|
||||||
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id)
|
return 100;
|
||||||
if (isExponential) return 100
|
const isExponential = SessionData.getBrightnessExponential(deviceInfo.id);
|
||||||
return deviceInfo.displayMax || 100
|
if (isExponential)
|
||||||
|
return 100;
|
||||||
|
return deviceInfo.displayMax || 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -214,8 +198,8 @@ DankOSD {
|
|||||||
id: vertFill
|
id: vertFill
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: {
|
height: {
|
||||||
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum)
|
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum);
|
||||||
return ratio * parent.height
|
return ratio * parent.height;
|
||||||
}
|
}
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
@@ -229,9 +213,9 @@ DankOSD {
|
|||||||
height: 8
|
height: 8
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
y: {
|
y: {
|
||||||
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum)
|
const ratio = (vertSlider.value - vertSlider.minimum) / (vertSlider.maximum - vertSlider.minimum);
|
||||||
const travel = parent.height - height
|
const travel = parent.height - height;
|
||||||
return Math.max(0, Math.min(travel, travel * (1 - ratio)))
|
return Math.max(0, Math.min(travel, travel * (1 - ratio)));
|
||||||
}
|
}
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
@@ -248,50 +232,34 @@ DankOSD {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
onContainsMouseChanged: {
|
onContainsMouseChanged: {
|
||||||
setChildHovered(containsMouse)
|
setChildHovered(containsMouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
onPressed: mouse => {
|
onPressed: mouse => {
|
||||||
vertSlider.dragging = true
|
vertSlider.dragging = true;
|
||||||
updateBrightness(mouse)
|
updateBrightness(mouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
onReleased: {
|
onReleased: {
|
||||||
vertSlider.dragging = false
|
vertSlider.dragging = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onPositionChanged: mouse => {
|
onPositionChanged: mouse => {
|
||||||
if (pressed) {
|
if (pressed) {
|
||||||
updateBrightness(mouse)
|
updateBrightness(mouse);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: mouse => {
|
onClicked: mouse => {
|
||||||
updateBrightness(mouse)
|
updateBrightness(mouse);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateBrightness(mouse) {
|
function updateBrightness(mouse) {
|
||||||
if (DisplayService.brightnessAvailable) {
|
if (DisplayService.brightnessAvailable) {
|
||||||
const ratio = 1.0 - (mouse.y / height)
|
const ratio = 1.0 - (mouse.y / height);
|
||||||
const newValue = Math.round(vertSlider.minimum + ratio * (vertSlider.maximum - vertSlider.minimum))
|
const newValue = Math.round(vertSlider.minimum + ratio * (vertSlider.maximum - vertSlider.minimum));
|
||||||
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true)
|
DisplayService.setBrightness(newValue, DisplayService.lastIpcDevice, true);
|
||||||
resetHideTimer()
|
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -302,10 +270,10 @@ DankOSD {
|
|||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.bottomMargin: gap
|
anchors.bottomMargin: gap
|
||||||
text: {
|
text: {
|
||||||
const deviceInfo = DisplayService.getCurrentDeviceInfo()
|
const deviceInfo = DisplayService.getCurrentDeviceInfo();
|
||||||
const isExponential = deviceInfo ? SessionData.getBrightnessExponential(deviceInfo.id) : false
|
const isExponential = deviceInfo ? SessionData.getBrightnessExponential(deviceInfo.id) : false;
|
||||||
const unit = (deviceInfo && deviceInfo.class === "ddc" && !isExponential) ? "" : "%"
|
const unit = (deviceInfo && deviceInfo.class === "ddc" && !isExponential) ? "" : "%";
|
||||||
return vertSlider.value + unit
|
return vertSlider.value + unit;
|
||||||
}
|
}
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceText
|
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 {
|
PluginPopout {
|
||||||
id: pluginPopout
|
id: pluginPopout
|
||||||
contentWidth: root.popoutWidth
|
contentWidth: root.popoutWidth
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ Column {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: {
|
onPressed: {
|
||||||
if (root.closePopout) {
|
if (root.closePopout) {
|
||||||
root.closePopout()
|
root.closePopout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -784,13 +784,31 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleControlCenterSettingChanged(sectionId, widgetIndex, settingName, value) {
|
function handleControlCenterSettingChanged(sectionId, widgetIndex, settingName, value) {
|
||||||
// Control Center settings are global, not per-widget instance
|
switch (settingName) {
|
||||||
if (settingName === "showNetworkIcon") {
|
case "showNetworkIcon":
|
||||||
SettingsData.set("controlCenterShowNetworkIcon", value);
|
SettingsData.set("controlCenterShowNetworkIcon", value)
|
||||||
} else if (settingName === "showBluetoothIcon") {
|
break
|
||||||
SettingsData.set("controlCenterShowBluetoothIcon", value);
|
case "showBluetoothIcon":
|
||||||
} else if (settingName === "showAudioIcon") {
|
SettingsData.set("controlCenterShowBluetoothIcon", value)
|
||||||
SettingsData.set("controlCenterShowAudioIcon", 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 {
|
DankActionButton {
|
||||||
|
id: ccMenuButton
|
||||||
visible: modelData.id === "controlCenterButton"
|
visible: modelData.id === "controlCenterButton"
|
||||||
buttonSize: 32
|
buttonSize: 32
|
||||||
iconName: "more_vert"
|
iconName: "more_vert"
|
||||||
iconSize: 18
|
iconSize: 18
|
||||||
iconColor: Theme.outline
|
iconColor: Theme.outline
|
||||||
onClicked: {
|
onClicked: {
|
||||||
console.log("Control Center three-dot button clicked for widget:", modelData.id);
|
|
||||||
controlCenterContextMenu.widgetData = modelData;
|
controlCenterContextMenu.widgetData = modelData;
|
||||||
controlCenterContextMenu.sectionId = root.sectionId;
|
controlCenterContextMenu.sectionId = root.sectionId;
|
||||||
controlCenterContextMenu.widgetIndex = index;
|
controlCenterContextMenu.widgetIndex = index;
|
||||||
// Position relative to the action buttons row, not the specific button
|
|
||||||
var parentPos = parent.mapToItem(root, 0, 0);
|
var buttonPos = ccMenuButton.mapToItem(root, 0, 0);
|
||||||
controlCenterContextMenu.x = parentPos.x - 210; // Position to the left with margin
|
var popupWidth = controlCenterContextMenu.width;
|
||||||
controlCenterContextMenu.y = parentPos.y - 10; // Slightly above
|
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();
|
controlCenterContextMenu.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
|
id: privacyMenuButton
|
||||||
visible: modelData.id === "privacyIndicator"
|
visible: modelData.id === "privacyIndicator"
|
||||||
buttonSize: 32
|
buttonSize: 32
|
||||||
iconName: "more_vert"
|
iconName: "more_vert"
|
||||||
iconSize: 18
|
iconSize: 18
|
||||||
iconColor: Theme.outline
|
iconColor: Theme.outline
|
||||||
onClicked: {
|
onClicked: {
|
||||||
console.log("Privacy three-dot button clicked for widget:", modelData.id);
|
|
||||||
privacyContextMenu.widgetData = modelData;
|
privacyContextMenu.widgetData = modelData;
|
||||||
privacyContextMenu.sectionId = root.sectionId;
|
privacyContextMenu.sectionId = root.sectionId;
|
||||||
privacyContextMenu.widgetIndex = index;
|
privacyContextMenu.widgetIndex = index;
|
||||||
// Position relative to the action buttons row, not the specific button
|
|
||||||
var parentPos = parent.mapToItem(root, 0, 0);
|
var buttonPos = privacyMenuButton.mapToItem(root, 0, 0);
|
||||||
privacyContextMenu.x = parentPos.x - 210; // Position to the left with margin
|
var popupWidth = privacyContextMenu.width;
|
||||||
privacyContextMenu.y = parentPos.y - 10; // Slightly above
|
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();
|
privacyContextMenu.open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -699,21 +729,13 @@ Column {
|
|||||||
property string sectionId: ""
|
property string sectionId: ""
|
||||||
property int widgetIndex: -1
|
property int widgetIndex: -1
|
||||||
|
|
||||||
width: 200
|
width: 220
|
||||||
height: 120
|
height: menuColumn.implicitHeight + Theme.spacingS * 2
|
||||||
padding: 0
|
padding: 0
|
||||||
modal: true
|
modal: true
|
||||||
focus: true
|
focus: true
|
||||||
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
|
||||||
|
|
||||||
onOpened: {
|
|
||||||
console.log("Control Center context menu opened");
|
|
||||||
}
|
|
||||||
|
|
||||||
onClosed: {
|
|
||||||
console.log("Control Center context menu closed");
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
color: Theme.withAlpha(Theme.surfaceContainer, Theme.popupTransparency)
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
@@ -722,168 +744,117 @@ Column {
|
|||||||
}
|
}
|
||||||
|
|
||||||
contentItem: Item {
|
contentItem: Item {
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
id: menuColumn
|
id: menuColumn
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Theme.spacingS
|
anchors.margins: Theme.spacingS
|
||||||
spacing: 2
|
spacing: 2
|
||||||
|
|
||||||
Rectangle {
|
Repeater {
|
||||||
width: parent.width
|
model: [
|
||||||
height: 32
|
{
|
||||||
radius: Theme.cornerRadius
|
icon: "lan",
|
||||||
color: networkToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
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 {
|
delegate: Rectangle {
|
||||||
anchors.left: parent.left
|
required property var modelData
|
||||||
anchors.leftMargin: Theme.spacingS
|
required property int index
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
spacing: Theme.spacingS
|
|
||||||
|
|
||||||
DankIcon {
|
width: menuColumn.width
|
||||||
name: "lan"
|
height: 32
|
||||||
size: 16
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceText
|
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
|
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 {
|
DankToggle {
|
||||||
text: I18n.tr("Network Icon")
|
id: toggle
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
anchors.right: parent.right
|
||||||
color: Theme.surfaceText
|
anchors.rightMargin: Theme.spacingS
|
||||||
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
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 40
|
||||||
|
height: 20
|
||||||
|
checked: modelData.checked
|
||||||
|
onToggled: {
|
||||||
|
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
MouseArea {
|
||||||
text: I18n.tr("Bluetooth Icon")
|
id: toggleArea
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
anchors.fill: parent
|
||||||
color: Theme.surfaceText
|
hoverEnabled: true
|
||||||
font.weight: Font.Normal
|
cursorShape: Qt.PointingHandCursor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
onPressed: {
|
||||||
}
|
toggle.checked = !toggle.checked;
|
||||||
}
|
root.controlCenterSettingChanged(controlCenterContextMenu.sectionId, controlCenterContextMenu.widgetIndex, modelData.setting, toggle.checked);
|
||||||
|
}
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -932,7 +903,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 32
|
height: 32
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: networkToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
|
color: "transparent"
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -954,7 +925,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 32
|
height: 32
|
||||||
radius: Theme.cornerRadius
|
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 {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -1006,7 +977,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 32
|
height: 32
|
||||||
radius: Theme.cornerRadius
|
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 {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
@@ -1058,7 +1029,7 @@ Column {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: 32
|
height: 32
|
||||||
radius: Theme.cornerRadius
|
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 {
|
Row {
|
||||||
anchors.left: parent.left
|
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 Singleton
|
||||||
|
|
||||||
pragma ComponentBehavior: Bound
|
pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
|
||||||
import qs.Common
|
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -29,6 +26,9 @@ Singleton {
|
|||||||
property string wifiConnectionUuid: activeService?.wifiConnectionUuid ?? ""
|
property string wifiConnectionUuid: activeService?.wifiConnectionUuid ?? ""
|
||||||
property string wifiDevicePath: activeService?.wifiDevicePath ?? ""
|
property string wifiDevicePath: activeService?.wifiDevicePath ?? ""
|
||||||
property string activeAccessPointPath: activeService?.activeAccessPointPath ?? ""
|
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 string currentWifiSSID: activeService?.currentWifiSSID ?? ""
|
||||||
property int wifiSignalStrength: activeService?.wifiSignalStrength ?? 0
|
property int wifiSignalStrength: activeService?.wifiSignalStrength ?? 0
|
||||||
@@ -70,6 +70,14 @@ Singleton {
|
|||||||
|
|
||||||
property bool subscriptionConnected: activeService?.subscriptionConnected ?? false
|
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 credentialsToken: activeService?.credentialsToken ?? ""
|
||||||
property string credentialsSSID: activeService?.credentialsSSID ?? ""
|
property string credentialsSSID: activeService?.credentialsSSID ?? ""
|
||||||
property string credentialsSetting: activeService?.credentialsSetting ?? ""
|
property string credentialsSetting: activeService?.credentialsSetting ?? ""
|
||||||
@@ -88,12 +96,12 @@ Singleton {
|
|||||||
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
|
readonly property string socketPath: Quickshell.env("DMS_SOCKET")
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
console.info("NetworkService: Initializing...")
|
console.info("NetworkService: Initializing...");
|
||||||
if (!socketPath || socketPath.length === 0) {
|
if (!socketPath || socketPath.length === 0) {
|
||||||
console.info("NetworkService: DMS_SOCKET not set, using LegacyNetworkService")
|
console.info("NetworkService: DMS_SOCKET not set, using LegacyNetworkService");
|
||||||
useLegacyService()
|
useLegacyService();
|
||||||
} else {
|
} 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() {
|
function onNetworkAvailableChanged() {
|
||||||
if (!activeService && DMSNetworkService.networkAvailable) {
|
if (!activeService && DMSNetworkService.networkAvailable) {
|
||||||
console.info("NetworkService: Network capability detected, using DMSNetworkService")
|
console.info("NetworkService: Network capability detected, using DMSNetworkService");
|
||||||
activeService = DMSNetworkService
|
activeService = DMSNetworkService;
|
||||||
usingLegacy = false
|
usingLegacy = false;
|
||||||
console.info("NetworkService: Switched to DMSNetworkService, networkAvailable:", networkAvailable)
|
console.info("NetworkService: Switched to DMSNetworkService, networkAvailable:", networkAvailable);
|
||||||
connectSignals()
|
connectSignals();
|
||||||
} else if (!activeService && !DMSNetworkService.networkAvailable && socketPath && socketPath.length > 0) {
|
} else if (!activeService && !DMSNetworkService.networkAvailable && socketPath && socketPath.length > 0) {
|
||||||
console.info("NetworkService: Network capability not available in DMS, using LegacyNetworkService")
|
console.info("NetworkService: Network capability not available in DMS, using LegacyNetworkService");
|
||||||
useLegacyService()
|
useLegacyService();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function useLegacyService() {
|
function useLegacyService() {
|
||||||
activeService = LegacyNetworkService
|
activeService = LegacyNetworkService;
|
||||||
usingLegacy = true
|
usingLegacy = true;
|
||||||
console.info("NetworkService: Switched to LegacyNetworkService, networkAvailable:", networkAvailable)
|
console.info("NetworkService: Switched to LegacyNetworkService, networkAvailable:", networkAvailable);
|
||||||
if (LegacyNetworkService.activate) {
|
if (LegacyNetworkService.activate) {
|
||||||
LegacyNetworkService.activate()
|
LegacyNetworkService.activate();
|
||||||
}
|
}
|
||||||
connectSignals()
|
connectSignals();
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectSignals() {
|
function connectSignals() {
|
||||||
if (activeService) {
|
if (activeService) {
|
||||||
if (activeService.networksUpdated) {
|
if (activeService.networksUpdated) {
|
||||||
activeService.networksUpdated.connect(root.networksUpdated)
|
activeService.networksUpdated.connect(root.networksUpdated);
|
||||||
}
|
}
|
||||||
if (activeService.connectionChanged) {
|
if (activeService.connectionChanged) {
|
||||||
activeService.connectionChanged.connect(root.connectionChanged)
|
activeService.connectionChanged.connect(root.connectionChanged);
|
||||||
}
|
}
|
||||||
if (activeService.credentialsNeeded) {
|
if (activeService.credentialsNeeded) {
|
||||||
activeService.credentialsNeeded.connect(root.credentialsNeeded)
|
activeService.credentialsNeeded.connect(root.credentialsNeeded);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addRef() {
|
function addRef() {
|
||||||
if (activeService && activeService.addRef) {
|
if (activeService && activeService.addRef) {
|
||||||
activeService.addRef()
|
activeService.addRef();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeRef() {
|
function removeRef() {
|
||||||
if (activeService && activeService.removeRef) {
|
if (activeService && activeService.removeRef) {
|
||||||
activeService.removeRef()
|
activeService.removeRef();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getState() {
|
function getState() {
|
||||||
if (activeService && activeService.getState) {
|
if (activeService && activeService.getState) {
|
||||||
activeService.getState()
|
activeService.getState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function scanWifi() {
|
function scanWifi() {
|
||||||
if (activeService && activeService.scanWifi) {
|
if (activeService && activeService.scanWifi) {
|
||||||
activeService.scanWifi()
|
activeService.scanWifi();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function scanWifiNetworks() {
|
function scanWifiNetworks() {
|
||||||
if (activeService && activeService.scanWifiNetworks) {
|
if (activeService && activeService.scanWifiNetworks) {
|
||||||
activeService.scanWifiNetworks()
|
activeService.scanWifiNetworks();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectToWifi(ssid, password = "", username = "", anonymousIdentity = "", domainSuffixMatch = "") {
|
function connectToWifi(ssid, password = "", username = "", anonymousIdentity = "", domainSuffixMatch = "") {
|
||||||
if (activeService && activeService.connectToWifi) {
|
if (activeService && activeService.connectToWifi) {
|
||||||
activeService.connectToWifi(ssid, password, username, anonymousIdentity, domainSuffixMatch)
|
activeService.connectToWifi(ssid, password, username, anonymousIdentity, domainSuffixMatch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function disconnectWifi() {
|
function disconnectWifi() {
|
||||||
if (activeService && activeService.disconnectWifi) {
|
if (activeService && activeService.disconnectWifi) {
|
||||||
activeService.disconnectWifi()
|
activeService.disconnectWifi();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function forgetWifiNetwork(ssid) {
|
function forgetWifiNetwork(ssid) {
|
||||||
if (activeService && activeService.forgetWifiNetwork) {
|
if (activeService && activeService.forgetWifiNetwork) {
|
||||||
activeService.forgetWifiNetwork(ssid)
|
activeService.forgetWifiNetwork(ssid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleWifiRadio() {
|
function toggleWifiRadio() {
|
||||||
if (activeService && activeService.toggleWifiRadio) {
|
if (activeService && activeService.toggleWifiRadio) {
|
||||||
activeService.toggleWifiRadio()
|
activeService.toggleWifiRadio();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function enableWifiDevice() {
|
function enableWifiDevice() {
|
||||||
if (activeService && activeService.enableWifiDevice) {
|
if (activeService && activeService.enableWifiDevice) {
|
||||||
activeService.enableWifiDevice()
|
activeService.enableWifiDevice();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNetworkPreference(preference) {
|
function setNetworkPreference(preference) {
|
||||||
if (activeService && activeService.setNetworkPreference) {
|
if (activeService && activeService.setNetworkPreference) {
|
||||||
activeService.setNetworkPreference(preference)
|
activeService.setNetworkPreference(preference);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setConnectionPriority(type) {
|
function setConnectionPriority(type) {
|
||||||
if (activeService && activeService.setConnectionPriority) {
|
if (activeService && activeService.setConnectionPriority) {
|
||||||
activeService.setConnectionPriority(type)
|
activeService.setConnectionPriority(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectToWifiAndSetPreference(ssid, password, username = "", anonymousIdentity = "", domainSuffixMatch = "") {
|
function connectToWifiAndSetPreference(ssid, password, username = "", anonymousIdentity = "", domainSuffixMatch = "") {
|
||||||
if (activeService && activeService.connectToWifiAndSetPreference) {
|
if (activeService && activeService.connectToWifiAndSetPreference) {
|
||||||
activeService.connectToWifiAndSetPreference(ssid, password, username, anonymousIdentity, domainSuffixMatch)
|
activeService.connectToWifiAndSetPreference(ssid, password, username, anonymousIdentity, domainSuffixMatch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleNetworkConnection(type) {
|
function toggleNetworkConnection(type) {
|
||||||
if (activeService && activeService.toggleNetworkConnection) {
|
if (activeService && activeService.toggleNetworkConnection) {
|
||||||
activeService.toggleNetworkConnection(type)
|
activeService.toggleNetworkConnection(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startAutoScan() {
|
function startAutoScan() {
|
||||||
if (activeService && activeService.startAutoScan) {
|
if (activeService && activeService.startAutoScan) {
|
||||||
activeService.startAutoScan()
|
activeService.startAutoScan();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopAutoScan() {
|
function stopAutoScan() {
|
||||||
if (activeService && activeService.stopAutoScan) {
|
if (activeService && activeService.stopAutoScan) {
|
||||||
activeService.stopAutoScan()
|
activeService.stopAutoScan();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchNetworkInfo(ssid) {
|
function fetchNetworkInfo(ssid) {
|
||||||
if (activeService && activeService.fetchNetworkInfo) {
|
if (activeService && activeService.fetchNetworkInfo) {
|
||||||
activeService.fetchNetworkInfo(ssid)
|
activeService.fetchNetworkInfo(ssid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function fetchWiredNetworkInfo(uuid) {
|
function fetchWiredNetworkInfo(uuid) {
|
||||||
if (activeService && activeService.fetchWiredNetworkInfo) {
|
if (activeService && activeService.fetchWiredNetworkInfo) {
|
||||||
activeService.fetchWiredNetworkInfo(uuid)
|
activeService.fetchWiredNetworkInfo(uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNetworkInfo(ssid) {
|
function getNetworkInfo(ssid) {
|
||||||
if (activeService && activeService.getNetworkInfo) {
|
if (activeService && activeService.getNetworkInfo) {
|
||||||
return activeService.getNetworkInfo(ssid)
|
return activeService.getNetworkInfo(ssid);
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getWiredNetworkInfo(uuid) {
|
function getWiredNetworkInfo(uuid) {
|
||||||
if (activeService && activeService.getWiredNetworkInfo) {
|
if (activeService && activeService.getWiredNetworkInfo) {
|
||||||
return activeService.getWiredNetworkInfo(uuid)
|
return activeService.getWiredNetworkInfo(uuid);
|
||||||
}
|
}
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshNetworkState() {
|
function refreshNetworkState() {
|
||||||
if (activeService && activeService.refreshNetworkState) {
|
if (activeService && activeService.refreshNetworkState) {
|
||||||
activeService.refreshNetworkState()
|
activeService.refreshNetworkState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function connectToSpecificWiredConfig(uuid) {
|
function connectToSpecificWiredConfig(uuid) {
|
||||||
if (activeService && activeService.connectToSpecificWiredConfig) {
|
if (activeService && activeService.connectToSpecificWiredConfig) {
|
||||||
activeService.connectToSpecificWiredConfig(uuid)
|
activeService.connectToSpecificWiredConfig(uuid);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function submitCredentials(token, secrets, save) {
|
function submitCredentials(token, secrets, save) {
|
||||||
if (activeService && activeService.submitCredentials) {
|
if (activeService && activeService.submitCredentials) {
|
||||||
activeService.submitCredentials(token, secrets, save)
|
activeService.submitCredentials(token, secrets, save);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancelCredentials(token) {
|
function cancelCredentials(token) {
|
||||||
if (activeService && activeService.cancelCredentials) {
|
if (activeService && activeService.cancelCredentials) {
|
||||||
activeService.cancelCredentials(token)
|
activeService.cancelCredentials(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setWifiAutoconnect(ssid, autoconnect) {
|
function setWifiAutoconnect(ssid, autoconnect) {
|
||||||
if (activeService && activeService.setWifiAutoconnect) {
|
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 QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import qs.Common
|
import Quickshell.Wayland
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool cyclingActive: false
|
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 string cachedCyclingTime: SessionData.wallpaperCyclingTime
|
||||||
property int cachedCyclingInterval: SessionData.wallpaperCyclingInterval
|
property int cachedCyclingInterval: SessionData.wallpaperCyclingInterval
|
||||||
property string lastTimeCheck: ""
|
property string lastTimeCheck: ""
|
||||||
@@ -24,8 +33,8 @@ Singleton {
|
|||||||
running: false
|
running: false
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (typeof WallpaperCyclingService !== "undefined" && targetScreen !== "") {
|
if (typeof WallpaperCyclingService !== "undefined" && targetScreen !== "" && !WallpaperCyclingService.anyFullscreen) {
|
||||||
WallpaperCyclingService.cycleNextForMonitor(targetScreen)
|
WallpaperCyclingService.cycleNextForMonitor(targetScreen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -41,24 +50,26 @@ Singleton {
|
|||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (text && text.trim()) {
|
if (text && text.trim()) {
|
||||||
const files = text.trim().split('\n').filter(file => file.length > 0)
|
const files = text.trim().split('\n').filter(file => file.length > 0);
|
||||||
if (files.length <= 1) return
|
if (files.length <= 1)
|
||||||
const wallpaperList = files.sort()
|
return;
|
||||||
const currentPath = currentWallpaper
|
const wallpaperList = files.sort();
|
||||||
let currentIndex = wallpaperList.findIndex(path => path === currentPath)
|
const currentPath = currentWallpaper;
|
||||||
if (currentIndex === -1) currentIndex = 0
|
let currentIndex = wallpaperList.findIndex(path => path === currentPath);
|
||||||
let targetIndex
|
if (currentIndex === -1)
|
||||||
|
currentIndex = 0;
|
||||||
|
let targetIndex;
|
||||||
if (goToPrevious) {
|
if (goToPrevious) {
|
||||||
targetIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1
|
targetIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1;
|
||||||
} else {
|
} else {
|
||||||
targetIndex = (currentIndex + 1) % wallpaperList.length
|
targetIndex = (currentIndex + 1) % wallpaperList.length;
|
||||||
}
|
}
|
||||||
const targetWallpaper = wallpaperList[targetIndex]
|
const targetWallpaper = wallpaperList[targetIndex];
|
||||||
if (targetWallpaper && targetWallpaper !== currentPath) {
|
if (targetWallpaper && targetWallpaper !== currentPath) {
|
||||||
if (targetScreenName) {
|
if (targetScreenName) {
|
||||||
SessionData.setMonitorWallpaper(targetScreenName, targetWallpaper)
|
SessionData.setMonitorWallpaper(targetScreenName, targetWallpaper);
|
||||||
} else {
|
} else {
|
||||||
SessionData.setWallpaper(targetWallpaper)
|
SessionData.setWallpaper(targetWallpaper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,205 +82,205 @@ Singleton {
|
|||||||
target: SessionData
|
target: SessionData
|
||||||
|
|
||||||
function onWallpaperCyclingEnabledChanged() {
|
function onWallpaperCyclingEnabledChanged() {
|
||||||
updateCyclingState()
|
updateCyclingState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWallpaperCyclingModeChanged() {
|
function onWallpaperCyclingModeChanged() {
|
||||||
updateCyclingState()
|
updateCyclingState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWallpaperCyclingIntervalChanged() {
|
function onWallpaperCyclingIntervalChanged() {
|
||||||
cachedCyclingInterval = SessionData.wallpaperCyclingInterval
|
cachedCyclingInterval = SessionData.wallpaperCyclingInterval;
|
||||||
if (SessionData.wallpaperCyclingMode === "interval") {
|
if (SessionData.wallpaperCyclingMode === "interval") {
|
||||||
updateCyclingState()
|
updateCyclingState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onWallpaperCyclingTimeChanged() {
|
function onWallpaperCyclingTimeChanged() {
|
||||||
cachedCyclingTime = SessionData.wallpaperCyclingTime
|
cachedCyclingTime = SessionData.wallpaperCyclingTime;
|
||||||
if (SessionData.wallpaperCyclingMode === "time") {
|
if (SessionData.wallpaperCyclingMode === "time") {
|
||||||
updateCyclingState()
|
updateCyclingState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function onPerMonitorWallpaperChanged() {
|
function onPerMonitorWallpaperChanged() {
|
||||||
updateCyclingState()
|
updateCyclingState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMonitorCyclingSettingsChanged() {
|
function onMonitorCyclingSettingsChanged() {
|
||||||
updateCyclingState()
|
updateCyclingState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCyclingState() {
|
function updateCyclingState() {
|
||||||
if (SessionData.perMonitorWallpaper) {
|
if (SessionData.perMonitorWallpaper) {
|
||||||
stopCycling()
|
stopCycling();
|
||||||
updatePerMonitorCycling()
|
updatePerMonitorCycling();
|
||||||
} else if (SessionData.wallpaperCyclingEnabled && SessionData.wallpaperPath) {
|
} else if (SessionData.wallpaperCyclingEnabled && SessionData.wallpaperPath) {
|
||||||
startCycling()
|
startCycling();
|
||||||
stopAllMonitorCycling()
|
stopAllMonitorCycling();
|
||||||
} else {
|
} else {
|
||||||
stopCycling()
|
stopCycling();
|
||||||
stopAllMonitorCycling()
|
stopAllMonitorCycling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updatePerMonitorCycling() {
|
function updatePerMonitorCycling() {
|
||||||
if (typeof Quickshell === "undefined") return
|
if (typeof Quickshell === "undefined")
|
||||||
|
return;
|
||||||
var screens = Quickshell.screens
|
var screens = Quickshell.screens;
|
||||||
for (var i = 0; i < screens.length; i++) {
|
for (var i = 0; i < screens.length; i++) {
|
||||||
var screenName = screens[i].name
|
var screenName = screens[i].name;
|
||||||
var settings = SessionData.getMonitorCyclingSettings(screenName)
|
var settings = SessionData.getMonitorCyclingSettings(screenName);
|
||||||
var wallpaper = SessionData.getMonitorWallpaper(screenName)
|
var wallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||||
|
|
||||||
if (settings.enabled && wallpaper && !wallpaper.startsWith("#")) {
|
if (settings.enabled && wallpaper && !wallpaper.startsWith("#")) {
|
||||||
startMonitorCycling(screenName, settings)
|
startMonitorCycling(screenName, settings);
|
||||||
} else {
|
} else {
|
||||||
stopMonitorCycling(screenName)
|
stopMonitorCycling(screenName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopAllMonitorCycling() {
|
function stopAllMonitorCycling() {
|
||||||
var screenNames = Object.keys(monitorTimers)
|
var screenNames = Object.keys(monitorTimers);
|
||||||
for (var i = 0; i < screenNames.length; i++) {
|
for (var i = 0; i < screenNames.length; i++) {
|
||||||
stopMonitorCycling(screenNames[i])
|
stopMonitorCycling(screenNames[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function startCycling() {
|
function startCycling() {
|
||||||
if (SessionData.wallpaperCyclingMode === "interval") {
|
if (SessionData.wallpaperCyclingMode === "interval") {
|
||||||
intervalTimer.interval = cachedCyclingInterval * 1000
|
intervalTimer.interval = cachedCyclingInterval * 1000;
|
||||||
intervalTimer.start()
|
intervalTimer.start();
|
||||||
cyclingActive = true
|
cyclingActive = true;
|
||||||
} else if (SessionData.wallpaperCyclingMode === "time") {
|
} else if (SessionData.wallpaperCyclingMode === "time") {
|
||||||
cyclingActive = true
|
cyclingActive = true;
|
||||||
checkTimeBasedCycling()
|
checkTimeBasedCycling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopCycling() {
|
function stopCycling() {
|
||||||
intervalTimer.stop()
|
intervalTimer.stop();
|
||||||
cyclingActive = false
|
cyclingActive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function startMonitorCycling(screenName, settings) {
|
function startMonitorCycling(screenName, settings) {
|
||||||
if (settings.mode === "interval") {
|
if (settings.mode === "interval") {
|
||||||
var timer = monitorTimers[screenName]
|
var timer = monitorTimers[screenName];
|
||||||
if (!timer && monitorTimerComponent && monitorTimerComponent.status === Component.Ready) {
|
if (!timer && monitorTimerComponent && monitorTimerComponent.status === Component.Ready) {
|
||||||
var newTimers = Object.assign({}, monitorTimers)
|
var newTimers = Object.assign({}, monitorTimers);
|
||||||
newTimers[screenName] = monitorTimerComponent.createObject(root)
|
newTimers[screenName] = monitorTimerComponent.createObject(root);
|
||||||
newTimers[screenName].targetScreen = screenName
|
newTimers[screenName].targetScreen = screenName;
|
||||||
monitorTimers = newTimers
|
monitorTimers = newTimers;
|
||||||
timer = monitorTimers[screenName]
|
timer = monitorTimers[screenName];
|
||||||
}
|
}
|
||||||
if (timer) {
|
if (timer) {
|
||||||
timer.interval = settings.interval * 1000
|
timer.interval = settings.interval * 1000;
|
||||||
timer.start()
|
timer.start();
|
||||||
}
|
}
|
||||||
} else if (settings.mode === "time") {
|
} else if (settings.mode === "time") {
|
||||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
var newChecks = Object.assign({}, monitorLastTimeChecks);
|
||||||
newChecks[screenName] = ""
|
newChecks[screenName] = "";
|
||||||
monitorLastTimeChecks = newChecks
|
monitorLastTimeChecks = newChecks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopMonitorCycling(screenName) {
|
function stopMonitorCycling(screenName) {
|
||||||
var timer = monitorTimers[screenName]
|
var timer = monitorTimers[screenName];
|
||||||
if (timer) {
|
if (timer) {
|
||||||
timer.stop()
|
timer.stop();
|
||||||
timer.destroy()
|
timer.destroy();
|
||||||
var newTimers = Object.assign({}, monitorTimers)
|
var newTimers = Object.assign({}, monitorTimers);
|
||||||
delete newTimers[screenName]
|
delete newTimers[screenName];
|
||||||
monitorTimers = newTimers
|
monitorTimers = newTimers;
|
||||||
}
|
}
|
||||||
|
|
||||||
var process = monitorProcesses[screenName]
|
var process = monitorProcesses[screenName];
|
||||||
if (process) {
|
if (process) {
|
||||||
process.destroy()
|
process.destroy();
|
||||||
var newProcesses = Object.assign({}, monitorProcesses)
|
var newProcesses = Object.assign({}, monitorProcesses);
|
||||||
delete newProcesses[screenName]
|
delete newProcesses[screenName];
|
||||||
monitorProcesses = newProcesses
|
monitorProcesses = newProcesses;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
var newChecks = Object.assign({}, monitorLastTimeChecks);
|
||||||
delete newChecks[screenName]
|
delete newChecks[screenName];
|
||||||
monitorLastTimeChecks = newChecks
|
monitorLastTimeChecks = newChecks;
|
||||||
}
|
}
|
||||||
|
|
||||||
function cycleToNextWallpaper(screenName, wallpaperPath) {
|
function cycleToNextWallpaper(screenName, wallpaperPath) {
|
||||||
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath
|
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath;
|
||||||
if (!currentWallpaper) return
|
if (!currentWallpaper)
|
||||||
|
return;
|
||||||
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'))
|
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'));
|
||||||
|
|
||||||
if (screenName && monitorProcessComponent && monitorProcessComponent.status === Component.Ready) {
|
if (screenName && monitorProcessComponent && monitorProcessComponent.status === Component.Ready) {
|
||||||
// Use per-monitor process
|
// Use per-monitor process
|
||||||
var process = monitorProcesses[screenName]
|
var process = monitorProcesses[screenName];
|
||||||
if (!process) {
|
if (!process) {
|
||||||
var newProcesses = Object.assign({}, monitorProcesses)
|
var newProcesses = Object.assign({}, monitorProcesses);
|
||||||
newProcesses[screenName] = monitorProcessComponent.createObject(root)
|
newProcesses[screenName] = monitorProcessComponent.createObject(root);
|
||||||
monitorProcesses = newProcesses
|
monitorProcesses = newProcesses;
|
||||||
process = monitorProcesses[screenName]
|
process = monitorProcesses[screenName];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process) {
|
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.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.targetScreenName = screenName;
|
||||||
process.currentWallpaper = currentWallpaper
|
process.currentWallpaper = currentWallpaper;
|
||||||
process.goToPrevious = false
|
process.goToPrevious = false;
|
||||||
process.running = true
|
process.running = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use global process for fallback
|
// 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.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.targetScreenName = screenName || "";
|
||||||
cyclingProcess.currentWallpaper = currentWallpaper
|
cyclingProcess.currentWallpaper = currentWallpaper;
|
||||||
cyclingProcess.running = true
|
cyclingProcess.running = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cycleToPrevWallpaper(screenName, wallpaperPath) {
|
function cycleToPrevWallpaper(screenName, wallpaperPath) {
|
||||||
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath
|
const currentWallpaper = wallpaperPath || SessionData.wallpaperPath;
|
||||||
if (!currentWallpaper) return
|
if (!currentWallpaper)
|
||||||
|
return;
|
||||||
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'))
|
const wallpaperDir = currentWallpaper.substring(0, currentWallpaper.lastIndexOf('/'));
|
||||||
|
|
||||||
if (screenName && monitorProcessComponent && monitorProcessComponent.status === Component.Ready) {
|
if (screenName && monitorProcessComponent && monitorProcessComponent.status === Component.Ready) {
|
||||||
// Use per-monitor process (same as next, but with prev flag)
|
// Use per-monitor process (same as next, but with prev flag)
|
||||||
var process = monitorProcesses[screenName]
|
var process = monitorProcesses[screenName];
|
||||||
if (!process) {
|
if (!process) {
|
||||||
var newProcesses = Object.assign({}, monitorProcesses)
|
var newProcesses = Object.assign({}, monitorProcesses);
|
||||||
newProcesses[screenName] = monitorProcessComponent.createObject(root)
|
newProcesses[screenName] = monitorProcessComponent.createObject(root);
|
||||||
monitorProcesses = newProcesses
|
monitorProcesses = newProcesses;
|
||||||
process = monitorProcesses[screenName]
|
process = monitorProcesses[screenName];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (process) {
|
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.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.targetScreenName = screenName;
|
||||||
process.currentWallpaper = currentWallpaper
|
process.currentWallpaper = currentWallpaper;
|
||||||
process.goToPrevious = true
|
process.goToPrevious = true;
|
||||||
process.running = true
|
process.running = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Use global process for fallback
|
// 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.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.targetScreenName = screenName || "";
|
||||||
prevCyclingProcess.currentWallpaper = currentWallpaper
|
prevCyclingProcess.currentWallpaper = currentWallpaper;
|
||||||
prevCyclingProcess.running = true
|
prevCyclingProcess.running = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cycleNextManually() {
|
function cycleNextManually() {
|
||||||
if (SessionData.wallpaperPath) {
|
if (SessionData.wallpaperPath) {
|
||||||
cycleToNextWallpaper()
|
cycleToNextWallpaper();
|
||||||
// Restart timers if cycling is active
|
// Restart timers if cycling is active
|
||||||
if (cyclingActive && SessionData.wallpaperCyclingEnabled) {
|
if (cyclingActive && SessionData.wallpaperCyclingEnabled) {
|
||||||
if (SessionData.wallpaperCyclingMode === "interval") {
|
if (SessionData.wallpaperCyclingMode === "interval") {
|
||||||
intervalTimer.interval = cachedCyclingInterval * 1000
|
intervalTimer.interval = cachedCyclingInterval * 1000;
|
||||||
intervalTimer.restart()
|
intervalTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,71 +288,73 @@ Singleton {
|
|||||||
|
|
||||||
function cyclePrevManually() {
|
function cyclePrevManually() {
|
||||||
if (SessionData.wallpaperPath) {
|
if (SessionData.wallpaperPath) {
|
||||||
cycleToPrevWallpaper()
|
cycleToPrevWallpaper();
|
||||||
// Restart timers if cycling is active
|
// Restart timers if cycling is active
|
||||||
if (cyclingActive && SessionData.wallpaperCyclingEnabled) {
|
if (cyclingActive && SessionData.wallpaperCyclingEnabled) {
|
||||||
if (SessionData.wallpaperCyclingMode === "interval") {
|
if (SessionData.wallpaperCyclingMode === "interval") {
|
||||||
intervalTimer.interval = cachedCyclingInterval * 1000
|
intervalTimer.interval = cachedCyclingInterval * 1000;
|
||||||
intervalTimer.restart()
|
intervalTimer.restart();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cycleNextForMonitor(screenName) {
|
function cycleNextForMonitor(screenName) {
|
||||||
if (!screenName) return
|
if (!screenName)
|
||||||
|
return;
|
||||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName)
|
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||||
if (currentWallpaper) {
|
if (currentWallpaper) {
|
||||||
cycleToNextWallpaper(screenName, currentWallpaper)
|
cycleToNextWallpaper(screenName, currentWallpaper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function cyclePrevForMonitor(screenName) {
|
function cyclePrevForMonitor(screenName) {
|
||||||
if (!screenName) return
|
if (!screenName)
|
||||||
|
return;
|
||||||
var currentWallpaper = SessionData.getMonitorWallpaper(screenName)
|
var currentWallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||||
if (currentWallpaper) {
|
if (currentWallpaper) {
|
||||||
cycleToPrevWallpaper(screenName, currentWallpaper)
|
cycleToPrevWallpaper(screenName, currentWallpaper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkTimeBasedCycling() {
|
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 (!SessionData.perMonitorWallpaper) {
|
||||||
if (currentTime === cachedCyclingTime && currentTime !== lastTimeCheck) {
|
if (currentTime === cachedCyclingTime && currentTime !== lastTimeCheck) {
|
||||||
lastTimeCheck = currentTime
|
lastTimeCheck = currentTime;
|
||||||
cycleToNextWallpaper()
|
cycleToNextWallpaper();
|
||||||
} else if (currentTime !== cachedCyclingTime) {
|
} else if (currentTime !== cachedCyclingTime) {
|
||||||
lastTimeCheck = ""
|
lastTimeCheck = "";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
checkPerMonitorTimeBasedCycling(currentTime)
|
checkPerMonitorTimeBasedCycling(currentTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkPerMonitorTimeBasedCycling(currentTime) {
|
function checkPerMonitorTimeBasedCycling(currentTime) {
|
||||||
if (typeof Quickshell === "undefined") return
|
if (typeof Quickshell === "undefined")
|
||||||
|
return;
|
||||||
var screens = Quickshell.screens
|
var screens = Quickshell.screens;
|
||||||
for (var i = 0; i < screens.length; i++) {
|
for (var i = 0; i < screens.length; i++) {
|
||||||
var screenName = screens[i].name
|
var screenName = screens[i].name;
|
||||||
var settings = SessionData.getMonitorCyclingSettings(screenName)
|
var settings = SessionData.getMonitorCyclingSettings(screenName);
|
||||||
var wallpaper = SessionData.getMonitorWallpaper(screenName)
|
var wallpaper = SessionData.getMonitorWallpaper(screenName);
|
||||||
|
|
||||||
if (settings.enabled && settings.mode === "time" && wallpaper && !wallpaper.startsWith("#")) {
|
if (settings.enabled && settings.mode === "time" && wallpaper && !wallpaper.startsWith("#")) {
|
||||||
var lastCheck = monitorLastTimeChecks[screenName] || ""
|
var lastCheck = monitorLastTimeChecks[screenName] || "";
|
||||||
|
|
||||||
if (currentTime === settings.time && currentTime !== lastCheck) {
|
if (currentTime === settings.time && currentTime !== lastCheck) {
|
||||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
var newChecks = Object.assign({}, monitorLastTimeChecks);
|
||||||
newChecks[screenName] = currentTime
|
newChecks[screenName] = currentTime;
|
||||||
monitorLastTimeChecks = newChecks
|
monitorLastTimeChecks = newChecks;
|
||||||
cycleNextForMonitor(screenName)
|
cycleNextForMonitor(screenName);
|
||||||
} else if (currentTime !== settings.time) {
|
} else if (currentTime !== settings.time) {
|
||||||
var newChecks = Object.assign({}, monitorLastTimeChecks)
|
var newChecks = Object.assign({}, monitorLastTimeChecks);
|
||||||
newChecks[screenName] = ""
|
newChecks[screenName] = "";
|
||||||
monitorLastTimeChecks = newChecks
|
monitorLastTimeChecks = newChecks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -352,7 +365,11 @@ Singleton {
|
|||||||
interval: cachedCyclingInterval * 1000
|
interval: cachedCyclingInterval * 1000
|
||||||
running: false
|
running: false
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: cycleToNextWallpaper()
|
onTriggered: {
|
||||||
|
if (anyFullscreen)
|
||||||
|
return;
|
||||||
|
cycleToNextWallpaper();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SystemClock {
|
SystemClock {
|
||||||
@@ -360,7 +377,7 @@ Singleton {
|
|||||||
precision: SystemClock.Minutes
|
precision: SystemClock.Minutes
|
||||||
onDateChanged: {
|
onDateChanged: {
|
||||||
if ((SessionData.wallpaperCyclingMode === "time" && cyclingActive) || SessionData.perMonitorWallpaper) {
|
if ((SessionData.wallpaperCyclingMode === "time" && cyclingActive) || SessionData.perMonitorWallpaper) {
|
||||||
checkTimeBasedCycling()
|
checkTimeBasedCycling();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -376,22 +393,23 @@ Singleton {
|
|||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (text && text.trim()) {
|
if (text && text.trim()) {
|
||||||
const files = text.trim().split('\n').filter(file => file.length > 0)
|
const files = text.trim().split('\n').filter(file => file.length > 0);
|
||||||
if (files.length <= 1) return
|
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 nextIndex = (currentIndex + 1) % wallpaperList.length;
|
||||||
const currentPath = cyclingProcess.currentWallpaper
|
const nextWallpaper = wallpaperList[nextIndex];
|
||||||
let currentIndex = wallpaperList.findIndex(path => path === currentPath)
|
|
||||||
if (currentIndex === -1) currentIndex = 0
|
|
||||||
|
|
||||||
const nextIndex = (currentIndex + 1) % wallpaperList.length
|
|
||||||
const nextWallpaper = wallpaperList[nextIndex]
|
|
||||||
|
|
||||||
if (nextWallpaper && nextWallpaper !== currentPath) {
|
if (nextWallpaper && nextWallpaper !== currentPath) {
|
||||||
if (cyclingProcess.targetScreenName) {
|
if (cyclingProcess.targetScreenName) {
|
||||||
SessionData.setMonitorWallpaper(cyclingProcess.targetScreenName, nextWallpaper)
|
SessionData.setMonitorWallpaper(cyclingProcess.targetScreenName, nextWallpaper);
|
||||||
} else {
|
} else {
|
||||||
SessionData.setWallpaper(nextWallpaper)
|
SessionData.setWallpaper(nextWallpaper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -410,27 +428,27 @@ Singleton {
|
|||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
if (text && text.trim()) {
|
if (text && text.trim()) {
|
||||||
const files = text.trim().split('\n').filter(file => file.length > 0)
|
const files = text.trim().split('\n').filter(file => file.length > 0);
|
||||||
if (files.length <= 1) return
|
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 prevIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1;
|
||||||
const currentPath = prevCyclingProcess.currentWallpaper
|
const prevWallpaper = wallpaperList[prevIndex];
|
||||||
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]
|
|
||||||
|
|
||||||
if (prevWallpaper && prevWallpaper !== currentPath) {
|
if (prevWallpaper && prevWallpaper !== currentPath) {
|
||||||
if (prevCyclingProcess.targetScreenName) {
|
if (prevCyclingProcess.targetScreenName) {
|
||||||
SessionData.setMonitorWallpaper(prevCyclingProcess.targetScreenName, prevWallpaper)
|
SessionData.setMonitorWallpaper(prevCyclingProcess.targetScreenName, prevWallpaper);
|
||||||
} else {
|
} else {
|
||||||
SessionData.setWallpaper(prevWallpaper)
|
SessionData.setWallpaper(prevWallpaper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ Item {
|
|||||||
property string layerNamespace: "dms:popout"
|
property string layerNamespace: "dms:popout"
|
||||||
property alias content: contentLoader.sourceComponent
|
property alias content: contentLoader.sourceComponent
|
||||||
property alias contentLoader: contentLoader
|
property alias contentLoader: contentLoader
|
||||||
|
property Component overlayContent: null
|
||||||
|
property alias overlayLoader: overlayLoader
|
||||||
property real popupWidth: 400
|
property real popupWidth: 400
|
||||||
property real popupHeight: 300
|
property real popupHeight: 300
|
||||||
property real triggerX: 0
|
property real triggerX: 0
|
||||||
@@ -243,6 +245,13 @@ Item {
|
|||||||
backgroundClicked();
|
backgroundClicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
id: overlayLoader
|
||||||
|
anchors.fill: parent
|
||||||
|
active: root.overlayContent !== null && backgroundWindow.visible
|
||||||
|
sourceComponent: root.overlayContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
|
|||||||
Reference in New Issue
Block a user