mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-06 13:38:28 -04:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 82a9824175 | |||
| 2baf048293 | |||
| cffc33e14a | |||
| 0c811e3417 | |||
| 6ad46cf2c2 | |||
| 34626070af | |||
| 80b27b9a6a | |||
| 49f968d26b | |||
| 99b0dc596d | |||
| 158c0c12d8 | |||
| e089948225 | |||
| 7f2ba56e06 | |||
| 6de5593216 | |||
| 365474b0d9 | |||
| dc8a47644a | |||
| 27483e68dc | |||
| 400a18a8ed | |||
| 32ddf614c3 | |||
| 19d919ed5c | |||
| 594a2cde19 | |||
| 11287459c3 |
@@ -313,6 +313,7 @@ func EnsureContrastDPSLstar(hexColor, hexBg string, minLc float64, isLightMode b
|
|||||||
fg := HexToRGB(hexColor)
|
fg := HexToRGB(hexColor)
|
||||||
cf := colorful.Color{R: fg.R, G: fg.G, B: fg.B}
|
cf := colorful.Color{R: fg.R, G: fg.G, B: fg.B}
|
||||||
Lf, af, bf := cf.Lab()
|
Lf, af, bf := cf.Lab()
|
||||||
|
Lf *= 100.0
|
||||||
|
|
||||||
dir := 1.0
|
dir := 1.0
|
||||||
if isLightMode {
|
if isLightMode {
|
||||||
@@ -341,6 +342,7 @@ func EnsureContrastDPSBidirectional(hexColor, hexBg string, minLc float64, isLig
|
|||||||
fg := HexToRGB(hexColor)
|
fg := HexToRGB(hexColor)
|
||||||
cf := colorful.Color{R: fg.R, G: fg.G, B: fg.B}
|
cf := colorful.Color{R: fg.R, G: fg.G, B: fg.B}
|
||||||
origL, af, bf := cf.Lab()
|
origL, af, bf := cf.Lab()
|
||||||
|
origL *= 100.0
|
||||||
|
|
||||||
var darkerResult, lighterResult string
|
var darkerResult, lighterResult string
|
||||||
darkerL, lighterL := origL, origL
|
darkerL, lighterL := origL, origL
|
||||||
@@ -420,6 +422,24 @@ func blendHue(base, target, factor float64) float64 {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// color8 sits a fixed L* offset from the background so it keeps its dim role
|
||||||
|
// regardless of primary brightness (conventional palettes put ANSI bright
|
||||||
|
// black ~2-2.5:1 from the background, e.g. catppuccin-mocha #585b70 on #1e1e2e)
|
||||||
|
func DeriveDim(bgHex string, hue, sat float64, isLight bool) string {
|
||||||
|
offset := 22.0
|
||||||
|
if isLight {
|
||||||
|
offset = -offset
|
||||||
|
}
|
||||||
|
|
||||||
|
bgL := getLstar(bgHex)
|
||||||
|
targetL := math.Max(0, math.Min(100, bgL+offset))
|
||||||
|
|
||||||
|
tint := HSVToRGB(HSV{H: hue, S: sat, V: 0.5})
|
||||||
|
c := colorful.Color{R: tint.R, G: tint.G, B: tint.B}
|
||||||
|
_, af, bf := c.Lab()
|
||||||
|
return labToHex(targetL, af, bf)
|
||||||
|
}
|
||||||
|
|
||||||
func DeriveContainer(primary string, isLight bool) string {
|
func DeriveContainer(primary string, isLight bool) string {
|
||||||
rgb := HexToRGB(primary)
|
rgb := HexToRGB(primary)
|
||||||
hsv := RGBToHSV(rgb)
|
hsv := RGBToHSV(rgb)
|
||||||
@@ -500,10 +520,7 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) Palette {
|
|||||||
gray7V := baseVal * 0.28
|
gray7V := baseVal * 0.28
|
||||||
palette.Color7 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray7S, V: gray7V})), bgColor, normalTextTarget, opts))
|
palette.Color7 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray7S, V: gray7V})), bgColor, normalTextTarget, opts))
|
||||||
|
|
||||||
gray8S := baseSat * 0.05
|
palette.Color8 = NewColorInfo(DeriveDim(bgColor, hsv.H, baseSat*0.05, true))
|
||||||
gray8V := baseVal * 0.85
|
|
||||||
dimTarget := secondaryTarget * 0.5
|
|
||||||
palette.Color8 = NewColorInfo(ensureContrastBidirectional(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray8S, V: gray8V})), bgColor, dimTarget, opts))
|
|
||||||
|
|
||||||
brightRedS := math.Min(baseSat*1.0, 1.0)
|
brightRedS := math.Min(baseSat*1.0, 1.0)
|
||||||
brightRedV := math.Min(baseVal*1.2, 1.0)
|
brightRedV := math.Min(baseVal*1.2, 1.0)
|
||||||
@@ -559,9 +576,7 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) Palette {
|
|||||||
gray7V := math.Min(baseVal*1.05, 1.0)
|
gray7V := math.Min(baseVal*1.05, 1.0)
|
||||||
palette.Color7 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray7S, V: gray7V})), bgColor, normalTextTarget, opts))
|
palette.Color7 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray7S, V: gray7V})), bgColor, normalTextTarget, opts))
|
||||||
|
|
||||||
gray8S := baseSat * 0.15
|
palette.Color8 = NewColorInfo(DeriveDim(bgColor, hsv.H, baseSat*0.15, false))
|
||||||
gray8V := baseVal * 0.65
|
|
||||||
palette.Color8 = NewColorInfo(ensureContrastAuto(RGBToHex(HSVToRGB(HSV{H: hsv.H, S: gray8S, V: gray8V})), bgColor, secondaryTarget, opts))
|
|
||||||
|
|
||||||
brightRedS := math.Min(baseSat*0.75, 1.0)
|
brightRedS := math.Min(baseSat*0.75, 1.0)
|
||||||
brightRedV := math.Min(baseVal*1.35, 1.0)
|
brightRedV := math.Min(baseVal*1.35, 1.0)
|
||||||
|
|||||||
@@ -679,3 +679,73 @@ func TestContrastAlgorithmComparison(t *testing.T) {
|
|||||||
|
|
||||||
t.Logf("WCAG and DPS palettes differ in %d/16 colors", differentCount)
|
t.Logf("WCAG and DPS palettes differ in %d/16 colors", differentCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnsureContrastDPSLightModeStaysLight(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
result string
|
||||||
|
bg string
|
||||||
|
minLstar float64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "bidirectional adjustment",
|
||||||
|
result: EnsureContrastDPSBidirectional("#d0ccc6", "#f8f8f8", 17.5, true),
|
||||||
|
bg: "#f8f8f8",
|
||||||
|
minLstar: 20.0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "lstar adjustment",
|
||||||
|
result: EnsureContrastDPSLstar("#c0c0c0", "#f8f8f8", 30.0, true),
|
||||||
|
bg: "#f8f8f8",
|
||||||
|
minLstar: 20.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
lstar := getLstar(tt.result)
|
||||||
|
if lstar < tt.minLstar {
|
||||||
|
t.Errorf("result %s has L* %.2f on light bg %s, expected >= %.2f (collapsed to near-black)",
|
||||||
|
tt.result, lstar, tt.bg, tt.minLstar)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeneratePaletteColor8Dim(t *testing.T) {
|
||||||
|
hues := []string{"#e91e63", "#f59e0b", "#22c55e", "#06b6d4", "#8b5cf6", "#ef4444"}
|
||||||
|
|
||||||
|
for _, base := range hues {
|
||||||
|
t.Run(base, func(t *testing.T) {
|
||||||
|
palette := GeneratePalette(base, PaletteOptions{IsLight: false, UseDPS: true})
|
||||||
|
|
||||||
|
bgRatio := ContrastRatio(palette.Color8.Hex, palette.Color0.Hex)
|
||||||
|
if bgRatio < 1.5 || bgRatio > 3.0 {
|
||||||
|
t.Errorf("Color8 %s vs bg %s ratio %.2f, expected 1.5-3.0 (bright black stays near bg)",
|
||||||
|
palette.Color8.Hex, palette.Color0.Hex, bgRatio)
|
||||||
|
}
|
||||||
|
|
||||||
|
sepRatio := ContrastRatio(palette.Color4.Hex, palette.Color8.Hex)
|
||||||
|
if sepRatio < 2.0 {
|
||||||
|
t.Errorf("Color4 %s vs Color8 %s ratio %.2f, expected >= 2.0 (blue must not collide with bright black)",
|
||||||
|
palette.Color4.Hex, palette.Color8.Hex, sepRatio)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeneratePaletteLightColor8StaysLight(t *testing.T) {
|
||||||
|
palette := GeneratePalette("#f59e0b", PaletteOptions{IsLight: true, UseDPS: true})
|
||||||
|
|
||||||
|
lstar := getLstar(palette.Color8.Hex)
|
||||||
|
if lstar < 60.0 {
|
||||||
|
t.Errorf("light mode Color8 %s has L* %.2f, expected >= 60 (dim grey, not near-black)",
|
||||||
|
palette.Color8.Hex, lstar)
|
||||||
|
}
|
||||||
|
|
||||||
|
bgRatio := ContrastRatio(palette.Color8.Hex, palette.Color0.Hex)
|
||||||
|
if bgRatio > 3.0 {
|
||||||
|
t.Errorf("light mode Color8 %s vs bg %s ratio %.2f, expected <= 3.0",
|
||||||
|
palette.Color8.Hex, palette.Color0.Hex, bgRatio)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ func NewGentooDistribution(config DistroConfig, logChan chan<- string) *GentooDi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func emergeInstallArgs(packages []string) []string {
|
||||||
|
args := []string{"emerge", "--ask=n", "--quiet", "--autounmask-continue=y", "--autounmask-keep-keywords=y", "--autounmask-keep-masks=y"}
|
||||||
|
return append(args, packages...)
|
||||||
|
}
|
||||||
|
|
||||||
func (g *GentooDistribution) getArchKeyword() string {
|
func (g *GentooDistribution) getArchKeyword() string {
|
||||||
arch := runtime.GOARCH
|
arch := runtime.GOARCH
|
||||||
switch arch {
|
switch arch {
|
||||||
@@ -319,6 +324,7 @@ func (g *GentooDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
}
|
}
|
||||||
g.log("Portage tree synced successfully")
|
g.log("Portage tree synced successfully")
|
||||||
|
|
||||||
|
args := emergeInstallArgs(missingPkgs)
|
||||||
g.log(fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")))
|
g.log(fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")))
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: PhasePrerequisites,
|
Phase: PhasePrerequisites,
|
||||||
@@ -326,12 +332,10 @@ func (g *GentooDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
Step: fmt.Sprintf("Installing %d prerequisites...", len(missingPkgs)),
|
Step: fmt.Sprintf("Installing %d prerequisites...", len(missingPkgs)),
|
||||||
IsComplete: false,
|
IsComplete: false,
|
||||||
NeedsSudo: true,
|
NeedsSudo: true,
|
||||||
CommandInfo: fmt.Sprintf("sudo emerge --ask=n %s", strings.Join(missingPkgs, " ")),
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
LogOutput: fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")),
|
LogOutput: fmt.Sprintf("Installing prerequisites: %s", strings.Join(missingPkgs, ", ")),
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"emerge", "--ask=n", "--quiet"}
|
|
||||||
args = append(args, missingPkgs...)
|
|
||||||
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -521,8 +525,7 @@ func (g *GentooDistribution) installPortagePackages(ctx context.Context, package
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"emerge", "--ask=n", "--quiet"}
|
args := emergeInstallArgs(packageNames)
|
||||||
args = append(args, packageNames...)
|
|
||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: PhaseSystemPackages,
|
Phase: PhaseSystemPackages,
|
||||||
@@ -713,8 +716,7 @@ func (g *GentooDistribution) installGURUPackages(ctx context.Context, packages [
|
|||||||
guruPackages[i] = pkg + "::guru"
|
guruPackages[i] = pkg + "::guru"
|
||||||
}
|
}
|
||||||
|
|
||||||
args := []string{"emerge", "--ask=n", "--quiet"}
|
args := emergeInstallArgs(guruPackages)
|
||||||
args = append(args, guruPackages...)
|
|
||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
progressChan <- InstallProgressMsg{
|
||||||
Phase: PhaseAURPackages,
|
Phase: PhaseAURPackages,
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func NewManager() (*Manager, error) {
|
|||||||
return nil, fmt.Errorf("failed to find keyboards: %w", err)
|
return nil, fmt.Errorf("failed to find keyboards: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
initialCapsLock := readInitialCapsLockState(devices[0])
|
initialCapsLock, _ := capsLockFromDevices(devices)
|
||||||
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
watcher, err := fsnotify.NewWatcher()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -85,14 +85,21 @@ func NewManager() (*Manager, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func readInitialCapsLockState(device EvdevDevice) bool {
|
func capsLockFromDevices(devices []EvdevDevice) (bool, bool) {
|
||||||
ledStates, err := device.State(evLedType)
|
for _, device := range devices {
|
||||||
if err != nil {
|
if device == nil {
|
||||||
log.Debugf("Could not read LED state: %v", err)
|
continue
|
||||||
return false
|
}
|
||||||
|
|
||||||
|
ledStates, err := device.State(evLedType)
|
||||||
|
if err != nil || len(ledStates) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return ledStates[ledCapslockKey], true
|
||||||
}
|
}
|
||||||
|
|
||||||
return ledStates[ledCapslockKey]
|
return false, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func findKeyboards() ([]EvdevDevice, error) {
|
func findKeyboards() ([]EvdevDevice, error) {
|
||||||
@@ -297,25 +304,22 @@ func (m *Manager) readAndUpdateCapsLockState(deviceIndex int) {
|
|||||||
m.devicesMutex.RUnlock()
|
m.devicesMutex.RUnlock()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
device := m.devices[deviceIndex]
|
ordered := make([]EvdevDevice, 0, len(m.devices))
|
||||||
|
ordered = append(ordered, m.devices[deviceIndex])
|
||||||
|
for i, device := range m.devices {
|
||||||
|
if i == deviceIndex {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ordered = append(ordered, device)
|
||||||
|
}
|
||||||
m.devicesMutex.RUnlock()
|
m.devicesMutex.RUnlock()
|
||||||
|
|
||||||
ledStates, err := device.State(evLedType)
|
capsLockState, ok := capsLockFromDevices(ordered)
|
||||||
if err != nil {
|
if !ok {
|
||||||
log.Warnf("Failed to read LED state: %v", err)
|
log.Debug("No LED-capable device available for caps lock state")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(ledStates) == 0 {
|
|
||||||
log.Debug("No LED state available (empty map)")
|
|
||||||
|
|
||||||
// This means the device either:
|
|
||||||
// - doesn't support LED reporting at all, or
|
|
||||||
// - the kernel returned an empty state
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
capsLockState := ledStates[ledCapslockKey]
|
|
||||||
m.updateCapsLockStateDirect(capsLockState)
|
m.updateCapsLockStateDirect(capsLockState)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -306,7 +306,7 @@ func TestNotifySubscribers(t *testing.T) {
|
|||||||
m.Close()
|
m.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadInitialCapsLockState(t *testing.T) {
|
func TestCapsLockFromDevices(t *testing.T) {
|
||||||
t.Run("caps lock is on", func(t *testing.T) {
|
t.Run("caps lock is on", func(t *testing.T) {
|
||||||
mockDevice := mocks.NewMockEvdevDevice(t)
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
ledState := evdev.StateMap{
|
ledState := evdev.StateMap{
|
||||||
@@ -314,7 +314,8 @@ func TestReadInitialCapsLockState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
||||||
|
|
||||||
result := readInitialCapsLockState(mockDevice)
|
result, ok := capsLockFromDevices([]EvdevDevice{mockDevice})
|
||||||
|
assert.True(t, ok)
|
||||||
assert.True(t, result)
|
assert.True(t, result)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -325,7 +326,8 @@ func TestReadInitialCapsLockState(t *testing.T) {
|
|||||||
}
|
}
|
||||||
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
||||||
|
|
||||||
result := readInitialCapsLockState(mockDevice)
|
result, ok := capsLockFromDevices([]EvdevDevice{mockDevice})
|
||||||
|
assert.True(t, ok)
|
||||||
assert.False(t, result)
|
assert.False(t, result)
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -333,9 +335,25 @@ func TestReadInitialCapsLockState(t *testing.T) {
|
|||||||
mockDevice := mocks.NewMockEvdevDevice(t)
|
mockDevice := mocks.NewMockEvdevDevice(t)
|
||||||
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(nil, errors.New("read error")).Once()
|
mockDevice.EXPECT().State(evdev.EvType(evLedType)).Return(nil, errors.New("read error")).Once()
|
||||||
|
|
||||||
result := readInitialCapsLockState(mockDevice)
|
result, ok := capsLockFromDevices([]EvdevDevice{mockDevice})
|
||||||
|
assert.False(t, ok)
|
||||||
assert.False(t, result)
|
assert.False(t, result)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("falls back past device without LED state", func(t *testing.T) {
|
||||||
|
noLedDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
noLedDevice.EXPECT().State(evdev.EvType(evLedType)).Return(evdev.StateMap{}, nil).Once()
|
||||||
|
|
||||||
|
ledDevice := mocks.NewMockEvdevDevice(t)
|
||||||
|
ledState := evdev.StateMap{
|
||||||
|
ledCapslockKey: true,
|
||||||
|
}
|
||||||
|
ledDevice.EXPECT().State(evdev.EvType(evLedType)).Return(ledState, nil).Once()
|
||||||
|
|
||||||
|
result, ok := capsLockFromDevices([]EvdevDevice{noLedDevice, nil, ledDevice})
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.True(t, result)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHasInputGroupAccess(t *testing.T) {
|
func TestHasInputGroupAccess(t *testing.T) {
|
||||||
|
|||||||
@@ -759,6 +759,9 @@ func (m *Manager) schedulerLoop() {
|
|||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
m.recalcSchedule(now)
|
m.recalcSchedule(now)
|
||||||
|
// publish independent of output readiness so night status never
|
||||||
|
// presents a stale schedule while applies are blocked (#2967)
|
||||||
|
m.updateStateFromSchedule()
|
||||||
|
|
||||||
waitDur := 24 * time.Hour
|
waitDur := 24 * time.Hour
|
||||||
if enabled {
|
if enabled {
|
||||||
@@ -1104,13 +1107,15 @@ func (m *Manager) SetTemperature(low, high int) error {
|
|||||||
m.configMutex.Unlock()
|
m.configMutex.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
m.config.LowTemp = low
|
updated := m.config
|
||||||
m.config.HighTemp = high
|
updated.LowTemp = low
|
||||||
err := m.config.Validate()
|
updated.HighTemp = high
|
||||||
m.configMutex.Unlock()
|
if err := updated.Validate(); err != nil {
|
||||||
if err != nil {
|
m.configMutex.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
m.config = updated
|
||||||
|
m.configMutex.Unlock()
|
||||||
m.triggerUpdate()
|
m.triggerUpdate()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1122,14 +1127,16 @@ func (m *Manager) SetLocation(lat, lon float64) error {
|
|||||||
m.configMutex.Unlock()
|
m.configMutex.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
m.config.Latitude = &lat
|
updated := m.config
|
||||||
m.config.Longitude = &lon
|
updated.Latitude = &lat
|
||||||
m.config.UseIPLocation = false
|
updated.Longitude = &lon
|
||||||
err := m.config.Validate()
|
updated.UseIPLocation = false
|
||||||
m.configMutex.Unlock()
|
if err := updated.Validate(); err != nil {
|
||||||
if err != nil {
|
m.configMutex.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
m.config = updated
|
||||||
|
m.configMutex.Unlock()
|
||||||
m.triggerUpdate()
|
m.triggerUpdate()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1164,13 +1171,15 @@ func (m *Manager) SetManualTimes(sunrise, sunset time.Time) error {
|
|||||||
m.configMutex.Unlock()
|
m.configMutex.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
m.config.ManualSunrise = &sunrise
|
updated := m.config
|
||||||
m.config.ManualSunset = &sunset
|
updated.ManualSunrise = &sunrise
|
||||||
err := m.config.Validate()
|
updated.ManualSunset = &sunset
|
||||||
m.configMutex.Unlock()
|
if err := updated.Validate(); err != nil {
|
||||||
if err != nil {
|
m.configMutex.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
m.config = updated
|
||||||
|
m.configMutex.Unlock()
|
||||||
m.triggerUpdate()
|
m.triggerUpdate()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1193,12 +1202,14 @@ func (m *Manager) SetGamma(gamma float64) error {
|
|||||||
m.configMutex.Unlock()
|
m.configMutex.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
m.config.Gamma = gamma
|
updated := m.config
|
||||||
err := m.config.Validate()
|
updated.Gamma = gamma
|
||||||
m.configMutex.Unlock()
|
if err := updated.Validate(); err != nil {
|
||||||
if err != nil {
|
m.configMutex.Unlock()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
m.config = updated
|
||||||
|
m.configMutex.Unlock()
|
||||||
m.triggerUpdate()
|
m.triggerUpdate()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient"
|
mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_gamma_control"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestManager_ActorSerializesOutputStateAccess(t *testing.T) {
|
func TestManager_ActorSerializesOutputStateAccess(t *testing.T) {
|
||||||
@@ -412,3 +413,75 @@ func TestNewManager_InvalidConfig(t *testing.T) {
|
|||||||
_, err := NewManager(mockDisplay, config)
|
_, err := NewManager(mockDisplay, config)
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSetters_RejectedValuesLeaveConfigUntouched(t *testing.T) {
|
||||||
|
newManager := func() *Manager {
|
||||||
|
return &Manager{
|
||||||
|
config: DefaultConfig(),
|
||||||
|
updateTrigger: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("SetTemperature", func(t *testing.T) {
|
||||||
|
m := newManager()
|
||||||
|
before := m.config
|
||||||
|
|
||||||
|
err := m.SetTemperature(3200, 2500)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, before, m.config)
|
||||||
|
assert.Empty(t, m.updateTrigger)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SetLocation", func(t *testing.T) {
|
||||||
|
m := newManager()
|
||||||
|
before := m.config
|
||||||
|
|
||||||
|
err := m.SetLocation(120.0, 10.0)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, before, m.config)
|
||||||
|
assert.Empty(t, m.updateTrigger)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("SetGamma", func(t *testing.T) {
|
||||||
|
m := newManager()
|
||||||
|
before := m.config
|
||||||
|
|
||||||
|
err := m.SetGamma(-1.0)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, before, m.config)
|
||||||
|
assert.Empty(t, m.updateTrigger)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetters_ValidValuesCommitAndTrigger(t *testing.T) {
|
||||||
|
m := &Manager{
|
||||||
|
config: DefaultConfig(),
|
||||||
|
updateTrigger: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
|
||||||
|
err := m.SetTemperature(3000, 6000)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, 3000, m.config.LowTemp)
|
||||||
|
assert.Equal(t, 6000, m.config.HighTemp)
|
||||||
|
assert.Len(t, m.updateTrigger, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestApplyGamma_SkipsUnchangedTempAndGamma(t *testing.T) {
|
||||||
|
m := &Manager{config: DefaultConfig()}
|
||||||
|
m.controlsInitialized = true
|
||||||
|
|
||||||
|
out := &outputState{
|
||||||
|
id: 1,
|
||||||
|
rampSize: 256,
|
||||||
|
gammaControl: &wlr_gamma_control.ZwlrGammaControlV1{},
|
||||||
|
lastTemp: 5000,
|
||||||
|
lastGamma: m.config.Gamma,
|
||||||
|
}
|
||||||
|
m.outputs.Store(out.id, out)
|
||||||
|
|
||||||
|
m.applyGamma(5000)
|
||||||
|
|
||||||
|
assert.False(t, out.failed, "unchanged temp must not reach the compositor write path")
|
||||||
|
assert.Equal(t, 5000, out.lastTemp)
|
||||||
|
assert.Equal(t, uint32(256), out.rampSize)
|
||||||
|
}
|
||||||
|
|||||||
@@ -271,3 +271,61 @@ function getConflictingBinds(keyCombo, currentAction, allBinds, modKey) {
|
|||||||
}
|
}
|
||||||
return conflicts;
|
return conflicts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function qtKeyFromName(name) {
|
||||||
|
var n = (name || "").toUpperCase();
|
||||||
|
if (n.length === 1 && n >= "A" && n <= "Z")
|
||||||
|
return Qt.Key_A + (n.charCodeAt(0) - 65);
|
||||||
|
if (n.length === 1 && n >= "0" && n <= "9")
|
||||||
|
return Qt.Key_0 + (n.charCodeAt(0) - 48);
|
||||||
|
if (n.length >= 2 && n[0] === "F") {
|
||||||
|
var f = parseInt(n.slice(1), 10);
|
||||||
|
if (f >= 1 && f <= 12)
|
||||||
|
return Qt.Key_F1 + (f - 1);
|
||||||
|
}
|
||||||
|
var named = {
|
||||||
|
"SPACE": Qt.Key_Space,
|
||||||
|
"TAB": Qt.Key_Tab,
|
||||||
|
"RETURN": Qt.Key_Return,
|
||||||
|
"ENTER": Qt.Key_Enter,
|
||||||
|
"BACKSPACE": Qt.Key_Backspace,
|
||||||
|
"DELETE": Qt.Key_Delete,
|
||||||
|
"HOME": Qt.Key_Home,
|
||||||
|
"END": Qt.Key_End,
|
||||||
|
"UP": Qt.Key_Up,
|
||||||
|
"DOWN": Qt.Key_Down,
|
||||||
|
"LEFT": Qt.Key_Left,
|
||||||
|
"RIGHT": Qt.Key_Right
|
||||||
|
};
|
||||||
|
return named[n] || 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isModifierKey(qk) {
|
||||||
|
return qk === Qt.Key_Control || qk === Qt.Key_Shift || qk === Qt.Key_Alt || qk === Qt.Key_Meta
|
||||||
|
|| qk === Qt.Key_NumLock || qk === Qt.Key_CapsLock || qk === Qt.Key_ScrollLock;
|
||||||
|
}
|
||||||
|
|
||||||
|
function eventMatchesCombo(event, combo) {
|
||||||
|
if (!combo)
|
||||||
|
return false;
|
||||||
|
var parts = combo.split("+");
|
||||||
|
var keyName = parts[parts.length - 1].trim().toUpperCase();
|
||||||
|
var wantsShift = false;
|
||||||
|
var hasCtrl = false;
|
||||||
|
for (var i = 0; i < parts.length - 1; i++) {
|
||||||
|
var mod = parts[i].trim().toLowerCase();
|
||||||
|
if (mod === "shift")
|
||||||
|
wantsShift = true;
|
||||||
|
else if (mod === "ctrl" || mod === "control")
|
||||||
|
hasCtrl = true;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (hasCtrl && !(event.modifiers & Qt.ControlModifier))
|
||||||
|
return false;
|
||||||
|
if (event.modifiers & (Qt.AltModifier | Qt.MetaModifier))
|
||||||
|
return false;
|
||||||
|
if (((event.modifiers & Qt.ShiftModifier) !== 0) !== wantsShift)
|
||||||
|
return false;
|
||||||
|
return event.key === qtKeyFromName(keyName);
|
||||||
|
}
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ Singleton {
|
|||||||
property var includedTransitions: availableWallpaperTransitions.filter(t => t !== "none")
|
property var includedTransitions: availableWallpaperTransitions.filter(t => t !== "none")
|
||||||
|
|
||||||
property bool wallpaperCyclingEnabled: false
|
property bool wallpaperCyclingEnabled: false
|
||||||
|
property bool wallpaperCyclingRandom: false
|
||||||
property string wallpaperCyclingMode: "interval"
|
property string wallpaperCyclingMode: "interval"
|
||||||
property int wallpaperCyclingInterval: 300
|
property int wallpaperCyclingInterval: 300
|
||||||
property string wallpaperCyclingTime: "06:00"
|
property string wallpaperCyclingTime: "06:00"
|
||||||
@@ -646,6 +647,11 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setWallpaperCyclingRandom(random) {
|
||||||
|
wallpaperCyclingRandom = random;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
function setWallpaperCyclingMode(mode) {
|
function setWallpaperCyclingMode(mode) {
|
||||||
wallpaperCyclingMode = mode;
|
wallpaperCyclingMode = mode;
|
||||||
saveSettings();
|
saveSettings();
|
||||||
@@ -692,6 +698,37 @@ Singleton {
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setMonitorCyclingRandom(screenName, random) {
|
||||||
|
var screen = null;
|
||||||
|
var screens = Quickshell.screens;
|
||||||
|
for (var i = 0; i < screens.length; i++) {
|
||||||
|
if (screens[i].name === screenName) {
|
||||||
|
screen = screens[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!screen) {
|
||||||
|
log.warn("Screen not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var identifier = typeof SettingsData !== "undefined" ? SettingsData.getScreenDisplayName(screen) : screen.name;
|
||||||
|
|
||||||
|
var newSettings = {};
|
||||||
|
for (var key in monitorCyclingSettings) {
|
||||||
|
var isThisScreen = key === screen.name || (screen.model && key === screen.model);
|
||||||
|
if (!isThisScreen) {
|
||||||
|
newSettings[key] = monitorCyclingSettings[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newSettings[identifier] = getMonitorCyclingSettings(screenName);
|
||||||
|
newSettings[identifier].random = random;
|
||||||
|
monitorCyclingSettings = newSettings;
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
|
||||||
function setMonitorCyclingMode(screenName, mode) {
|
function setMonitorCyclingMode(screenName, mode) {
|
||||||
var screen = null;
|
var screen = null;
|
||||||
var screens = Quickshell.screens;
|
var screens = Quickshell.screens;
|
||||||
@@ -1322,6 +1359,7 @@ Singleton {
|
|||||||
function getMonitorCyclingSettings(screenName) {
|
function getMonitorCyclingSettings(screenName) {
|
||||||
var defaults = {
|
var defaults = {
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
|
"random": false,
|
||||||
"mode": "interval",
|
"mode": "interval",
|
||||||
"interval": 300,
|
"interval": 300,
|
||||||
"time": "06:00"
|
"time": "06:00"
|
||||||
|
|||||||
@@ -659,9 +659,6 @@ Singleton {
|
|||||||
readonly property string iconTheme: resolveIconTheme()
|
readonly property string iconTheme: resolveIconTheme()
|
||||||
property var availableIconThemes: ["System Default"]
|
property var availableIconThemes: ["System Default"]
|
||||||
property string systemDefaultIconTheme: ""
|
property string systemDefaultIconTheme: ""
|
||||||
property bool qt5ctAvailable: false
|
|
||||||
property bool qt6ctAvailable: false
|
|
||||||
property bool gtkAvailable: false
|
|
||||||
|
|
||||||
property var cursorSettings: ({
|
property var cursorSettings: ({
|
||||||
"theme": "System Default",
|
"theme": "System Default",
|
||||||
@@ -798,8 +795,6 @@ Singleton {
|
|||||||
property int fadeToDpmsGracePeriod: 5
|
property int fadeToDpmsGracePeriod: 5
|
||||||
property string launchPrefix: ""
|
property string launchPrefix: ""
|
||||||
|
|
||||||
property bool gtkThemingEnabled: false
|
|
||||||
property bool qtThemingEnabled: false
|
|
||||||
property bool syncModeWithPortal: true
|
property bool syncModeWithPortal: true
|
||||||
property bool terminalsAlwaysDark: false
|
property bool terminalsAlwaysDark: false
|
||||||
|
|
||||||
@@ -922,6 +917,8 @@ Singleton {
|
|||||||
property bool lockPamInlineU2f: false
|
property bool lockPamInlineU2f: false
|
||||||
property bool lockPamExternallyManaged: false
|
property bool lockPamExternallyManaged: false
|
||||||
property string lockU2fPamPath: ""
|
property string lockU2fPamPath: ""
|
||||||
|
property string lockScreenSecurityKeyShortcut: "Ctrl+Q"
|
||||||
|
property bool lockScreenSecurityKeyShortcutEnabled: false
|
||||||
property bool greeterPamExternallyManaged: false
|
property bool greeterPamExternallyManaged: false
|
||||||
property string lockScreenInactiveColor: "#000000"
|
property string lockScreenInactiveColor: "#000000"
|
||||||
property int lockScreenNotificationMode: 0
|
property int lockScreenNotificationMode: 0
|
||||||
@@ -1683,7 +1680,6 @@ Singleton {
|
|||||||
_hasLoaded = true;
|
_hasLoaded = true;
|
||||||
applyStoredTheme();
|
applyStoredTheme();
|
||||||
updateCompositorCursor();
|
updateCompositorCursor();
|
||||||
Processes.detectQtTools();
|
|
||||||
Qt.callLater(checkIconThemeDrift);
|
Qt.callLater(checkIconThemeDrift);
|
||||||
|
|
||||||
_checkSettingsWritable();
|
_checkSettingsWritable();
|
||||||
|
|||||||
@@ -96,8 +96,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property bool matugenAvailable: false
|
property bool matugenAvailable: false
|
||||||
property bool gtkThemingEnabled: typeof SettingsData !== "undefined" ? SettingsData.gtkAvailable : false
|
|
||||||
property bool qtThemingEnabled: typeof SettingsData !== "undefined" ? (SettingsData.qt5ctAvailable || SettingsData.qt6ctAvailable) : false
|
|
||||||
property var workerRunning: false
|
property var workerRunning: false
|
||||||
property var pendingThemeRequest: null
|
property var pendingThemeRequest: null
|
||||||
|
|
||||||
@@ -352,6 +350,8 @@ Singleton {
|
|||||||
readonly property color readableSurfaceHigh: withAlpha(surfaceContainerHigh, popupTransparency)
|
readonly property color readableSurfaceHigh: withAlpha(surfaceContainerHigh, popupTransparency)
|
||||||
readonly property color floatingSurface: foregroundLayers ? readableSurface : withAlpha(readableSurface, 0)
|
readonly property color floatingSurface: foregroundLayers ? readableSurface : withAlpha(readableSurface, 0)
|
||||||
readonly property color floatingSurfaceHigh: foregroundLayers ? readableSurfaceHigh : withAlpha(readableSurfaceHigh, 0)
|
readonly property color floatingSurfaceHigh: foregroundLayers ? readableSurfaceHigh : withAlpha(readableSurfaceHigh, 0)
|
||||||
|
readonly property color floatingWindowSurface: readableSurface
|
||||||
|
readonly property color notepadWindowSurface: withAlpha(surfaceContainer, notepadTransparency)
|
||||||
readonly property color nestedSurface: floatingSurfaceHigh
|
readonly property color nestedSurface: floatingSurfaceHigh
|
||||||
readonly property color notificationFloatingSurface: notificationForegroundLayers ? readableSurface : withAlpha(readableSurface, 0)
|
readonly property color notificationFloatingSurface: notificationForegroundLayers ? readableSurface : withAlpha(readableSurface, 0)
|
||||||
readonly property color notificationFloatingSurfaceHigh: notificationForegroundLayers ? readableSurfaceHigh : withAlpha(readableSurfaceHigh, 0)
|
readonly property color notificationFloatingSurfaceHigh: notificationForegroundLayers ? readableSurfaceHigh : withAlpha(readableSurfaceHigh, 0)
|
||||||
|
|||||||
@@ -1,31 +1,69 @@
|
|||||||
pragma Singleton
|
pragma Singleton
|
||||||
|
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import Quickshell.Services.SystemTray
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var activeTrayMenus: ({})
|
property var activeTrayMenus: ({})
|
||||||
|
property var _pendingMenuRequest: null
|
||||||
|
|
||||||
|
signal openTrayMenuRequested
|
||||||
|
|
||||||
|
function requestOpenMenu(itemId, screenName) {
|
||||||
|
_pendingMenuRequest = {
|
||||||
|
"itemId": itemId,
|
||||||
|
"screenName": screenName
|
||||||
|
};
|
||||||
|
openTrayMenuRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every SystemTrayBar instance receives the signal; the claim ensures
|
||||||
|
// exactly one opens the menu, preferring the requested screen
|
||||||
|
function claimMenuRequest(instanceScreenName) {
|
||||||
|
if (!_pendingMenuRequest)
|
||||||
|
return null;
|
||||||
|
if (_pendingMenuRequest.screenName && _pendingMenuRequest.screenName !== instanceScreenName)
|
||||||
|
return null;
|
||||||
|
const request = _pendingMenuRequest;
|
||||||
|
_pendingMenuRequest = null;
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findTrayItem(itemId: string): var {
|
||||||
|
if (!itemId)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return SystemTray.items.values.find(item => {
|
||||||
|
const id = item?.id || "";
|
||||||
|
const title = item?.tooltipTitle || "";
|
||||||
|
const fullKey = title ? `${id}::${title}` : id;
|
||||||
|
return fullKey === itemId || id === itemId;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function registerMenu(screenName, menu) {
|
function registerMenu(screenName, menu) {
|
||||||
if (!screenName || !menu) return
|
if (!screenName || !menu)
|
||||||
const newMenus = Object.assign({}, activeTrayMenus)
|
return;
|
||||||
newMenus[screenName] = menu
|
const newMenus = Object.assign({}, activeTrayMenus);
|
||||||
activeTrayMenus = newMenus
|
newMenus[screenName] = menu;
|
||||||
|
activeTrayMenus = newMenus;
|
||||||
}
|
}
|
||||||
|
|
||||||
function unregisterMenu(screenName) {
|
function unregisterMenu(screenName) {
|
||||||
if (!screenName) return
|
if (!screenName)
|
||||||
const newMenus = Object.assign({}, activeTrayMenus)
|
return;
|
||||||
delete newMenus[screenName]
|
const newMenus = Object.assign({}, activeTrayMenus);
|
||||||
activeTrayMenus = newMenus
|
delete newMenus[screenName];
|
||||||
|
activeTrayMenus = newMenus;
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAllMenus() {
|
function closeHoverMenus() {
|
||||||
for (const screenName in activeTrayMenus) {
|
for (const screenName in activeTrayMenus) {
|
||||||
const menu = activeTrayMenus[screenName]
|
const menu = activeTrayMenus[screenName]
|
||||||
if (!menu) continue
|
if (!menu || menu.openedByHover !== true) continue
|
||||||
if (typeof menu.close === "function") {
|
if (typeof menu.close === "function") {
|
||||||
menu.close()
|
menu.close()
|
||||||
} else if (menu.showMenu !== undefined) {
|
} else if (menu.showMenu !== undefined) {
|
||||||
@@ -33,4 +71,17 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function closeAllMenus() {
|
||||||
|
for (const screenName in activeTrayMenus) {
|
||||||
|
const menu = activeTrayMenus[screenName];
|
||||||
|
if (!menu)
|
||||||
|
continue;
|
||||||
|
if (typeof menu.close === "function") {
|
||||||
|
menu.close();
|
||||||
|
} else if (menu.showMenu !== undefined) {
|
||||||
|
menu.showMenu = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -487,41 +487,10 @@ Singleton {
|
|||||||
return pamFprintDetected ? "probe_failed" : "missing_pam_support";
|
return pamFprintDetected ? "probe_failed" : "missing_pam_support";
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Qt tools detection ---
|
|
||||||
|
|
||||||
function detectQtTools() {
|
|
||||||
qtToolsDetectionProcess.running = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkPluginSettings() {
|
function checkPluginSettings() {
|
||||||
pluginSettingsCheckProcess.running = true;
|
pluginSettingsCheckProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
property var qtToolsDetectionProcess: Process {
|
|
||||||
command: ["sh", "-c", "echo -n 'qt5ct:'; command -v qt5ct >/dev/null && echo 'true' || echo 'false'; echo -n 'qt6ct:'; command -v qt6ct >/dev/null && echo 'true' || echo 'false'; echo -n 'gtk:'; (command -v gsettings >/dev/null || command -v dconf >/dev/null) && echo 'true' || echo 'false'"]
|
|
||||||
running: false
|
|
||||||
|
|
||||||
stdout: StdioCollector {
|
|
||||||
onStreamFinished: {
|
|
||||||
if (!settingsRoot)
|
|
||||||
return;
|
|
||||||
if (text && text.trim()) {
|
|
||||||
const lines = text.trim().split("\n");
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const line = lines[i];
|
|
||||||
if (line.startsWith("qt5ct:")) {
|
|
||||||
settingsRoot.qt5ctAvailable = line.split(":")[1] === "true";
|
|
||||||
} else if (line.startsWith("qt6ct:")) {
|
|
||||||
settingsRoot.qt6ctAvailable = line.split(":")[1] === "true";
|
|
||||||
} else if (line.startsWith("gtk:")) {
|
|
||||||
settingsRoot.gtkAvailable = line.split(":")[1] === "true";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: authApplyDebounce
|
id: authApplyDebounce
|
||||||
interval: 300
|
interval: 300
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ var SPEC = {
|
|||||||
includedTransitions: { def: ["fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"] },
|
includedTransitions: { def: ["fade", "wipe", "disc", "stripes", "iris bloom", "pixelate", "portal"] },
|
||||||
|
|
||||||
wallpaperCyclingEnabled: { def: false },
|
wallpaperCyclingEnabled: { def: false },
|
||||||
|
wallpaperCyclingRandom: { def: false },
|
||||||
wallpaperCyclingMode: { def: "interval" },
|
wallpaperCyclingMode: { def: "interval" },
|
||||||
wallpaperCyclingInterval: { def: 300 },
|
wallpaperCyclingInterval: { def: 300 },
|
||||||
wallpaperCyclingTime: { def: "06:00" },
|
wallpaperCyclingTime: { def: "06:00" },
|
||||||
|
|||||||
@@ -298,9 +298,6 @@ var SPEC = {
|
|||||||
lastAppliedIconTheme: { def: "" },
|
lastAppliedIconTheme: { def: "" },
|
||||||
availableIconThemes: { def: ["System Default"], persist: false },
|
availableIconThemes: { def: ["System Default"], persist: false },
|
||||||
systemDefaultIconTheme: { def: "", persist: false },
|
systemDefaultIconTheme: { def: "", persist: false },
|
||||||
qt5ctAvailable: { def: false, persist: false },
|
|
||||||
qt6ctAvailable: { def: false, persist: false },
|
|
||||||
gtkAvailable: { def: false, persist: false },
|
|
||||||
|
|
||||||
cursorSettings: { def: { theme: "System Default", size: 24, niri: { hideWhenTyping: false, hideAfterInactiveMs: 0 }, hyprland: { hideOnKeyPress: false, hideOnTouch: false, inactiveTimeout: 0 }, dwl: { cursorHideTimeout: 0 }, mango: { cursorHideTimeout: 0 } }, onChange: "updateCompositorCursor" },
|
cursorSettings: { def: { theme: "System Default", size: 24, niri: { hideWhenTyping: false, hideAfterInactiveMs: 0 }, hyprland: { hideOnKeyPress: false, hideOnTouch: false, inactiveTimeout: 0 }, dwl: { cursorHideTimeout: 0 }, mango: { cursorHideTimeout: 0 } }, onChange: "updateCompositorCursor" },
|
||||||
availableCursorThemes: { def: ["System Default"], persist: false },
|
availableCursorThemes: { def: ["System Default"], persist: false },
|
||||||
@@ -374,8 +371,6 @@ var SPEC = {
|
|||||||
fadeToDpmsGracePeriod: { def: 5 },
|
fadeToDpmsGracePeriod: { def: 5 },
|
||||||
launchPrefix: { def: "" },
|
launchPrefix: { def: "" },
|
||||||
|
|
||||||
gtkThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
|
||||||
qtThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
|
||||||
syncModeWithPortal: { def: true },
|
syncModeWithPortal: { def: true },
|
||||||
terminalsAlwaysDark: { def: false, onChange: "regenSystemThemes" },
|
terminalsAlwaysDark: { def: false, onChange: "regenSystemThemes" },
|
||||||
|
|
||||||
@@ -473,6 +468,8 @@ var SPEC = {
|
|||||||
enableU2f: { def: false, onChange: "scheduleAuthApply" },
|
enableU2f: { def: false, onChange: "scheduleAuthApply" },
|
||||||
u2fMode: { def: "or" },
|
u2fMode: { def: "or" },
|
||||||
lockPamPath: { def: "" },
|
lockPamPath: { def: "" },
|
||||||
|
lockScreenSecurityKeyShortcut: { def: "Ctrl+Q" },
|
||||||
|
lockScreenSecurityKeyShortcutEnabled: { def: false },
|
||||||
lockPamInlineFprint: { def: false },
|
lockPamInlineFprint: { def: false },
|
||||||
lockPamInlineU2f: { def: false },
|
lockPamInlineU2f: { def: false },
|
||||||
lockPamExternallyManaged: { def: false },
|
lockPamExternallyManaged: { def: false },
|
||||||
|
|||||||
@@ -1015,6 +1015,9 @@ Item {
|
|||||||
case "reboot":
|
case "reboot":
|
||||||
SessionService.reboot();
|
SessionService.reboot();
|
||||||
break;
|
break;
|
||||||
|
case "softreboot":
|
||||||
|
SessionService.softReboot();
|
||||||
|
break;
|
||||||
case "poweroff":
|
case "poweroff":
|
||||||
SessionService.poweroff();
|
SessionService.poweroff();
|
||||||
break;
|
break;
|
||||||
|
|||||||
+14
-14
@@ -2023,18 +2023,6 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
IpcHandler {
|
IpcHandler {
|
||||||
function findTrayItem(itemId: string): var {
|
|
||||||
if (!itemId)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return SystemTray.items.values.find(item => {
|
|
||||||
const id = item?.id || "";
|
|
||||||
const title = item?.tooltipTitle || "";
|
|
||||||
const fullKey = title ? `${id}::${title}` : id;
|
|
||||||
return fullKey === itemId || id === itemId;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function list(): string {
|
function list(): string {
|
||||||
const items = SystemTray.items.values;
|
const items = SystemTray.items.values;
|
||||||
if (items.length === 0)
|
if (items.length === 0)
|
||||||
@@ -2050,7 +2038,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function activate(itemId: string): string {
|
function activate(itemId: string): string {
|
||||||
const item = findTrayItem(itemId);
|
const item = TrayMenuManager.findTrayItem(itemId);
|
||||||
if (!item)
|
if (!item)
|
||||||
return `ERROR: Tray item not found: ${itemId}`;
|
return `ERROR: Tray item not found: ${itemId}`;
|
||||||
|
|
||||||
@@ -2058,8 +2046,20 @@ Item {
|
|||||||
return `SUCCESS: Activated ${itemId}`;
|
return `SUCCESS: Activated ${itemId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function menu(itemId: string): string {
|
||||||
|
const item = TrayMenuManager.findTrayItem(itemId);
|
||||||
|
if (!item)
|
||||||
|
return `ERROR: Tray item not found: ${itemId}`;
|
||||||
|
|
||||||
|
if (!item.hasMenu)
|
||||||
|
return `ERROR: Tray item has no menu: ${itemId}`;
|
||||||
|
|
||||||
|
TrayMenuManager.requestOpenMenu(itemId, BarWidgetService.getFocusedScreenName());
|
||||||
|
return `SUCCESS: Requested menu ${itemId}`;
|
||||||
|
}
|
||||||
|
|
||||||
function status(itemId: string): string {
|
function status(itemId: string): string {
|
||||||
const item = findTrayItem(itemId);
|
const item = TrayMenuManager.findTrayItem(itemId);
|
||||||
if (!item)
|
if (!item)
|
||||||
return `ERROR: Tray item not found: ${itemId}`;
|
return `ERROR: Tray item not found: ${itemId}`;
|
||||||
|
|
||||||
|
|||||||
@@ -819,16 +819,24 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isCategoryFiltered) {
|
if (isCategoryFiltered) {
|
||||||
var rawApps = AppSearchService.getAppsInCategory(appCategory);
|
var categoryPluginId = AppSearchService.getPluginIdForCategory(appCategory);
|
||||||
for (var i = 0; i < rawApps.length; i++) {
|
if (categoryPluginId) {
|
||||||
allItems.push(getOrTransformApp(rawApps[i]));
|
var pluginCategoryItems = getPluginItems(categoryPluginId, "");
|
||||||
}
|
for (var i = 0; i < pluginCategoryItems.length; i++) {
|
||||||
// Also include core apps (DMS Settings etc.) that match this category
|
allItems.push(pluginCategoryItems[i]);
|
||||||
var allCoreApps = AppSearchService.getCoreApps("");
|
}
|
||||||
for (var i = 0; i < allCoreApps.length; i++) {
|
} else {
|
||||||
var coreAppCats = AppSearchService.getCategoriesForApp(allCoreApps[i]);
|
var rawApps = AppSearchService.getAppsInCategory(appCategory);
|
||||||
if (coreAppCats.indexOf(appCategory) !== -1)
|
for (var i = 0; i < rawApps.length; i++) {
|
||||||
allItems.push(transformCoreApp(allCoreApps[i]));
|
allItems.push(getOrTransformApp(rawApps[i]));
|
||||||
|
}
|
||||||
|
// Also include core apps (DMS Settings etc.) that match this category
|
||||||
|
var allCoreApps = AppSearchService.getCoreApps("");
|
||||||
|
for (var i = 0; i < allCoreApps.length; i++) {
|
||||||
|
var coreAppCats = AppSearchService.getCategoriesForApp(allCoreApps[i]);
|
||||||
|
if (coreAppCats.indexOf(appCategory) !== -1)
|
||||||
|
allItems.push(transformCoreApp(allCoreApps[i]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var apps = searchApps(searchQuery);
|
var apps = searchApps(searchQuery);
|
||||||
@@ -839,7 +847,7 @@ Item {
|
|||||||
|
|
||||||
var scoredItems = Scorer.scoreItems(allItems, searchQuery, getFrecencyForItem);
|
var scoredItems = Scorer.scoreItems(allItems, searchQuery, getFrecencyForItem);
|
||||||
var sortAlpha = !searchQuery && SettingsData.sortAppsAlphabetically;
|
var sortAlpha = !searchQuery && SettingsData.sortAppsAlphabetically;
|
||||||
var newSections = Scorer.groupBySection(scoredItems, sectionDefinitions, sortAlpha, searchQuery ? 50 : 500);
|
var newSections = Scorer.groupBySection(scoredItems, buildDynamicSectionDefs(allItems), sortAlpha, searchQuery ? 50 : 500);
|
||||||
|
|
||||||
for (var sid in collapsedSections) {
|
for (var sid in collapsedSections) {
|
||||||
for (var i = 0; i < newSections.length; i++) {
|
for (var i = 0; i < newSections.length; i++) {
|
||||||
|
|||||||
@@ -13,6 +13,17 @@ FocusScope {
|
|||||||
property bool awaitingFprintForPassword: false
|
property bool awaitingFprintForPassword: false
|
||||||
property var windowControls: null
|
property var windowControls: null
|
||||||
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
|
readonly property int inputFieldHeight: Theme.fontSizeMedium + Theme.spacingL * 2
|
||||||
|
readonly property bool _blurActive: Theme.blurForegroundLayers || Theme.transparentBlurLayers
|
||||||
|
readonly property real _fieldAlpha: {
|
||||||
|
if (Theme.transparentBlurLayers)
|
||||||
|
return 0.28;
|
||||||
|
if (Theme.blurForegroundLayers)
|
||||||
|
return Math.max(Theme.popupTransparency, 0.62);
|
||||||
|
return Theme.popupTransparency;
|
||||||
|
}
|
||||||
|
readonly property color _fieldColor: Theme.withAlpha(Theme.surfaceContainerHigh, _fieldAlpha)
|
||||||
|
readonly property color _fieldBorderColor: Theme.withAlpha(Theme.outline, _blurActive ? 0.16 : Theme.layerOutlineOpacity)
|
||||||
|
readonly property color _fieldFocusedBorderColor: Theme.withAlpha(Theme.primary, _blurActive ? 0.72 : 1.0)
|
||||||
|
|
||||||
property string polkitEtcPamText: ""
|
property string polkitEtcPamText: ""
|
||||||
property string polkitLibPamText: ""
|
property string polkitLibPamText: ""
|
||||||
@@ -202,7 +213,7 @@ FocusScope {
|
|||||||
StyledText {
|
StyledText {
|
||||||
text: root.currentFlow?.message ?? ""
|
text: root.currentFlow?.message ?? ""
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.surfaceTextMedium
|
color: Theme.surfaceText
|
||||||
width: parent.width
|
width: parent.width
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
maximumLineCount: 2
|
maximumLineCount: 2
|
||||||
@@ -272,9 +283,10 @@ FocusScope {
|
|||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: root.inputFieldHeight
|
height: root.inputFieldHeight
|
||||||
backgroundColor: Theme.surfaceHover
|
cornerRadius: Theme.cornerRadius
|
||||||
normalBorderColor: Theme.outlineStrong
|
backgroundColor: root._fieldColor
|
||||||
focusedBorderColor: Theme.primary
|
normalBorderColor: root._fieldBorderColor
|
||||||
|
focusedBorderColor: root._fieldFocusedBorderColor
|
||||||
borderWidth: 1
|
borderWidth: 1
|
||||||
focusedBorderWidth: 2
|
focusedBorderWidth: 2
|
||||||
leftIconName: root.polkitPamHasFprint ? "fingerprint" : ""
|
leftIconName: root.polkitPamHasFprint ? "fingerprint" : ""
|
||||||
@@ -352,7 +364,7 @@ FocusScope {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: I18n.tr("Authenticate")
|
text: I18n.tr("Authenticate")
|
||||||
font.pixelSize: Theme.fontSizeMedium
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
color: Theme.background
|
color: Theme.primaryText
|
||||||
font.weight: Font.Medium
|
font.weight: Font.Medium
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ FloatingWindow {
|
|||||||
title: I18n.tr("Authentication")
|
title: I18n.tr("Authentication")
|
||||||
minimumSize: Qt.size(460, 220)
|
minimumSize: Qt.size(460, 220)
|
||||||
maximumSize: Qt.size(460, 220)
|
maximumSize: Qt.size(460, 220)
|
||||||
color: Theme.surfaceContainer
|
color: Theme.floatingWindowSurface
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
onClosed: hide()
|
onClosed: hide()
|
||||||
@@ -53,6 +53,25 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: root
|
||||||
|
blurX: 0
|
||||||
|
blurY: 0
|
||||||
|
blurWidth: root.visible ? root.width : 0
|
||||||
|
blurHeight: root.visible ? root.height : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
border.color: BlurService.borderColor
|
||||||
|
border.width: BlurService.borderWidth
|
||||||
|
antialiasing: true
|
||||||
|
z: 100
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: contentLoader
|
id: contentLoader
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|||||||
@@ -175,6 +175,8 @@ DankModal {
|
|||||||
visibleActions = allActions.filter(action => {
|
visibleActions = allActions.filter(action => {
|
||||||
if (action === "hibernate" && !SessionService.hibernateSupported)
|
if (action === "hibernate" && !SessionService.hibernateSupported)
|
||||||
return false;
|
return false;
|
||||||
|
if (action === "softreboot" && !SessionService.softRebootSupported)
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -223,6 +225,12 @@ DankModal {
|
|||||||
"label": I18n.tr("Reboot"),
|
"label": I18n.tr("Reboot"),
|
||||||
"key": "R"
|
"key": "R"
|
||||||
};
|
};
|
||||||
|
case "softreboot":
|
||||||
|
return {
|
||||||
|
"icon": "autorenew",
|
||||||
|
"label": I18n.tr("Soft Reboot"),
|
||||||
|
"key": "B"
|
||||||
|
};
|
||||||
case "logout":
|
case "logout":
|
||||||
return {
|
return {
|
||||||
"icon": "logout",
|
"icon": "logout",
|
||||||
@@ -370,7 +378,7 @@ DankModal {
|
|||||||
|
|
||||||
function handleListNavigation(event, isPressed) {
|
function handleListNavigation(event, isPressed) {
|
||||||
if (!isPressed) {
|
if (!isPressed) {
|
||||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_X || event.key === Qt.Key_L || event.key === Qt.Key_S || event.key === Qt.Key_H || event.key === Qt.Key_D || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
|
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_B || event.key === Qt.Key_X || event.key === Qt.Key_L || event.key === Qt.Key_S || event.key === Qt.Key_H || event.key === Qt.Key_D || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
|
||||||
cancelHold();
|
cancelHold();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
@@ -429,6 +437,12 @@ DankModal {
|
|||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Qt.Key_B:
|
||||||
|
if (visibleActions.includes("softreboot")) {
|
||||||
|
startHold("softreboot", visibleActions.indexOf("softreboot"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case Qt.Key_X:
|
case Qt.Key_X:
|
||||||
if (visibleActions.includes("logout")) {
|
if (visibleActions.includes("logout")) {
|
||||||
startHold("logout", visibleActions.indexOf("logout"));
|
startHold("logout", visibleActions.indexOf("logout"));
|
||||||
@@ -464,7 +478,7 @@ DankModal {
|
|||||||
|
|
||||||
function handleGridNavigation(event, isPressed) {
|
function handleGridNavigation(event, isPressed) {
|
||||||
if (!isPressed) {
|
if (!isPressed) {
|
||||||
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_X || event.key === Qt.Key_L || event.key === Qt.Key_S || event.key === Qt.Key_H || event.key === Qt.Key_D || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
|
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter || event.key === Qt.Key_R || event.key === Qt.Key_B || event.key === Qt.Key_X || event.key === Qt.Key_L || event.key === Qt.Key_S || event.key === Qt.Key_H || event.key === Qt.Key_D || (event.key === Qt.Key_P && !(event.modifiers & Qt.ControlModifier))) {
|
||||||
cancelHold();
|
cancelHold();
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
@@ -539,6 +553,12 @@ DankModal {
|
|||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Qt.Key_B:
|
||||||
|
if (visibleActions.includes("softreboot")) {
|
||||||
|
startHold("softreboot", visibleActions.indexOf("softreboot"));
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case Qt.Key_X:
|
case Qt.Key_X:
|
||||||
if (visibleActions.includes("logout")) {
|
if (visibleActions.includes("logout")) {
|
||||||
startHold("logout", visibleActions.indexOf("logout"));
|
startHold("logout", visibleActions.indexOf("logout"));
|
||||||
@@ -597,7 +617,7 @@ DankModal {
|
|||||||
|
|
||||||
readonly property var actionData: root.getActionData(modelData)
|
readonly property var actionData: root.getActionData(modelData)
|
||||||
readonly property bool isSelected: root.selectedIndex === index
|
readonly property bool isSelected: root.selectedIndex === index
|
||||||
readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff"
|
readonly property bool showWarning: modelData === "reboot" || modelData === "softreboot" || modelData === "poweroff"
|
||||||
readonly property bool isHolding: root.holdActionIndex === index && root.holdProgress > 0
|
readonly property bool isHolding: root.holdActionIndex === index && root.holdProgress > 0
|
||||||
|
|
||||||
width: (root.modalWidth - Theme.spacingL * 2 - Theme.spacingS * (root.gridColumns - 1)) / root.gridColumns
|
width: (root.modalWidth - Theme.spacingL * 2 - Theme.spacingS * (root.gridColumns - 1)) / root.gridColumns
|
||||||
@@ -627,7 +647,7 @@ DankModal {
|
|||||||
color: {
|
color: {
|
||||||
if (gridButtonRect.modelData === "poweroff")
|
if (gridButtonRect.modelData === "poweroff")
|
||||||
return Theme.errorSelected;
|
return Theme.errorSelected;
|
||||||
if (gridButtonRect.modelData === "reboot")
|
if (gridButtonRect.modelData === "reboot" || gridButtonRect.modelData === "softreboot")
|
||||||
return Theme.withAlpha(Theme.warning, 0.3);
|
return Theme.withAlpha(Theme.warning, 0.3);
|
||||||
return Theme.primarySelected;
|
return Theme.primarySelected;
|
||||||
}
|
}
|
||||||
@@ -722,7 +742,7 @@ DankModal {
|
|||||||
|
|
||||||
readonly property var actionData: root.getActionData(modelData)
|
readonly property var actionData: root.getActionData(modelData)
|
||||||
readonly property bool isSelected: root.selectedIndex === index
|
readonly property bool isSelected: root.selectedIndex === index
|
||||||
readonly property bool showWarning: modelData === "reboot" || modelData === "poweroff"
|
readonly property bool showWarning: modelData === "reboot" || modelData === "softreboot" || modelData === "poweroff"
|
||||||
readonly property bool isHolding: root.holdActionIndex === index && root.holdProgress > 0
|
readonly property bool isHolding: root.holdActionIndex === index && root.holdProgress > 0
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
@@ -752,7 +772,7 @@ DankModal {
|
|||||||
color: {
|
color: {
|
||||||
if (listButtonRect.modelData === "poweroff")
|
if (listButtonRect.modelData === "poweroff")
|
||||||
return Theme.errorSelected;
|
return Theme.errorSelected;
|
||||||
if (listButtonRect.modelData === "reboot")
|
if (listButtonRect.modelData === "reboot" || listButtonRect.modelData === "softreboot")
|
||||||
return Theme.withAlpha(Theme.warning, 0.3);
|
return Theme.withAlpha(Theme.warning, 0.3);
|
||||||
return Theme.primarySelected;
|
return Theme.primarySelected;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import qs.Widgets
|
|||||||
FloatingWindow {
|
FloatingWindow {
|
||||||
id: settingsModal
|
id: settingsModal
|
||||||
|
|
||||||
|
property bool disablePopupTransparency: true
|
||||||
property var profileBrowser: profileBrowserLoader.item
|
property var profileBrowser: profileBrowserLoader.item
|
||||||
property var wallpaperBrowser: wallpaperBrowserLoader.item
|
property var wallpaperBrowser: wallpaperBrowserLoader.item
|
||||||
|
|
||||||
@@ -95,7 +96,7 @@ FloatingWindow {
|
|||||||
minimumSize: Qt.size(500, 400)
|
minimumSize: Qt.size(500, 400)
|
||||||
implicitWidth: 900
|
implicitWidth: 900
|
||||||
implicitHeight: screen ? Math.min(940, screen.height - 100) : 940
|
implicitHeight: screen ? Math.min(940, screen.height - 100) : 940
|
||||||
color: Theme.surfaceContainer
|
color: Theme.floatingWindowSurface
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
onClosed: hide()
|
onClosed: hide()
|
||||||
@@ -120,6 +121,25 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: settingsModal
|
||||||
|
blurX: 0
|
||||||
|
blurY: 0
|
||||||
|
blurWidth: settingsModal.visible ? settingsModal.width : 0
|
||||||
|
blurHeight: settingsModal.visible ? settingsModal.height : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
border.color: BlurService.borderColor
|
||||||
|
border.width: BlurService.borderWidth
|
||||||
|
antialiasing: true
|
||||||
|
z: 100
|
||||||
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
active: settingsModal.visible
|
active: settingsModal.visible
|
||||||
sourceComponent: Component {
|
sourceComponent: Component {
|
||||||
@@ -180,8 +200,6 @@ FloatingWindow {
|
|||||||
FocusScope {
|
FocusScope {
|
||||||
id: contentFocusScope
|
id: contentFocusScope
|
||||||
|
|
||||||
property bool disablePopupTransparency: true
|
|
||||||
|
|
||||||
LayoutMirroring.enabled: I18n.isRtl
|
LayoutMirroring.enabled: I18n.isRtl
|
||||||
LayoutMirroring.childrenInherit: true
|
LayoutMirroring.childrenInherit: true
|
||||||
|
|
||||||
@@ -203,12 +221,6 @@ FloatingWindow {
|
|||||||
onDoubleClicked: windowControls.tryToggleMaximize()
|
onDoubleClicked: windowControls.tryToggleMaximize()
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: Theme.surfaceContainer
|
|
||||||
opacity: 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.spacingL
|
anchors.leftMargin: Theme.spacingL
|
||||||
|
|||||||
@@ -641,7 +641,7 @@ Rectangle {
|
|||||||
implicitWidth: __calculatedWidth
|
implicitWidth: __calculatedWidth
|
||||||
width: __calculatedWidth
|
width: __calculatedWidth
|
||||||
height: parent.height
|
height: parent.height
|
||||||
color: Theme.surfaceContainer
|
color: "transparent"
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
|||||||
@@ -201,7 +201,16 @@ Item {
|
|||||||
direction: root.effectiveShadowDirection
|
direction: root.effectiveShadowDirection
|
||||||
fallbackOffset: 4
|
fallbackOffset: 4
|
||||||
targetRadius: root.rt
|
targetRadius: root.rt
|
||||||
targetColor: barWindow._bgColor
|
// wing-side body corners are square where the gothic fillets attach;
|
||||||
|
// rounding the shadow there leaves a shadow-filled notch under the
|
||||||
|
// translucent bar fill (#2975)
|
||||||
|
topLeftRadius: root.gothEnabled && (root.isBottom || root.isRight) ? 0 : root.rt
|
||||||
|
topRightRadius: root.gothEnabled && (root.isBottom || root.isLeft) ? 0 : root.rt
|
||||||
|
bottomLeftRadius: root.gothEnabled && (root.isTop || root.isRight) ? 0 : root.rt
|
||||||
|
bottomRightRadius: root.gothEnabled && (root.isTop || root.isLeft) ? 0 : root.rt
|
||||||
|
// barShape below is the sole painter of the bar; a fill here doubles
|
||||||
|
// the alpha of translucent bars while the wings stay single-painted
|
||||||
|
targetColor: "transparent"
|
||||||
|
|
||||||
shadowBlurPx: root.shadowBlurPx
|
shadowBlurPx: root.shadowBlurPx
|
||||||
shadowOffsetX: root.shadowOffsetX
|
shadowOffsetX: root.shadowOffsetX
|
||||||
|
|||||||
@@ -609,7 +609,7 @@ Item {
|
|||||||
_closeHoverNotepad();
|
_closeHoverNotepad();
|
||||||
activeHoverTrigger = "";
|
activeHoverTrigger = "";
|
||||||
PopoutManager.dismissHoverPopoutForScreen(barWindow?.screen);
|
PopoutManager.dismissHoverPopoutForScreen(barWindow?.screen);
|
||||||
TrayMenuManager.closeAllMenus();
|
TrayMenuManager.closeHoverMenus();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _beginSupersededCloseForActive() {
|
function _beginSupersededCloseForActive() {
|
||||||
|
|||||||
@@ -83,6 +83,22 @@ BasePill {
|
|||||||
root.showForTrayItem(trayItem, anchorItem, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
root.showForTrayItem(trayItem, anchorItem, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: TrayMenuManager
|
||||||
|
|
||||||
|
function onOpenTrayMenuRequested() {
|
||||||
|
const request = TrayMenuManager.claimMenuRequest(root.parentScreen?.name);
|
||||||
|
if (!request)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const item = TrayMenuManager.findTrayItem(request.itemId);
|
||||||
|
if (!item || !item.hasMenu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
root.showForTrayItem(item, root, parentScreen, root.isAtBottom, root.isVerticalOrientation, root.axis);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function openInlineTrayContextMenu(trayItem, areaItem, mouse, anchorItem) {
|
function openInlineTrayContextMenu(trayItem, areaItem, mouse, anchorItem) {
|
||||||
if (!trayItem) {
|
if (!trayItem) {
|
||||||
return;
|
return;
|
||||||
@@ -1484,6 +1500,7 @@ BasePill {
|
|||||||
property bool isVertical: false
|
property bool isVertical: false
|
||||||
property var axis: null
|
property var axis: null
|
||||||
property bool showMenu: false
|
property bool showMenu: false
|
||||||
|
property bool openedByHover: false
|
||||||
property var menuHandle: null
|
property var menuHandle: null
|
||||||
|
|
||||||
ListModel {
|
ListModel {
|
||||||
@@ -1493,7 +1510,8 @@ BasePill {
|
|||||||
return entryStack.count ? entryStack.get(entryStack.count - 1).handle : null;
|
return entryStack.count ? entryStack.get(entryStack.count - 1).handle : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
|
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj, byHover) {
|
||||||
|
openedByHover = byHover === true;
|
||||||
trayItem = item;
|
trayItem = item;
|
||||||
anchorItem = anchor;
|
anchorItem = anchor;
|
||||||
parentScreen = screen;
|
parentScreen = screen;
|
||||||
@@ -2084,7 +2102,7 @@ BasePill {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj) {
|
function showForTrayItem(item, anchor, screen, atBottom, vertical, axisObj, byHover) {
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return;
|
return;
|
||||||
if (currentTrayMenu) {
|
if (currentTrayMenu) {
|
||||||
@@ -2099,7 +2117,7 @@ BasePill {
|
|||||||
currentTrayMenu = trayMenuComponent.createObject(null);
|
currentTrayMenu = trayMenuComponent.createObject(null);
|
||||||
if (!currentTrayMenu)
|
if (!currentTrayMenu)
|
||||||
return;
|
return;
|
||||||
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj);
|
currentTrayMenu.showForTrayItem(item, anchor, screen, atBottom, vertical ?? false, axisObj, byHover === true);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _trayLayoutRoot() {
|
function _trayLayoutRoot() {
|
||||||
@@ -2147,7 +2165,7 @@ BasePill {
|
|||||||
if (!hit?.trayItem?.hasMenu)
|
if (!hit?.trayItem?.hasMenu)
|
||||||
return false;
|
return false;
|
||||||
const anchor = hit.children?.length > 0 ? hit.children[0] : hit;
|
const anchor = hit.children?.length > 0 ? hit.children[0] : hit;
|
||||||
showForTrayItem(hit.trayItem, anchor, parentScreen, isAtBottom, isVerticalOrientation, axis);
|
showForTrayItem(hit.trayItem, anchor, parentScreen, isAtBottom, isVerticalOrientation, axis, true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -493,7 +493,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
text: activePlayer?.trackAlbum || ""
|
text: MprisController.stableAlbum
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
color: Theme.surfaceTextSecondary
|
color: Theme.surfaceTextSecondary
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import qs.Services
|
|||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
import qs.DankCommon.Session
|
import qs.DankCommon.Session
|
||||||
import "../../DankCommon/Common/LayoutCodes.js" as LayoutCodes
|
import "../../DankCommon/Common/LayoutCodes.js" as LayoutCodes
|
||||||
|
import "../../Common/KeyUtils.js" as KeyUtils
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -98,6 +99,18 @@ Item {
|
|||||||
return !demoMode && pam && pam.u2f && pam.u2f.available && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !pam.passwd.active && !pam.u2f.active && !pam.u2fPending && !root.unlocking;
|
return !demoMode && pam && pam.u2f && pam.u2f.available && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !pam.passwd.active && !pam.u2f.active && !pam.u2fPending && !root.unlocking;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function triggerSecurityKeyUnlock() {
|
||||||
|
if (!canStartSecurityKeyUnlock())
|
||||||
|
return;
|
||||||
|
passwordField.clear();
|
||||||
|
pam.u2f.startForAlternativeAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
function securityKeyShortcutMatches(event) {
|
||||||
|
return SettingsData.lockScreenSecurityKeyShortcutEnabled
|
||||||
|
&& KeyUtils.eventMatchesCombo(event, SettingsData.lockScreenSecurityKeyShortcut);
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
WeatherService.addRef();
|
WeatherService.addRef();
|
||||||
UserInfoService.getUserInfo();
|
UserInfoService.getUserInfo();
|
||||||
@@ -962,6 +975,12 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((event.modifiers & Qt.ControlModifier) && !(event.modifiers & (Qt.AltModifier | Qt.MetaModifier))) {
|
if ((event.modifiers & Qt.ControlModifier) && !(event.modifiers & (Qt.AltModifier | Qt.MetaModifier))) {
|
||||||
|
if (securityKeyShortcutMatches(event) && canStartSecurityKeyUnlock()) {
|
||||||
|
triggerSecurityKeyUnlock();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (event.key) {
|
switch (event.key) {
|
||||||
case Qt.Key_A:
|
case Qt.Key_A:
|
||||||
cursorPosition = 0;
|
cursorPosition = 0;
|
||||||
@@ -1238,10 +1257,8 @@ Item {
|
|||||||
buttonSize: 32
|
buttonSize: 32
|
||||||
visible: root.canStartSecurityKeyUnlock()
|
visible: root.canStartSecurityKeyUnlock()
|
||||||
enabled: visible
|
enabled: visible
|
||||||
onClicked: {
|
tooltipText: SettingsData.lockScreenSecurityKeyShortcutEnabled ? I18n.tr("Security key (%1)", "lock screen security key button tooltip with shortcut").arg(SettingsData.lockScreenSecurityKeyShortcut) : I18n.tr("Security key", "lock screen security key button tooltip")
|
||||||
passwordField.clear();
|
onClicked: root.triggerSecurityKeyUnlock()
|
||||||
pam.u2f.startForAlternativeAuth();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
DankActionButton {
|
DankActionButton {
|
||||||
id: virtualKeyboardButton
|
id: virtualKeyboardButton
|
||||||
@@ -1329,6 +1346,7 @@ Item {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height / 2
|
height: parent.height / 2
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: -1
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
color: Theme.withAlpha(Theme.surfaceContainer, 0.9)
|
color: Theme.withAlpha(Theme.surfaceContainer, 0.9)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import qs.Modules.Notepad
|
|||||||
FloatingWindow {
|
FloatingWindow {
|
||||||
id: win
|
id: win
|
||||||
|
|
||||||
|
property bool disablePopupTransparency: true
|
||||||
property alias shouldBeVisible: win.visible
|
property alias shouldBeVisible: win.visible
|
||||||
property alias notepad: notepad
|
property alias notepad: notepad
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ FloatingWindow {
|
|||||||
minimumSize: Qt.size(360, 320)
|
minimumSize: Qt.size(360, 320)
|
||||||
implicitWidth: 640
|
implicitWidth: 640
|
||||||
implicitHeight: 760
|
implicitHeight: 760
|
||||||
color: Theme.surfaceContainer
|
color: Theme.notepadWindowSurface
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
@@ -38,9 +39,27 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A compositor close (e.g. niri close-window)
|
|
||||||
onClosed: win.visible = false
|
onClosed: win.visible = false
|
||||||
|
|
||||||
|
WindowBlur {
|
||||||
|
targetWindow: win
|
||||||
|
blurX: 0
|
||||||
|
blurY: 0
|
||||||
|
blurWidth: win.visible ? win.width : 0
|
||||||
|
blurHeight: win.visible ? win.height : 0
|
||||||
|
blurRadius: Theme.cornerRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
radius: Theme.cornerRadius
|
||||||
|
color: "transparent"
|
||||||
|
border.color: BlurService.borderColor
|
||||||
|
border.width: BlurService.borderWidth
|
||||||
|
antialiasing: true
|
||||||
|
z: 100
|
||||||
|
}
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
@@ -58,12 +77,6 @@ FloatingWindow {
|
|||||||
onDoubleClicked: windowControls.tryToggleMaximize()
|
onDoubleClicked: windowControls.tryToggleMaximize()
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
color: Theme.surfaceContainerHigh
|
|
||||||
opacity: 0.5
|
|
||||||
}
|
|
||||||
|
|
||||||
Row {
|
Row {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: Theme.spacingM
|
anchors.leftMargin: Theme.spacingM
|
||||||
|
|||||||
@@ -156,9 +156,10 @@ DankOSD {
|
|||||||
if (MprisController.isFirefoxYoutubeHoverPreview(player))
|
if (MprisController.isFirefoxYoutubeHoverPreview(player))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const newTitle = player.trackTitle || "";
|
const metaPlayer = MprisController.bestMetadataPlayer(player);
|
||||||
const newArtist = player.trackArtist || "";
|
const newTitle = MprisController.displayTrackTitle(metaPlayer);
|
||||||
const newAlbum = player.trackAlbum || "";
|
const newArtist = metaPlayer.trackArtist || "";
|
||||||
|
const newAlbum = metaPlayer.trackAlbum || "";
|
||||||
const trackChanged = newTitle !== root._displayTitle || newArtist !== root._displayArtist || newAlbum !== root._displayAlbum;
|
const trackChanged = newTitle !== root._displayTitle || newArtist !== root._displayArtist || newAlbum !== root._displayAlbum;
|
||||||
|
|
||||||
root._displayTitle = newTitle;
|
root._displayTitle = newTitle;
|
||||||
@@ -263,37 +264,102 @@ DankOSD {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Row {
|
||||||
width: Theme.iconSize
|
id: transportControls
|
||||||
height: Theme.iconSize
|
|
||||||
radius: Theme.iconSize / 2
|
|
||||||
color: "transparent"
|
|
||||||
x: parent.gap
|
x: parent.gap
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingXXS
|
||||||
|
|
||||||
DankIcon {
|
Rectangle {
|
||||||
anchors.centerIn: parent
|
width: Theme.iconSize - 4
|
||||||
name: root._displayIcon
|
height: Theme.iconSize - 4
|
||||||
size: Theme.iconSize
|
radius: (Theme.iconSize - 4) / 2
|
||||||
color: playPauseButton.containsMouse ? Theme.primary : Theme.surfaceText
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: prevButton.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||||
|
opacity: (root.player?.canGoPrevious ?? false) ? 1 : 0.3
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "skip_previous"
|
||||||
|
size: Theme.iconSize - 10
|
||||||
|
color: prevButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: prevButton
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
enabled: root.player?.canGoPrevious ?? false
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
MprisController.previousOrRewind();
|
||||||
|
root.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
Rectangle {
|
||||||
id: playPauseButton
|
width: Theme.iconSize
|
||||||
|
height: Theme.iconSize
|
||||||
|
radius: Theme.iconSize / 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
anchors.fill: parent
|
DankIcon {
|
||||||
hoverEnabled: true
|
anchors.centerIn: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
name: root._displayIcon
|
||||||
onClicked: {
|
size: Theme.iconSize
|
||||||
togglePlaying();
|
color: playPauseButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||||
root.hide();
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: playPauseButton
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
togglePlaying();
|
||||||
|
root.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: Theme.iconSize - 4
|
||||||
|
height: Theme.iconSize - 4
|
||||||
|
radius: (Theme.iconSize - 4) / 2
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
color: nextButton.containsMouse ? Theme.surfaceTextHover : "transparent"
|
||||||
|
opacity: (root.player?.canGoNext ?? false) ? 1 : 0.3
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
name: "skip_next"
|
||||||
|
size: Theme.iconSize - 10
|
||||||
|
color: nextButton.containsMouse ? Theme.primary : Theme.surfaceText
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: nextButton
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
hoverEnabled: true
|
||||||
|
enabled: root.player?.canGoNext ?? false
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
MprisController.next();
|
||||||
|
root.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
x: parent.gap * 2 + Theme.iconSize
|
x: parent.gap * 2 + transportControls.width
|
||||||
width: parent.width - Theme.iconSize - parent.gap * 3
|
width: parent.width - transportControls.width - parent.gap * 3
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: Theme.spacingXXS
|
spacing: Theme.spacingXXS
|
||||||
|
|
||||||
|
|||||||
@@ -1111,15 +1111,50 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractNiriOutputBlocks(content) {
|
||||||
|
const blocks = [];
|
||||||
|
const headerRegex = /output\s+"([^"]+)"\s*\{/g;
|
||||||
|
let match;
|
||||||
|
while ((match = headerRegex.exec(content)) !== null) {
|
||||||
|
const start = headerRegex.lastIndex;
|
||||||
|
let depth = 1;
|
||||||
|
let i = start;
|
||||||
|
while (i < content.length && depth > 0) {
|
||||||
|
const ch = content[i];
|
||||||
|
if (ch === '{')
|
||||||
|
depth++;
|
||||||
|
else if (ch === '}')
|
||||||
|
depth--;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
blocks.push({
|
||||||
|
"name": match[1],
|
||||||
|
"body": content.slice(start, i - 1)
|
||||||
|
});
|
||||||
|
headerRegex.lastIndex = i;
|
||||||
|
}
|
||||||
|
return blocks;
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripNestedBlocks(body) {
|
||||||
|
let stripped = body;
|
||||||
|
let prev;
|
||||||
|
do {
|
||||||
|
prev = stripped;
|
||||||
|
stripped = stripped.replace(/[\w-]+\s*\{[^{}]*\}/g, "");
|
||||||
|
} while (stripped !== prev)
|
||||||
|
return stripped;
|
||||||
|
}
|
||||||
|
|
||||||
function parseNiriOutputs(content) {
|
function parseNiriOutputs(content) {
|
||||||
const result = {};
|
const result = {};
|
||||||
const outputRegex = /output\s+"([^"]+)"\s*\{([^}]*)\}/g;
|
for (const block of extractNiriOutputBlocks(content)) {
|
||||||
let match;
|
const name = block.name;
|
||||||
while ((match = outputRegex.exec(content)) !== null) {
|
const body = block.body;
|
||||||
const name = match[1];
|
|
||||||
const body = match[2];
|
|
||||||
|
|
||||||
const disabled = /^\s*off\s*$/m.test(body);
|
// off marks the output disabled only at the top level of its block;
|
||||||
|
// nested sections like hot-corners { off } must not count (#2966)
|
||||||
|
const disabled = /^\s*off\s*$/m.test(stripNestedBlocks(body));
|
||||||
const modeMatch = body.match(/mode\s+"(\d+)x(\d+)@([\d.]+)"/);
|
const modeMatch = body.match(/mode\s+"(\d+)x(\d+)@([\d.]+)"/);
|
||||||
const posMatch = body.match(/position\s+x=(-?\d+)\s+y=(-?\d+)/);
|
const posMatch = body.match(/position\s+x=(-?\d+)\s+y=(-?\d+)/);
|
||||||
const scaleMatch = body.match(/scale\s+([\d.]+)/);
|
const scaleMatch = body.match(/scale\s+([\d.]+)/);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import qs.Modals.FileBrowser
|
|||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
import qs.Modules.Settings.Widgets
|
import qs.Modules.Settings.Widgets
|
||||||
|
import "../../Common/KeyUtils.js" as KeyUtils
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
@@ -518,6 +519,117 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
settingKey: "lockScreenSecurityKeyShortcutEnabled"
|
||||||
|
tags: ["lock", "screen", "u2f", "yubikey", "security", "key", "shortcut", "keybind", "authentication"]
|
||||||
|
text: I18n.tr("Security key shortcut", "lock screen security key shortcut toggle")
|
||||||
|
description: I18n.tr("Keyboard shortcut to start security key unlock", "lock screen security key shortcut setting")
|
||||||
|
checked: SettingsData.lockScreenSecurityKeyShortcutEnabled
|
||||||
|
visible: SettingsData.enableU2f && SettingsData.u2fMode === "or" && !root.lockU2fControlledByPrimary
|
||||||
|
onToggled: checked => SettingsData.set("lockScreenSecurityKeyShortcutEnabled", checked)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
x: Theme.spacingM
|
||||||
|
spacing: Theme.spacingM
|
||||||
|
visible: SettingsData.lockScreenSecurityKeyShortcutEnabled && SettingsData.enableU2f && SettingsData.u2fMode === "or" && !root.lockU2fControlledByPrimary
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width - securityKeyCapture.width - parent.spacing
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
spacing: Theme.spacingXS
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: I18n.tr("Key combination", "lock screen security key shortcut key combination setting")
|
||||||
|
font.pixelSize: Theme.fontSizeMedium
|
||||||
|
font.weight: Font.Medium
|
||||||
|
color: Theme.surfaceText
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
text: securityKeyCapture.captureError !== "" ? securityKeyCapture.captureError : I18n.tr("Press Ctrl+key to set. Esc cancels.", "lock screen security key shortcut key combination capture hint")
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
color: securityKeyCapture.captureError !== "" ? Theme.warning : Theme.surfaceVariantText
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: Text.AlignLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DankButton {
|
||||||
|
id: securityKeyCapture
|
||||||
|
width: 200
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
focus: capturing
|
||||||
|
text: capturing ? I18n.tr("Press key...", "lock screen security key shortcut key combination capture prompt") : SettingsData.lockScreenSecurityKeyShortcut
|
||||||
|
backgroundColor: capturing ? Theme.primaryContainer : Theme.surfaceContainer
|
||||||
|
textColor: Theme.surfaceText
|
||||||
|
|
||||||
|
property bool capturing: false
|
||||||
|
property string captureError: ""
|
||||||
|
readonly property var reservedKeys: ["A", "E", "B", "F", "U", "K", "W", "H", "D"]
|
||||||
|
|
||||||
|
function startCapture() {
|
||||||
|
captureError = "";
|
||||||
|
capturing = true;
|
||||||
|
securityKeyCapture.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopCapture() {
|
||||||
|
capturing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (capturing)
|
||||||
|
stopCapture();
|
||||||
|
else
|
||||||
|
startCapture();
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: event => {
|
||||||
|
if (!securityKeyCapture.capturing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (KeyUtils.isModifierKey(event.key))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (event.key === Qt.Key_Escape) {
|
||||||
|
securityKeyCapture.stopCapture();
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mods = KeyUtils.modsFromEvent(event.modifiers);
|
||||||
|
const hasCtrl = mods.includes("Ctrl");
|
||||||
|
const hasAlt = mods.includes("Alt") || mods.includes("Super");
|
||||||
|
if (!hasCtrl || hasAlt)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const hasShift = mods.includes("Shift");
|
||||||
|
const key = KeyUtils.xkbKeyFromQtKey(event.key, !!(event.modifiers & Qt.KeypadModifier), hasShift);
|
||||||
|
if (!key)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!KeyUtils.qtKeyFromName(key))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!hasShift && securityKeyCapture.reservedKeys.indexOf(key.toUpperCase()) !== -1) {
|
||||||
|
securityKeyCapture.captureError = I18n.tr("Ctrl+%1 is used for password editing", "lock screen security key shortcut reserved key warning").arg(key.toUpperCase());
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SettingsData.set("lockScreenSecurityKeyShortcut", KeyUtils.formatToken(mods, key));
|
||||||
|
securityKeyCapture.captureError = "";
|
||||||
|
securityKeyCapture.stopCapture();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsDropdownRow {
|
SettingsDropdownRow {
|
||||||
settingKey: "lockU2fPamPath"
|
settingKey: "lockU2fPamPath"
|
||||||
tags: ["lock", "screen", "pam", "u2f", "security", "key", "source", "service"]
|
tags: ["lock", "screen", "pam", "u2f", "security", "key", "source", "service"]
|
||||||
|
|||||||
@@ -410,8 +410,8 @@ Item {
|
|||||||
settingKey: "powerMenuDefaultAction"
|
settingKey: "powerMenuDefaultAction"
|
||||||
tags: ["power", "menu", "default", "action", "reboot", "logout", "shutdown"]
|
tags: ["power", "menu", "default", "action", "reboot", "logout", "shutdown"]
|
||||||
text: I18n.tr("Default selected action")
|
text: I18n.tr("Default selected action")
|
||||||
options: [I18n.tr("Reboot"), I18n.tr("Log Out"), I18n.tr("Power Off"), I18n.tr("Lock"), I18n.tr("Suspend"), I18n.tr("Restart DMS"), I18n.tr("Hibernate")]
|
options: [I18n.tr("Reboot"), I18n.tr("Log Out"), I18n.tr("Power Off"), I18n.tr("Lock"), I18n.tr("Suspend"), I18n.tr("Restart DMS"), I18n.tr("Hibernate"), I18n.tr("Soft Reboot")]
|
||||||
property var actionValues: ["reboot", "logout", "poweroff", "lock", "suspend", "restart", "hibernate"]
|
property var actionValues: ["reboot", "logout", "poweroff", "lock", "suspend", "restart", "hibernate", "softreboot"]
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
const currentAction = SettingsData.powerMenuDefaultAction || "logout";
|
const currentAction = SettingsData.powerMenuDefaultAction || "logout";
|
||||||
@@ -475,6 +475,12 @@ Item {
|
|||||||
label: I18n.tr("Show Hibernate"),
|
label: I18n.tr("Show Hibernate"),
|
||||||
desc: I18n.tr("Only visible if hibernate is supported by your system"),
|
desc: I18n.tr("Only visible if hibernate is supported by your system"),
|
||||||
hibernate: true
|
hibernate: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "softreboot",
|
||||||
|
label: I18n.tr("Show Soft Reboot"),
|
||||||
|
desc: I18n.tr("Restart userspace without rebooting the kernel, requires systemd"),
|
||||||
|
softreboot: true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -484,7 +490,13 @@ Item {
|
|||||||
tags: ["power", "menu", "action", "show", modelData.key]
|
tags: ["power", "menu", "action", "show", modelData.key]
|
||||||
text: modelData.label
|
text: modelData.label
|
||||||
description: modelData.desc || ""
|
description: modelData.desc || ""
|
||||||
visible: !modelData.hibernate || SessionService.hibernateSupported
|
visible: {
|
||||||
|
if (modelData.hibernate)
|
||||||
|
return SessionService.hibernateSupported;
|
||||||
|
if (modelData.softreboot)
|
||||||
|
return SessionService.softRebootSupported;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
checked: SettingsData.powerMenuActions.includes(modelData.key)
|
checked: SettingsData.powerMenuActions.includes(modelData.key)
|
||||||
onToggled: checked => {
|
onToggled: checked => {
|
||||||
let actions = [...SettingsData.powerMenuActions];
|
let actions = [...SettingsData.powerMenuActions];
|
||||||
|
|||||||
@@ -1728,7 +1728,7 @@ Item {
|
|||||||
tags: ["surface", "popup", "transparency", "opacity", "modal"]
|
tags: ["surface", "popup", "transparency", "opacity", "modal"]
|
||||||
settingKey: "popupTransparency"
|
settingKey: "popupTransparency"
|
||||||
text: I18n.tr("Surface Opacity")
|
text: I18n.tr("Surface Opacity")
|
||||||
description: I18n.tr("Controls opacity of shell surfaces, popouts, and modals")
|
description: I18n.tr("Controls opacity of shell surfaces, popouts, modals, and floating windows", "Surface Opacity setting description including floating DMS windows")
|
||||||
visible: !themeColorsTab.connectedFrameModeActive
|
visible: !themeColorsTab.connectedFrameModeActive
|
||||||
value: Math.round(SettingsData.popupTransparency * 100)
|
value: Math.round(SettingsData.popupTransparency * 100)
|
||||||
minimum: 0
|
minimum: 0
|
||||||
|
|||||||
@@ -977,6 +977,33 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SettingsToggleRow {
|
||||||
|
id: randomToggle
|
||||||
|
tab: "wallpaper"
|
||||||
|
tags: ["cycling", "automatic", "random", "shuffle"]
|
||||||
|
settingKey: "wallpaperCyclingRandom"
|
||||||
|
width: parent.width - Theme.spacingM * 2
|
||||||
|
text: I18n.tr("Random Order")
|
||||||
|
description: I18n.tr("Select a random wallpaper instead of cycling in alphabetical order")
|
||||||
|
checked: SessionData.perMonitorWallpaper ? SessionData.getMonitorCyclingSettings(selectedMonitorName).random : SessionData.wallpaperCyclingRandom
|
||||||
|
onToggled: toggled => {
|
||||||
|
if (SessionData.perMonitorWallpaper) {
|
||||||
|
SessionData.setMonitorCyclingRandom(selectedMonitorName, toggled);
|
||||||
|
} else {
|
||||||
|
SessionData.setWallpaperCyclingRandom(toggled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onSelectedMonitorNameChanged() {
|
||||||
|
randomToggle.checked = Qt.binding(() => {
|
||||||
|
return SessionData.perMonitorWallpaper ? SessionData.getMonitorCyclingSettings(selectedMonitorName).random : SessionData.wallpaperCyclingRandom;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
SettingsDropdownRow {
|
SettingsDropdownRow {
|
||||||
id: intervalDropdown
|
id: intervalDropdown
|
||||||
property var intervalOptions: [I18n.tr("5 seconds", "wallpaper interval"), I18n.tr("10 seconds", "wallpaper interval"), I18n.tr("15 seconds", "wallpaper interval"), I18n.tr("20 seconds", "wallpaper interval"), I18n.tr("25 seconds", "wallpaper interval"), I18n.tr("30 seconds", "wallpaper interval"), I18n.tr("35 seconds", "wallpaper interval"), I18n.tr("40 seconds", "wallpaper interval"), I18n.tr("45 seconds", "wallpaper interval"), I18n.tr("50 seconds", "wallpaper interval"), I18n.tr("55 seconds", "wallpaper interval"), I18n.tr("1 minute", "wallpaper interval"), I18n.tr("5 minutes", "wallpaper interval"), I18n.tr("15 minutes", "wallpaper interval"), I18n.tr("30 minutes", "wallpaper interval"), I18n.tr("1 hour", "wallpaper interval"), I18n.tr("1 hour 30 minutes", "wallpaper interval"), I18n.tr("2 hours", "wallpaper interval"), I18n.tr("3 hours", "wallpaper interval"), I18n.tr("4 hours", "wallpaper interval"), I18n.tr("6 hours", "wallpaper interval"), I18n.tr("8 hours", "wallpaper interval"), I18n.tr("12 hours", "wallpaper interval")]
|
property var intervalOptions: [I18n.tr("5 seconds", "wallpaper interval"), I18n.tr("10 seconds", "wallpaper interval"), I18n.tr("15 seconds", "wallpaper interval"), I18n.tr("20 seconds", "wallpaper interval"), I18n.tr("25 seconds", "wallpaper interval"), I18n.tr("30 seconds", "wallpaper interval"), I18n.tr("35 seconds", "wallpaper interval"), I18n.tr("40 seconds", "wallpaper interval"), I18n.tr("45 seconds", "wallpaper interval"), I18n.tr("50 seconds", "wallpaper interval"), I18n.tr("55 seconds", "wallpaper interval"), I18n.tr("1 minute", "wallpaper interval"), I18n.tr("5 minutes", "wallpaper interval"), I18n.tr("15 minutes", "wallpaper interval"), I18n.tr("30 minutes", "wallpaper interval"), I18n.tr("1 hour", "wallpaper interval"), I18n.tr("1 hour 30 minutes", "wallpaper interval"), I18n.tr("2 hours", "wallpaper interval"), I18n.tr("3 hours", "wallpaper interval"), I18n.tr("4 hours", "wallpaper interval"), I18n.tr("6 hours", "wallpaper interval"), I18n.tr("8 hours", "wallpaper interval"), I18n.tr("12 hours", "wallpaper interval")]
|
||||||
|
|||||||
@@ -38,7 +38,9 @@ StyledRect {
|
|||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceContainerHigh
|
color: Theme.nestedSurface
|
||||||
|
border.color: Theme.outlineMedium
|
||||||
|
border.width: Theme.layerOutlineWidth
|
||||||
|
|
||||||
readonly property bool collapsed: collapsible && !expanded
|
readonly property bool collapsed: collapsible && !expanded
|
||||||
readonly property bool hasHeader: root.title !== "" || root.iconName !== ""
|
readonly property bool hasHeader: root.title !== "" || root.iconName !== ""
|
||||||
|
|||||||
@@ -35,7 +35,9 @@ StyledRect {
|
|||||||
width: parent?.width ?? 0
|
width: parent?.width ?? 0
|
||||||
height: Theme.spacingL * 2 + contentColumn.height
|
height: Theme.spacingL * 2 + contentColumn.height
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceContainerHigh
|
color: Theme.nestedSurface
|
||||||
|
border.color: Theme.outlineMedium
|
||||||
|
border.width: Theme.layerOutlineWidth
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!settingKey)
|
if (!settingKey)
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ StyledRect {
|
|||||||
width: parent?.width ?? 0
|
width: parent?.width ?? 0
|
||||||
height: Theme.spacingL * 2 + mainColumn.height
|
height: Theme.spacingL * 2 + mainColumn.height
|
||||||
radius: Theme.cornerRadius
|
radius: Theme.cornerRadius
|
||||||
color: Theme.surfaceContainerHigh
|
color: Theme.nestedSurface
|
||||||
|
border.color: Theme.outlineMedium
|
||||||
|
border.width: Theme.layerOutlineWidth
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (!settingKey)
|
if (!settingKey)
|
||||||
|
|||||||
@@ -785,16 +785,24 @@ Singleton {
|
|||||||
if (category === I18n.tr("All"))
|
if (category === I18n.tr("All"))
|
||||||
return visibleApps;
|
return visibleApps;
|
||||||
|
|
||||||
const pluginItems = getPluginItems(category, "");
|
|
||||||
if (pluginItems.length > 0)
|
|
||||||
return pluginItems;
|
|
||||||
|
|
||||||
return visibleApps.filter(app => {
|
return visibleApps.filter(app => {
|
||||||
const appCategories = getCategoriesForApp(app);
|
const appCategories = getCategoriesForApp(app);
|
||||||
return appCategories.includes(category);
|
return appCategories.includes(category);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPluginIdForCategory(category) {
|
||||||
|
if (typeof PluginService === "undefined")
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const launchers = PluginService.getLauncherPlugins();
|
||||||
|
for (const pluginId in launchers) {
|
||||||
|
if ((launchers[pluginId].name || pluginId) === category)
|
||||||
|
return pluginId;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Plugin launcher support functions
|
// Plugin launcher support functions
|
||||||
function getPluginCategories() {
|
function getPluginCategories() {
|
||||||
if (typeof PluginService === "undefined") {
|
if (typeof PluginService === "undefined") {
|
||||||
@@ -814,31 +822,19 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPluginCategoryIcon(category) {
|
function getPluginCategoryIcon(category) {
|
||||||
if (typeof PluginService === "undefined")
|
const pluginId = getPluginIdForCategory(category);
|
||||||
|
if (!pluginId)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
const launchers = PluginService.getLauncherPlugins();
|
return PluginService.getLauncherPlugins()[pluginId].icon || "extension";
|
||||||
for (const pluginId in launchers) {
|
|
||||||
const plugin = launchers[pluginId];
|
|
||||||
if ((plugin.name || pluginId) === category) {
|
|
||||||
return plugin.icon || "extension";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPluginItems(category, query) {
|
function getPluginItems(category, query) {
|
||||||
if (typeof PluginService === "undefined")
|
const pluginId = getPluginIdForCategory(category);
|
||||||
|
if (!pluginId)
|
||||||
return [];
|
return [];
|
||||||
|
|
||||||
const launchers = PluginService.getLauncherPlugins();
|
return getPluginItemsForPlugin(pluginId, query);
|
||||||
for (const pluginId in launchers) {
|
|
||||||
const plugin = launchers[pluginId];
|
|
||||||
if ((plugin.name || pluginId) === category) {
|
|
||||||
return getPluginItemsForPlugin(pluginId, query);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPluginItemsForPlugin(pluginId, query) {
|
function getPluginItemsForPlugin(pluginId, query) {
|
||||||
|
|||||||
@@ -23,6 +23,13 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: typeof PowerProfiles !== "undefined" ? PowerProfiles : null
|
||||||
|
function onHasPerformanceProfileChanged() {
|
||||||
|
root.applyPowerProfile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function applyPowerProfile() {
|
function applyPowerProfile() {
|
||||||
if (!batteryAvailable)
|
if (!batteryAvailable)
|
||||||
return;
|
return;
|
||||||
@@ -32,7 +39,7 @@ Singleton {
|
|||||||
const targetProfile = parseInt(profileValue);
|
const targetProfile = parseInt(profileValue);
|
||||||
if (isNaN(targetProfile) || PowerProfiles.profile === targetProfile)
|
if (isNaN(targetProfile) || PowerProfiles.profile === targetProfile)
|
||||||
return;
|
return;
|
||||||
PowerProfiles.profile = targetProfile;
|
PowerProfileWatcher.applyProfile(targetProfile);
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")
|
readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")
|
||||||
|
|||||||
@@ -1721,6 +1721,8 @@ Singleton {
|
|||||||
return "Temperature must be between 2500K and 6000K";
|
return "Temperature must be between 2500K and 6000K";
|
||||||
|
|
||||||
const rounded = Math.round(temp / 500) * 500;
|
const rounded = Math.round(temp / 500) * 500;
|
||||||
|
if (rounded > SessionData.nightModeHighTemperature)
|
||||||
|
return "Night temperature must not exceed the day temperature (" + SessionData.nightModeHighTemperature + "K)";
|
||||||
SessionData.setNightModeTemperature(rounded);
|
SessionData.setNightModeTemperature(rounded);
|
||||||
|
|
||||||
if (root.nightModeEnabled) {
|
if (root.nightModeEnabled) {
|
||||||
@@ -1750,6 +1752,8 @@ Singleton {
|
|||||||
return "Temperature must be between 2500K and 6500K";
|
return "Temperature must be between 2500K and 6500K";
|
||||||
|
|
||||||
const rounded = Math.round(temp / 500) * 500;
|
const rounded = Math.round(temp / 500) * 500;
|
||||||
|
if (rounded < SessionData.nightModeTemperature)
|
||||||
|
return "Day temperature must be at least the night temperature (" + SessionData.nightModeTemperature + "K)";
|
||||||
SessionData.setNightModeHighTemperature(rounded);
|
SessionData.setNightModeHighTemperature(rounded);
|
||||||
|
|
||||||
if (root.nightModeEnabled && SessionData.nightModeAutoEnabled)
|
if (root.nightModeEnabled && SessionData.nightModeAutoEnabled)
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ Singleton {
|
|||||||
// Chromium can report blank metadata between tracks
|
// Chromium can report blank metadata between tracks
|
||||||
property string stableTitle: ""
|
property string stableTitle: ""
|
||||||
property string stableArtist: ""
|
property string stableArtist: ""
|
||||||
|
property string stableAlbum: ""
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.activePlayer
|
target: root.activePlayer
|
||||||
@@ -59,6 +60,9 @@ Singleton {
|
|||||||
root._syncStableMeta();
|
root._syncStableMeta();
|
||||||
root._checkIdle();
|
root._checkIdle();
|
||||||
}
|
}
|
||||||
|
function onTrackAlbumChanged() {
|
||||||
|
root._syncStableMeta();
|
||||||
|
}
|
||||||
function onLengthChanged() {
|
function onLengthChanged() {
|
||||||
if (root.activePlayer && root.activePlayer.lengthSupported && root.activePlayer.length > 1) {
|
if (root.activePlayer && root.activePlayer.lengthSupported && root.activePlayer.length > 1) {
|
||||||
root.activePlayerStableLength = root.activePlayer.length;
|
root.activePlayerStableLength = root.activePlayer.length;
|
||||||
@@ -72,8 +76,10 @@ Singleton {
|
|||||||
|
|
||||||
onActivePlayerChanged: {
|
onActivePlayerChanged: {
|
||||||
activePlayerStableLength = (activePlayer && activePlayer.lengthSupported && activePlayer.length > 1) ? activePlayer.length : 0;
|
activePlayerStableLength = (activePlayer && activePlayer.lengthSupported && activePlayer.length > 1) ? activePlayer.length : 0;
|
||||||
stableTitle = activePlayer?.trackTitle || "";
|
stableTitle = "";
|
||||||
stableArtist = activePlayer?.trackArtist || "";
|
stableArtist = "";
|
||||||
|
stableAlbum = "";
|
||||||
|
_syncStableMeta();
|
||||||
_checkIdle();
|
_checkIdle();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,14 +88,24 @@ Singleton {
|
|||||||
if (!p) {
|
if (!p) {
|
||||||
stableTitle = "";
|
stableTitle = "";
|
||||||
stableArtist = "";
|
stableArtist = "";
|
||||||
|
stableAlbum = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isFirefoxYoutubeHoverPreview(p))
|
if (isFirefoxYoutubeHoverPreview(p))
|
||||||
return;
|
return;
|
||||||
if (p.trackTitle)
|
const metadataPlayer = bestMetadataPlayer(p);
|
||||||
stableTitle = p.trackTitle;
|
const nextTitle = displayTrackTitle(metadataPlayer);
|
||||||
if (p.trackArtist)
|
const trackChanged = nextTitle && stableTitle && nextTitle.toLowerCase() !== stableTitle.toLowerCase();
|
||||||
stableArtist = p.trackArtist;
|
if (trackChanged) {
|
||||||
|
stableArtist = "";
|
||||||
|
stableAlbum = "";
|
||||||
|
}
|
||||||
|
if (nextTitle)
|
||||||
|
stableTitle = nextTitle;
|
||||||
|
if (metadataPlayer.trackArtist)
|
||||||
|
stableArtist = metadataPlayer.trackArtist;
|
||||||
|
if (metadataPlayer.trackAlbum)
|
||||||
|
stableAlbum = metadataPlayer.trackAlbum;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chromium reports stopped media w/blank metadata, resolve by checking idle status
|
// Chromium reports stopped media w/blank metadata, resolve by checking idle status
|
||||||
@@ -101,6 +117,7 @@ Singleton {
|
|||||||
return;
|
return;
|
||||||
root.stableTitle = "";
|
root.stableTitle = "";
|
||||||
root.stableArtist = "";
|
root.stableArtist = "";
|
||||||
|
root.stableAlbum = "";
|
||||||
root._resolveActivePlayer();
|
root._resolveActivePlayer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -122,20 +139,133 @@ Singleton {
|
|||||||
delegate: Connections {
|
delegate: Connections {
|
||||||
required property MprisPlayer modelData
|
required property MprisPlayer modelData
|
||||||
target: modelData
|
target: modelData
|
||||||
|
ignoreUnknownSignals: true
|
||||||
function onIsPlayingChanged() {
|
function onIsPlayingChanged() {
|
||||||
|
root._resolveActivePlayer();
|
||||||
|
root._syncStableMeta();
|
||||||
|
}
|
||||||
|
function onTrackTitleChanged() {
|
||||||
if (modelData.isPlaying)
|
if (modelData.isPlaying)
|
||||||
root._resolveActivePlayer();
|
root._resolveActivePlayer();
|
||||||
|
root._syncStableMeta();
|
||||||
|
}
|
||||||
|
function onTrackArtistChanged() {
|
||||||
|
if (modelData.isPlaying)
|
||||||
|
root._resolveActivePlayer();
|
||||||
|
root._syncStableMeta();
|
||||||
|
}
|
||||||
|
function onTrackAlbumChanged() {
|
||||||
|
if (modelData.isPlaying)
|
||||||
|
root._resolveActivePlayer();
|
||||||
|
root._syncStableMeta();
|
||||||
|
}
|
||||||
|
function onMetadataChanged() {
|
||||||
|
if (modelData.isPlaying)
|
||||||
|
root._resolveActivePlayer();
|
||||||
|
root._syncStableMeta();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIdle(player: MprisPlayer): bool {
|
function isIdle(player: MprisPlayer): bool {
|
||||||
return player && player.playbackState === MprisPlaybackState.Stopped && !player.trackTitle && !player.trackArtist;
|
return player && player.playbackState === MprisPlaybackState.Stopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Known "<title> | <App>" suffixes stripped for matching only; display keeps the full title
|
||||||
|
readonly property var _appTitleSuffixes: ["youtube", "youtube music", "soundcloud", "spotify", "chrome", "chromium", "firefox", "brave", "vivaldi", "twitch"]
|
||||||
|
|
||||||
|
function _stripAppTitleSuffix(title: string): string {
|
||||||
|
const idx = title.lastIndexOf(" | ");
|
||||||
|
if (idx <= 0)
|
||||||
|
return title;
|
||||||
|
const suffix = title.substring(idx + 3).trim().toLowerCase();
|
||||||
|
return _appTitleSuffixes.indexOf(suffix) !== -1 ? title.substring(0, idx).trim() : title;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizedTrackTitle(player: MprisPlayer): string {
|
||||||
|
return _stripAppTitleSuffix((player?.trackTitle || "").trim()).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayTrackTitle(player: MprisPlayer): string {
|
||||||
|
return (player?.trackTitle || "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizedTrackArtist(player: MprisPlayer): string {
|
||||||
|
return (player?.trackArtist || "").trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Artist missing on either side: fall back to URL, then album, before trusting a title-only match
|
||||||
|
function isSameTrack(first: MprisPlayer, second: MprisPlayer): bool {
|
||||||
|
const firstTitle = normalizedTrackTitle(first);
|
||||||
|
if (!firstTitle || firstTitle !== normalizedTrackTitle(second))
|
||||||
|
return false;
|
||||||
|
const firstArtist = normalizedTrackArtist(first);
|
||||||
|
const secondArtist = normalizedTrackArtist(second);
|
||||||
|
if (firstArtist && secondArtist)
|
||||||
|
return firstArtist === secondArtist;
|
||||||
|
const firstUrl = (first?.metadata?.["xesam:url"] || "").toString();
|
||||||
|
const secondUrl = (second?.metadata?.["xesam:url"] || "").toString();
|
||||||
|
if (firstUrl && secondUrl)
|
||||||
|
return firstUrl === secondUrl;
|
||||||
|
const firstAlbum = (first?.trackAlbum || "").trim().toLowerCase();
|
||||||
|
const secondAlbum = (second?.trackAlbum || "").trim().toLowerCase();
|
||||||
|
if (firstAlbum && secondAlbum)
|
||||||
|
return firstAlbum === secondAlbum;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function metadataQuality(player: MprisPlayer): int {
|
||||||
|
if (!player)
|
||||||
|
return -1;
|
||||||
|
let quality = player.trackArtist ? 100 : 0;
|
||||||
|
quality += player.trackTitle ? 40 : 0;
|
||||||
|
quality += player.trackAlbum ? 20 : 0;
|
||||||
|
quality += player.trackArtUrl || player.metadata?.["mpris:artUrl"] ? 10 : 0;
|
||||||
|
quality += player.metadata?.["xesam:url"] ? 5 : 0;
|
||||||
|
return quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
function equivalentPlayers(player: MprisPlayer): var {
|
||||||
|
if (!player)
|
||||||
|
return [];
|
||||||
|
return availablePlayers.filter(candidate => {
|
||||||
|
return candidate.playbackState !== MprisPlaybackState.Stopped && isSameTrack(player, candidate);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function bestMetadataPlayer(player: MprisPlayer): MprisPlayer {
|
||||||
|
const equivalents = equivalentPlayers(player);
|
||||||
|
if (equivalents.length === 0)
|
||||||
|
return player;
|
||||||
|
return equivalents.reduce((best, candidate) => {
|
||||||
|
return metadataQuality(candidate) > metadataQuality(best) ? candidate : best;
|
||||||
|
}, player);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _bestPlayingPlayer(): MprisPlayer {
|
||||||
|
const playing = availablePlayers.filter(player => player.isPlaying);
|
||||||
|
if (playing.length === 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
const controllable = playing.filter(player => player.canControl);
|
||||||
|
if (activePlayer?.isPlaying) {
|
||||||
|
if (activePlayer.canControl || controllable.length === 0)
|
||||||
|
return activePlayer;
|
||||||
|
// Playing but not controllable: only a same-track controllable peer may take over
|
||||||
|
const mirror = controllable.find(player => isSameTrack(activePlayer, player));
|
||||||
|
return mirror || activePlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activePlayer?.canControl && activePlayer.playbackState === MprisPlaybackState.Paused) {
|
||||||
|
const onlyEquivalentMirrors = playing.every(player => isSameTrack(activePlayer, player));
|
||||||
|
if (onlyEquivalentMirrors)
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return controllable[0] || playing[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
function _resolveActivePlayer(): void {
|
function _resolveActivePlayer(): void {
|
||||||
// A playing player always wins; otherwise keep the selection stable w/idle
|
const playing = _bestPlayingPlayer();
|
||||||
const playing = availablePlayers.find(p => p.isPlaying);
|
|
||||||
if (playing) {
|
if (playing) {
|
||||||
if (activePlayer !== playing) {
|
if (activePlayer !== playing) {
|
||||||
activePlayer = playing;
|
activePlayer = playing;
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ Singleton {
|
|||||||
property bool loginctlCommandAvailable: false
|
property bool loginctlCommandAvailable: false
|
||||||
property bool systemctlCommandAvailable: false
|
property bool systemctlCommandAvailable: false
|
||||||
property bool hibernateSupported: false
|
property bool hibernateSupported: false
|
||||||
|
readonly property bool softRebootSupported: systemctlCommandAvailable
|
||||||
property bool inhibitorAvailable: true
|
property bool inhibitorAvailable: true
|
||||||
property bool idleInhibited: false
|
property bool idleInhibited: false
|
||||||
property string inhibitReason: "Keep system awake"
|
property string inhibitReason: "Keep system awake"
|
||||||
@@ -470,6 +471,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function softReboot() {
|
||||||
|
Quickshell.execDetached(["systemctl", "soft-reboot"]);
|
||||||
|
}
|
||||||
|
|
||||||
function poweroff() {
|
function poweroff() {
|
||||||
if (SettingsData.customPowerActionPowerOff.length === 0) {
|
if (SettingsData.customPowerActionPowerOff.length === 0) {
|
||||||
Quickshell.execDetached(powerManagerCommand("poweroff"));
|
Quickshell.execDetached(powerManagerCommand("poweroff"));
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ Singleton {
|
|||||||
return hash.toString(16).padStart(8, '0');
|
return hash.toString(16).padStart(8, '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
function getArtworkUrl(player) {
|
function _directArtworkUrl(player) {
|
||||||
if (!player) return "";
|
if (!player) return "";
|
||||||
|
|
||||||
let artUrl = player.trackArtUrl || "";
|
let artUrl = player.trackArtUrl || "";
|
||||||
@@ -54,6 +54,17 @@ Singleton {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getArtworkUrl(player) {
|
||||||
|
const directUrl = _directArtworkUrl(player);
|
||||||
|
if (directUrl !== "")
|
||||||
|
return directUrl;
|
||||||
|
|
||||||
|
const equivalent = MprisController.equivalentPlayers(player).find(candidate => {
|
||||||
|
return candidate !== player && _directArtworkUrl(candidate) !== "";
|
||||||
|
});
|
||||||
|
return _directArtworkUrl(equivalent);
|
||||||
|
}
|
||||||
|
|
||||||
function _commit(u, artKey, srcUrl) {
|
function _commit(u, artKey, srcUrl) {
|
||||||
resolvedArtUrl = u;
|
resolvedArtUrl = u;
|
||||||
_committedArtKey = u !== "" ? artKey : "";
|
_committedArtKey = u !== "" ? artKey : "";
|
||||||
@@ -171,11 +182,25 @@ Singleton {
|
|||||||
onActivePlayerChanged: _updateArtUrl()
|
onActivePlayerChanged: _updateArtUrl()
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.activePlayer
|
target: MprisController
|
||||||
ignoreUnknownSignals: true
|
function onAvailablePlayersChanged() {
|
||||||
function onTrackTitleChanged() { root._updateArtUrl(); }
|
root._updateArtUrl();
|
||||||
function onTrackArtUrlChanged() { root._updateArtUrl(); }
|
}
|
||||||
function onMetadataChanged() { root._updateArtUrl(); }
|
}
|
||||||
|
|
||||||
|
Instantiator {
|
||||||
|
model: MprisController.availablePlayers
|
||||||
|
delegate: Connections {
|
||||||
|
required property MprisPlayer modelData
|
||||||
|
target: modelData
|
||||||
|
ignoreUnknownSignals: true
|
||||||
|
function onIsPlayingChanged() { root._updateArtUrl(); }
|
||||||
|
function onTrackTitleChanged() { root._updateArtUrl(); }
|
||||||
|
function onTrackArtistChanged() { root._updateArtUrl(); }
|
||||||
|
function onTrackAlbumChanged() { root._updateArtUrl(); }
|
||||||
|
function onTrackArtUrlChanged() { root._updateArtUrl(); }
|
||||||
|
function onMetadataChanged() { root._updateArtUrl(); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _trackKey() {
|
function _trackKey() {
|
||||||
@@ -201,8 +226,8 @@ Singleton {
|
|||||||
}
|
}
|
||||||
_pendingArtKey = key;
|
_pendingArtKey = key;
|
||||||
const url = getArtworkUrl(activePlayer);
|
const url = getArtworkUrl(activePlayer);
|
||||||
// Ignore Chrome's same-track thumbnail size updates.
|
// Ignore duplicate notifications, but let a richer peer replace same-track art
|
||||||
if (key !== "" && key === _committedArtKey)
|
if (key !== "" && key === _committedArtKey && url === _committedSrcUrl)
|
||||||
return;
|
return;
|
||||||
if (key !== "" && url !== "" && url === _committedSrcUrl) {
|
if (key !== "" && url !== "" && url === _committedSrcUrl) {
|
||||||
// Chrome can publish track metadata before its new artwork URL.
|
// Chrome can publish track metadata before its new artwork URL.
|
||||||
|
|||||||
@@ -231,11 +231,28 @@ Singleton {
|
|||||||
if (currentIndex === -1)
|
if (currentIndex === -1)
|
||||||
currentIndex = 0;
|
currentIndex = 0;
|
||||||
|
|
||||||
let targetIndex;
|
let isRandom = false;
|
||||||
if (goToPrevious) {
|
if (targetScreenName) {
|
||||||
targetIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1;
|
isRandom = !!SessionData.getMonitorCyclingSettings(targetScreenName).random;
|
||||||
} else {
|
} else {
|
||||||
targetIndex = (currentIndex + 1) % wallpaperList.length;
|
isRandom = !!SessionData.wallpaperCyclingRandom;
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetIndex;
|
||||||
|
if (isRandom) {
|
||||||
|
if (wallpaperList.length > 1) {
|
||||||
|
do {
|
||||||
|
targetIndex = Math.floor(Math.random() * wallpaperList.length);
|
||||||
|
} while (targetIndex === currentIndex);
|
||||||
|
} else {
|
||||||
|
targetIndex = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (goToPrevious) {
|
||||||
|
targetIndex = currentIndex === 0 ? wallpaperList.length - 1 : currentIndex - 1;
|
||||||
|
} else {
|
||||||
|
targetIndex = (currentIndex + 1) % wallpaperList.length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const targetWallpaper = wallpaperList[targetIndex];
|
const targetWallpaper = wallpaperList[targetIndex];
|
||||||
if (!targetWallpaper || targetWallpaper === currentPath)
|
if (!targetWallpaper || targetWallpaper === currentPath)
|
||||||
|
|||||||
@@ -379,6 +379,9 @@ Item {
|
|||||||
|
|
||||||
animationsEnabled = true;
|
animationsEnabled = true;
|
||||||
shouldBeVisible = true;
|
shouldBeVisible = true;
|
||||||
|
// Content-sized popouts lay out when contentWindow maps, while the geometry
|
||||||
|
// handlers are still gated off. Re-snapshot so the surface isn't left short.
|
||||||
|
_setSettledSurfaceGeometry();
|
||||||
if (screen) {
|
if (screen) {
|
||||||
PopoutManager.showPopout(popoutHandle);
|
PopoutManager.showPopout(popoutHandle);
|
||||||
opened();
|
opened();
|
||||||
|
|||||||
@@ -338,6 +338,33 @@
|
|||||||
],
|
],
|
||||||
"icon": "palette"
|
"icon": "palette"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"section": "wallpaperCyclingRandom",
|
||||||
|
"label": "Random Order",
|
||||||
|
"tabIndex": 0,
|
||||||
|
"category": "Personalization",
|
||||||
|
"keywords": [
|
||||||
|
"alphabetical",
|
||||||
|
"appearance",
|
||||||
|
"automatic",
|
||||||
|
"background",
|
||||||
|
"bg",
|
||||||
|
"custom",
|
||||||
|
"customize",
|
||||||
|
"cycling",
|
||||||
|
"desktop",
|
||||||
|
"image",
|
||||||
|
"order",
|
||||||
|
"personal",
|
||||||
|
"personalization",
|
||||||
|
"picture",
|
||||||
|
"random",
|
||||||
|
"select",
|
||||||
|
"shuffle",
|
||||||
|
"wallpaper"
|
||||||
|
],
|
||||||
|
"description": "Select a random wallpaper instead of cycling in alphabetical order"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"section": "wallpaperTransition",
|
"section": "wallpaperTransition",
|
||||||
"label": "Transition Effect",
|
"label": "Transition Effect",
|
||||||
@@ -4536,6 +4563,7 @@
|
|||||||
"appearance",
|
"appearance",
|
||||||
"colors",
|
"colors",
|
||||||
"controls",
|
"controls",
|
||||||
|
"floating",
|
||||||
"look",
|
"look",
|
||||||
"modal",
|
"modal",
|
||||||
"modals",
|
"modals",
|
||||||
@@ -4548,9 +4576,10 @@
|
|||||||
"surface",
|
"surface",
|
||||||
"surfaces",
|
"surfaces",
|
||||||
"theme",
|
"theme",
|
||||||
"transparency"
|
"transparency",
|
||||||
|
"windows"
|
||||||
],
|
],
|
||||||
"description": "Controls opacity of shell surfaces, popouts, and modals"
|
"description": "Controls opacity of shell surfaces, popouts, modals, and floating windows"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"section": "syncModeWithPortal",
|
"section": "syncModeWithPortal",
|
||||||
@@ -5453,6 +5482,29 @@
|
|||||||
"yubikey"
|
"yubikey"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"section": "lockScreenSecurityKeyShortcutEnabled",
|
||||||
|
"label": "Security key shortcut",
|
||||||
|
"tabIndex": 11,
|
||||||
|
"category": "Lock Screen",
|
||||||
|
"keywords": [
|
||||||
|
"authentication",
|
||||||
|
"key",
|
||||||
|
"keybind",
|
||||||
|
"keyboard",
|
||||||
|
"lock",
|
||||||
|
"login",
|
||||||
|
"password",
|
||||||
|
"screen",
|
||||||
|
"security",
|
||||||
|
"shortcut",
|
||||||
|
"start",
|
||||||
|
"u2f",
|
||||||
|
"unlock",
|
||||||
|
"yubikey"
|
||||||
|
],
|
||||||
|
"description": "Keyboard shortcut to start security key unlock"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"section": "lockScreenShowMediaPlayer",
|
"section": "lockScreenShowMediaPlayer",
|
||||||
"label": "Show Media Player",
|
"label": "Show Media Player",
|
||||||
|
|||||||
@@ -4277,9 +4277,9 @@
|
|||||||
"comment": ""
|
"comment": ""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"term": "Controls opacity of shell surfaces, popouts, and modals",
|
"term": "Controls opacity of shell surfaces, popouts, modals, and floating windows",
|
||||||
"translation": "",
|
"translation": "",
|
||||||
"context": "Controls opacity of shell surfaces, popouts, and modals",
|
"context": "Surface Opacity setting description including floating DMS windows",
|
||||||
"reference": "",
|
"reference": "",
|
||||||
"comment": ""
|
"comment": ""
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user