mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
Compare commits
42 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 069ddab041 | |||
| 45cf6ecefd | |||
| 7058b00091 | |||
| 6ba4b79039 | |||
| b8e2ce1da8 | |||
| c58d55db7d | |||
| e15db00714 | |||
| 906eba5c17 | |||
| d2b61b91ef | |||
| 6c27b92445 | |||
| a64bd80cc9 | |||
| 661dc27d26 | |||
| 3d5a6fb9d7 | |||
| c2a5397d8a | |||
| 9b5538323d | |||
| 8321d28d18 | |||
| 39278860c8 | |||
| f455456ff1 | |||
| 5a80a32951 | |||
| 3cedd56f23 | |||
| dc1ad5b329 | |||
| 25c12a14a9 | |||
| b72b276fb4 | |||
| f89fb67316 | |||
| f457e04cb1 | |||
| 23daca0b57 | |||
| accdd0972c | |||
| 00c28f69fd | |||
| 2e0337b68c | |||
| 4975960ea4 | |||
| 0381a112b3 | |||
| ed5e2a22c0 | |||
| 847718459c | |||
| 95b120b475 | |||
| 4dd2f1d5be | |||
| bf5faf725b | |||
| e5eff919b8 | |||
| 606115748a | |||
| ad936bfdc9 | |||
| 110396cb2a | |||
| 7b08e61863 | |||
| 876ead665b |
@@ -7,7 +7,7 @@ type minimalInstallGroup struct {
|
|||||||
|
|
||||||
func shouldPreferMinimalInstall(pkg string) bool {
|
func shouldPreferMinimalInstall(pkg string) bool {
|
||||||
switch pkg {
|
switch pkg {
|
||||||
case "niri", "niri-git":
|
case "niri", "niri-git", "hyprland", "hyprland-git":
|
||||||
return true
|
return true
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ type TemplateDef struct {
|
|||||||
ID string
|
ID string
|
||||||
Commands []string
|
Commands []string
|
||||||
Flatpaks []string
|
Flatpaks []string
|
||||||
|
ConfigDirs []string
|
||||||
ConfigFile string
|
ConfigFile string
|
||||||
Kind TemplateKind
|
Kind TemplateKind
|
||||||
RunUnconditionally bool
|
RunUnconditionally bool
|
||||||
@@ -60,9 +61,9 @@ var templateRegistry = []TemplateDef{
|
|||||||
{ID: "firefox", Commands: []string{"firefox"}, ConfigFile: "firefox.toml"},
|
{ID: "firefox", Commands: []string{"firefox"}, ConfigFile: "firefox.toml"},
|
||||||
{ID: "pywalfox", Commands: []string{"pywalfox"}, ConfigFile: "pywalfox.toml"},
|
{ID: "pywalfox", Commands: []string{"pywalfox"}, ConfigFile: "pywalfox.toml"},
|
||||||
{ID: "zenbrowser", Commands: []string{"zen", "zen-browser", "zen-beta", "zen-twilight"}, Flatpaks: []string{"app.zen_browser.zen"}, ConfigFile: "zenbrowser.toml"},
|
{ID: "zenbrowser", Commands: []string{"zen", "zen-browser", "zen-beta", "zen-twilight"}, Flatpaks: []string{"app.zen_browser.zen"}, ConfigFile: "zenbrowser.toml"},
|
||||||
{ID: "vesktop", Commands: []string{"vesktop"}, Flatpaks: []string{"dev.vencord.Vesktop"}, ConfigFile: "vesktop.toml"},
|
{ID: "vesktop", Commands: []string{"vesktop"}, Flatpaks: []string{"dev.vencord.Vesktop"}, ConfigDirs: []string{"vesktop"}, ConfigFile: "vesktop.toml"},
|
||||||
{ID: "vencord", Commands: []string{"discord", "Discord", "discord-canary", "DiscordCanary"}, Flatpaks: []string{"com.discordapp.Discord", "com.discordapp.DiscordCanary"}, ConfigFile: "vencord.toml"},
|
{ID: "vencord", Commands: []string{"discord", "Discord", "discord-canary", "DiscordCanary"}, Flatpaks: []string{"com.discordapp.Discord", "com.discordapp.DiscordCanary"}, ConfigDirs: []string{"Vencord"}, ConfigFile: "vencord.toml"},
|
||||||
{ID: "equibop", Commands: []string{"equibop"}, ConfigFile: "equibop.toml"},
|
{ID: "equibop", Commands: []string{"equibop"}, ConfigDirs: []string{"equibop"}, ConfigFile: "equibop.toml"},
|
||||||
{ID: "ghostty", Commands: []string{"ghostty"}, ConfigFile: "ghostty.toml", Kind: TemplateKindTerminal},
|
{ID: "ghostty", Commands: []string{"ghostty"}, ConfigFile: "ghostty.toml", Kind: TemplateKindTerminal},
|
||||||
{ID: "kitty", Commands: []string{"kitty"}, ConfigFile: "kitty.toml", Kind: TemplateKindTerminal},
|
{ID: "kitty", Commands: []string{"kitty"}, ConfigFile: "kitty.toml", Kind: TemplateKindTerminal},
|
||||||
{ID: "foot", Commands: []string{"foot"}, ConfigFile: "foot.toml", Kind: TemplateKindTerminal},
|
{ID: "foot", Commands: []string{"foot"}, ConfigFile: "foot.toml", Kind: TemplateKindTerminal},
|
||||||
@@ -459,9 +460,9 @@ output_path = '%s'
|
|||||||
case TemplateKindGTK:
|
case TemplateKindGTK:
|
||||||
switch opts.Mode {
|
switch opts.Mode {
|
||||||
case ColorModeLight:
|
case ColorModeLight:
|
||||||
appendConfig(opts, cfgFile, nil, nil, "gtk3-light.toml")
|
appendConfig(opts, cfgFile, nil, nil, nil, "gtk3-light.toml")
|
||||||
default:
|
default:
|
||||||
appendConfig(opts, cfgFile, nil, nil, "gtk3-dark.toml")
|
appendConfig(opts, cfgFile, nil, nil, nil, "gtk3-dark.toml")
|
||||||
}
|
}
|
||||||
case TemplateKindTerminal:
|
case TemplateKindTerminal:
|
||||||
appendTerminalConfig(opts, cfgFile, tmpDir, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
|
appendTerminalConfig(opts, cfgFile, tmpDir, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
|
||||||
@@ -474,10 +475,10 @@ output_path = '%s'
|
|||||||
appendVSCodeConfig(cfgFile, "vscode-insiders", filepath.Join(homeDir, ".vscode-insiders/extensions"), opts.ShellDir)
|
appendVSCodeConfig(cfgFile, "vscode-insiders", filepath.Join(homeDir, ".vscode-insiders/extensions"), opts.ShellDir)
|
||||||
case TemplateKindEmacs:
|
case TemplateKindEmacs:
|
||||||
if utils.EmacsConfigDir() != "" {
|
if utils.EmacsConfigDir() != "" {
|
||||||
appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
|
appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigDirs, tmpl.ConfigFile)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigFile)
|
appendConfig(opts, cfgFile, tmpl.Commands, tmpl.Flatpaks, tmpl.ConfigDirs, tmpl.ConfigFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -512,13 +513,14 @@ func appendConfig(
|
|||||||
cfgFile *os.File,
|
cfgFile *os.File,
|
||||||
checkCmd []string,
|
checkCmd []string,
|
||||||
checkFlatpaks []string,
|
checkFlatpaks []string,
|
||||||
|
checkConfigDirs []string,
|
||||||
fileName string,
|
fileName string,
|
||||||
) {
|
) {
|
||||||
configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName)
|
configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName)
|
||||||
if _, err := os.Stat(configPath); err != nil {
|
if _, err := os.Stat(configPath); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !appExists(opts.AppChecker, checkCmd, checkFlatpaks) {
|
if !appExists(opts.AppChecker, checkCmd, checkFlatpaks) && !configDirExists(checkConfigDirs) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := os.ReadFile(configPath)
|
data, err := os.ReadFile(configPath)
|
||||||
@@ -599,6 +601,20 @@ func templateSessionActive(tmpl TemplateDef) bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func configDirExists(names []string) bool {
|
||||||
|
configHome := utils.XDGConfigHome()
|
||||||
|
if configHome == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, name := range names {
|
||||||
|
info, err := os.Stat(filepath.Join(configHome, name))
|
||||||
|
if err == nil && info.IsDir() {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func appExists(checker utils.AppChecker, checkCmd []string, checkFlatpaks []string) bool {
|
func appExists(checker utils.AppChecker, checkCmd []string, checkFlatpaks []string) bool {
|
||||||
// Both nil is treated as "skip check" / unconditionally run
|
// Both nil is treated as "skip check" / unconditionally run
|
||||||
if checkCmd == nil && checkFlatpaks == nil {
|
if checkCmd == nil && checkFlatpaks == nil {
|
||||||
@@ -1096,7 +1112,7 @@ func CheckTemplates(checker utils.AppChecker) []TemplateCheck {
|
|||||||
case tmpl.Kind == TemplateKindEmacs:
|
case tmpl.Kind == TemplateKindEmacs:
|
||||||
detected = appExists(checker, tmpl.Commands, tmpl.Flatpaks) && utils.EmacsConfigDir() != ""
|
detected = appExists(checker, tmpl.Commands, tmpl.Flatpaks) && utils.EmacsConfigDir() != ""
|
||||||
default:
|
default:
|
||||||
detected = appExists(checker, tmpl.Commands, tmpl.Flatpaks) && templateSessionActive(tmpl)
|
detected = (appExists(checker, tmpl.Commands, tmpl.Flatpaks) || configDirExists(tmpl.ConfigDirs)) && templateSessionActive(tmpl)
|
||||||
}
|
}
|
||||||
|
|
||||||
checks = append(checks, TemplateCheck{ID: tmpl.ID, Detected: detected})
|
checks = append(checks, TemplateCheck{ID: tmpl.ID, Detected: detected})
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func TestAppendConfigBinaryExists(t *testing.T) {
|
|||||||
|
|
||||||
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
||||||
|
|
||||||
appendConfig(opts, cfgFile, []string{"sh"}, nil, "test.toml")
|
appendConfig(opts, cfgFile, []string{"sh"}, nil, nil, "test.toml")
|
||||||
|
|
||||||
cfgFile.Close()
|
cfgFile.Close()
|
||||||
output, err := os.ReadFile(outFile)
|
output, err := os.ReadFile(outFile)
|
||||||
@@ -82,7 +82,7 @@ func TestAppendConfigBinaryDoesNotExist(t *testing.T) {
|
|||||||
|
|
||||||
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
||||||
|
|
||||||
appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{}, "test.toml")
|
appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{}, nil, "test.toml")
|
||||||
|
|
||||||
cfgFile.Close()
|
cfgFile.Close()
|
||||||
output, err := os.ReadFile(outFile)
|
output, err := os.ReadFile(outFile)
|
||||||
@@ -122,7 +122,7 @@ func TestAppendConfigFlatpakExists(t *testing.T) {
|
|||||||
|
|
||||||
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
||||||
|
|
||||||
appendConfig(opts, cfgFile, nil, []string{"app.zen_browser.zen"}, "test.toml")
|
appendConfig(opts, cfgFile, nil, []string{"app.zen_browser.zen"}, nil, "test.toml")
|
||||||
|
|
||||||
cfgFile.Close()
|
cfgFile.Close()
|
||||||
output, err := os.ReadFile(outFile)
|
output, err := os.ReadFile(outFile)
|
||||||
@@ -163,7 +163,7 @@ func TestAppendConfigFlatpakDoesNotExist(t *testing.T) {
|
|||||||
|
|
||||||
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
||||||
|
|
||||||
appendConfig(opts, cfgFile, []string{}, []string{"com.nonexistent.flatpak"}, "test.toml")
|
appendConfig(opts, cfgFile, []string{}, []string{"com.nonexistent.flatpak"}, nil, "test.toml")
|
||||||
|
|
||||||
cfgFile.Close()
|
cfgFile.Close()
|
||||||
output, err := os.ReadFile(outFile)
|
output, err := os.ReadFile(outFile)
|
||||||
@@ -203,7 +203,7 @@ func TestAppendConfigBothExist(t *testing.T) {
|
|||||||
|
|
||||||
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
||||||
|
|
||||||
appendConfig(opts, cfgFile, []string{"sh"}, []string{"app.zen_browser.zen"}, "test.toml")
|
appendConfig(opts, cfgFile, []string{"sh"}, []string{"app.zen_browser.zen"}, nil, "test.toml")
|
||||||
|
|
||||||
cfgFile.Close()
|
cfgFile.Close()
|
||||||
output, err := os.ReadFile(outFile)
|
output, err := os.ReadFile(outFile)
|
||||||
@@ -244,7 +244,7 @@ func TestAppendConfigNeitherExists(t *testing.T) {
|
|||||||
|
|
||||||
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
||||||
|
|
||||||
appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{"com.nonexistent.flatpak"}, "test.toml")
|
appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{"com.nonexistent.flatpak"}, nil, "test.toml")
|
||||||
|
|
||||||
cfgFile.Close()
|
cfgFile.Close()
|
||||||
output, err := os.ReadFile(outFile)
|
output, err := os.ReadFile(outFile)
|
||||||
@@ -281,7 +281,7 @@ func TestAppendConfigNoChecks(t *testing.T) {
|
|||||||
|
|
||||||
opts := &Options{ShellDir: shellDir}
|
opts := &Options{ShellDir: shellDir}
|
||||||
|
|
||||||
appendConfig(opts, cfgFile, nil, nil, "test.toml")
|
appendConfig(opts, cfgFile, nil, nil, nil, "test.toml")
|
||||||
|
|
||||||
cfgFile.Close()
|
cfgFile.Close()
|
||||||
output, err := os.ReadFile(outFile)
|
output, err := os.ReadFile(outFile)
|
||||||
@@ -312,7 +312,7 @@ func TestAppendConfigFileDoesNotExist(t *testing.T) {
|
|||||||
|
|
||||||
opts := &Options{ShellDir: shellDir}
|
opts := &Options{ShellDir: shellDir}
|
||||||
|
|
||||||
appendConfig(opts, cfgFile, nil, nil, "nonexistent.toml")
|
appendConfig(opts, cfgFile, nil, nil, nil, "nonexistent.toml")
|
||||||
|
|
||||||
cfgFile.Close()
|
cfgFile.Close()
|
||||||
output, err := os.ReadFile(outFile)
|
output, err := os.ReadFile(outFile)
|
||||||
@@ -487,3 +487,90 @@ func TestBuildMergedConfigSkipsMangowcWithoutActiveSession(t *testing.T) {
|
|||||||
}
|
}
|
||||||
assert.NotContains(t, string(output), "[templates.dmsmango]")
|
assert.NotContains(t, string(output), "[templates.dmsmango]")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAppendConfigConfigDirExists(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
shellDir := filepath.Join(tempDir, "shell")
|
||||||
|
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||||
|
if err := os.MkdirAll(configsDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create configs dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testConfig := "vencord config content"
|
||||||
|
if err := os.WriteFile(filepath.Join(configsDir, "vencord.toml"), []byte(testConfig), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configHome := filepath.Join(tempDir, "config")
|
||||||
|
if err := os.MkdirAll(filepath.Join(configHome, "Vencord"), 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create Vencord config dir: %v", err)
|
||||||
|
}
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", configHome)
|
||||||
|
|
||||||
|
outFile := filepath.Join(tempDir, "output.toml")
|
||||||
|
cfgFile, err := os.Create(outFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create output file: %v", err)
|
||||||
|
}
|
||||||
|
defer cfgFile.Close()
|
||||||
|
|
||||||
|
mockChecker := mocks_utils.NewMockAppChecker(t)
|
||||||
|
mockChecker.EXPECT().AnyCommandExists("nonexistent-binary-12345").Return(false)
|
||||||
|
mockChecker.EXPECT().AnyFlatpakExists("com.nonexistent.flatpak").Return(false)
|
||||||
|
|
||||||
|
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
||||||
|
|
||||||
|
appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{"com.nonexistent.flatpak"}, []string{"Vencord"}, "vencord.toml")
|
||||||
|
|
||||||
|
cfgFile.Close()
|
||||||
|
output, err := os.ReadFile(outFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read output: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, testConfig+"\n", string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAppendConfigConfigDirDoesNotExist(t *testing.T) {
|
||||||
|
tempDir := t.TempDir()
|
||||||
|
|
||||||
|
shellDir := filepath.Join(tempDir, "shell")
|
||||||
|
configsDir := filepath.Join(shellDir, "matugen", "configs")
|
||||||
|
if err := os.MkdirAll(configsDir, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create configs dir: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(filepath.Join(configsDir, "vencord.toml"), []byte("vencord config content"), 0o644); err != nil {
|
||||||
|
t.Fatalf("failed to write config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
configHome := filepath.Join(tempDir, "config")
|
||||||
|
if err := os.MkdirAll(configHome, 0o755); err != nil {
|
||||||
|
t.Fatalf("failed to create config home: %v", err)
|
||||||
|
}
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", configHome)
|
||||||
|
|
||||||
|
outFile := filepath.Join(tempDir, "output.toml")
|
||||||
|
cfgFile, err := os.Create(outFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create output file: %v", err)
|
||||||
|
}
|
||||||
|
defer cfgFile.Close()
|
||||||
|
|
||||||
|
mockChecker := mocks_utils.NewMockAppChecker(t)
|
||||||
|
mockChecker.EXPECT().AnyCommandExists("nonexistent-binary-12345").Return(false)
|
||||||
|
mockChecker.EXPECT().AnyFlatpakExists("com.nonexistent.flatpak").Return(false)
|
||||||
|
|
||||||
|
opts := &Options{ShellDir: shellDir, AppChecker: mockChecker}
|
||||||
|
|
||||||
|
appendConfig(opts, cfgFile, []string{"nonexistent-binary-12345"}, []string{"com.nonexistent.flatpak"}, []string{"Vencord"}, "vencord.toml")
|
||||||
|
|
||||||
|
cfgFile.Close()
|
||||||
|
output, err := os.ReadFile(outFile)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to read output: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Empty(t, string(output))
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ func NewManager() (*Manager, error) {
|
|||||||
broker := NewSubscriptionBroker(m.broadcastPairingPrompt)
|
broker := NewSubscriptionBroker(m.broadcastPairingPrompt)
|
||||||
m.promptBroker = broker
|
m.promptBroker = broker
|
||||||
|
|
||||||
adapter, err := m.findAdapter()
|
adapter, err := findAdapter(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
conn.Close()
|
||||||
return nil, fmt.Errorf("no bluetooth adapter found: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
m.adapterPath = adapter
|
m.adapterPath = adapter
|
||||||
|
|
||||||
@@ -74,12 +74,12 @@ func NewManager() (*Manager, error) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) findAdapter() (dbus.ObjectPath, error) {
|
func findAdapter(conn *dbus.Conn) (dbus.ObjectPath, error) {
|
||||||
obj := m.dbusConn.Object(bluezService, dbus.ObjectPath("/"))
|
obj := conn.Object(bluezService, dbus.ObjectPath("/"))
|
||||||
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
var objects map[dbus.ObjectPath]map[string]map[string]dbus.Variant
|
||||||
|
|
||||||
if err := obj.Call(objectMgrIface+".GetManagedObjects", 0).Store(&objects); err != nil {
|
if err := obj.Call(objectMgrIface+".GetManagedObjects", 0).Store(&objects); err != nil {
|
||||||
return "", err
|
return "", fmt.Errorf("%w: %v", ErrNoAdapter, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for path, interfaces := range objects {
|
for path, interfaces := range objects {
|
||||||
@@ -89,7 +89,7 @@ func (m *Manager) findAdapter() (dbus.ObjectPath, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf("no adapter found")
|
return "", ErrNoAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) initialize() error {
|
func (m *Manager) initialize() error {
|
||||||
@@ -487,6 +487,11 @@ func (m *Manager) StopDiscovery() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) SetPowered(powered bool) error {
|
func (m *Manager) SetPowered(powered bool) error {
|
||||||
|
if powered {
|
||||||
|
if err := rfkillUnblockBluetooth(); err != nil {
|
||||||
|
log.Debugf("[BluezManager] rfkill unblock failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
obj := m.dbusConn.Object(bluezService, m.adapterPath)
|
obj := m.dbusConn.Object(bluezService, m.adapterPath)
|
||||||
return obj.Call(propertiesIface+".Set", 0, adapter1Iface, "Powered", dbus.MakeVariant(powered)).Err
|
return obj.Call(propertiesIface+".Set", 0, adapter1Iface, "Powered", dbus.MakeVariant(powered)).Err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package bluez
|
||||||
|
|
||||||
|
func rfkillUnblockBluetooth() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package bluez
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// linux/rfkill.h: struct rfkill_event { __u32 idx; __u8 type; __u8 op; __u8 soft; __u8 hard; },
|
||||||
|
// RFKILL_TYPE_BLUETOOTH=2, RFKILL_OP_CHANGE_ALL=3
|
||||||
|
func rfkillUnblockBluetooth() error {
|
||||||
|
f, err := os.OpenFile("/dev/rfkill", os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var event [8]byte
|
||||||
|
event[4] = 2
|
||||||
|
event[5] = 3
|
||||||
|
_, err = f.Write(event[:])
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package bluez
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/godbus/dbus/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrNoAdapter = errors.New("no bluetooth adapter found")
|
||||||
|
|
||||||
|
func WaitForAdapter() error {
|
||||||
|
conn, err := dbus.ConnectSystemBus()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
if err := conn.AddMatchSignal(
|
||||||
|
dbus.WithMatchInterface(objectMgrIface),
|
||||||
|
dbus.WithMatchMember("InterfacesAdded"),
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
signals := make(chan *dbus.Signal, 64)
|
||||||
|
conn.Signal(signals)
|
||||||
|
|
||||||
|
if _, err := findAdapter(conn); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for sig := range signals {
|
||||||
|
if sig == nil || sig.Name != objectMgrIface+".InterfacesAdded" || len(sig.Body) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ifaces, ok := sig.Body[1].(map[string]map[string]dbus.Variant)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, ok := ifaces[adapter1Iface]; ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Errorf("dbus signal stream closed")
|
||||||
|
}
|
||||||
@@ -229,6 +229,9 @@ func (m *Manager) snapshotState() CUPSState {
|
|||||||
func (m *Manager) Subscribe(id string) chan CUPSState {
|
func (m *Manager) Subscribe(id string) chan CUPSState {
|
||||||
ch := make(chan CUPSState, 64)
|
ch := make(chan CUPSState, 64)
|
||||||
|
|
||||||
|
m.subLifecycleMu.Lock()
|
||||||
|
defer m.subLifecycleMu.Unlock()
|
||||||
|
|
||||||
wasEmpty := true
|
wasEmpty := true
|
||||||
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
m.subscribers.Range(func(key string, ch chan CUPSState) bool {
|
||||||
wasEmpty = false
|
wasEmpty = false
|
||||||
@@ -237,19 +240,25 @@ func (m *Manager) Subscribe(id string) chan CUPSState {
|
|||||||
|
|
||||||
m.subscribers.Store(id, ch)
|
m.subscribers.Store(id, ch)
|
||||||
|
|
||||||
if wasEmpty && m.subscription != nil {
|
if !wasEmpty || m.subscription == nil {
|
||||||
if err := m.subscription.Start(); err != nil {
|
return ch
|
||||||
log.Warnf("[CUPS] Failed to start subscription manager: %v", err)
|
|
||||||
} else {
|
|
||||||
m.eventWG.Add(1)
|
|
||||||
go m.eventHandler()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := m.subscription.Start(); err != nil {
|
||||||
|
log.Warnf("[CUPS] Failed to start subscription manager: %v", err)
|
||||||
|
return ch
|
||||||
|
}
|
||||||
|
|
||||||
|
m.eventWG.Add(1)
|
||||||
|
go m.eventHandler()
|
||||||
|
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Unsubscribe(id string) {
|
func (m *Manager) Unsubscribe(id string) {
|
||||||
|
m.subLifecycleMu.Lock()
|
||||||
|
defer m.subLifecycleMu.Unlock()
|
||||||
|
|
||||||
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
if val, ok := m.subscribers.LoadAndDelete(id); ok {
|
||||||
close(val)
|
close(val)
|
||||||
}
|
}
|
||||||
@@ -260,18 +269,22 @@ func (m *Manager) Unsubscribe(id string) {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
if isEmpty && m.subscription != nil {
|
if !isEmpty || m.subscription == nil {
|
||||||
m.subscription.Stop()
|
return
|
||||||
m.eventWG.Wait()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m.subscription.Stop()
|
||||||
|
m.eventWG.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Close() {
|
func (m *Manager) Close() {
|
||||||
close(m.stopChan)
|
close(m.stopChan)
|
||||||
|
|
||||||
|
m.subLifecycleMu.Lock()
|
||||||
if m.subscription != nil {
|
if m.subscription != nil {
|
||||||
m.subscription.Stop()
|
m.subscription.Stop()
|
||||||
}
|
}
|
||||||
|
m.subLifecycleMu.Unlock()
|
||||||
|
|
||||||
m.eventWG.Wait()
|
m.eventWG.Wait()
|
||||||
m.notifierWg.Wait()
|
m.notifierWg.Wait()
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package cups
|
package cups
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups"
|
mocks_cups "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/cups"
|
||||||
@@ -75,6 +78,67 @@ func TestManager_Subscribe(t *testing.T) {
|
|||||||
assert.Equal(t, 0, count)
|
assert.Equal(t, 0, count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mirrors the real managers: eventChan guarded by mu, conn/running deliberately
|
||||||
|
// unsynchronized so overlapping Start/Stop trips the race detector
|
||||||
|
type stubSubscription struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
events chan SubscriptionEvent
|
||||||
|
conn *int
|
||||||
|
running bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubSubscription) Start() error {
|
||||||
|
if s.running {
|
||||||
|
return errors.New("already running")
|
||||||
|
}
|
||||||
|
s.running = true
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
s.events = make(chan SubscriptionEvent)
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
v := 0
|
||||||
|
s.conn = &v
|
||||||
|
*s.conn++
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubSubscription) Stop() {
|
||||||
|
if !s.running {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.running = false
|
||||||
|
s.conn = nil
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
close(s.events)
|
||||||
|
s.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *stubSubscription) Events() <-chan SubscriptionEvent {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
return s.events
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestManager_SubscribeUnsubscribeRace(t *testing.T) {
|
||||||
|
m := NewTestManager(mocks_cups.NewMockCUPSClientInterface(t), nil)
|
||||||
|
m.subscription = &stubSubscription{}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for i := range 8 {
|
||||||
|
wg.Go(func() {
|
||||||
|
id := fmt.Sprintf("client-%d", i)
|
||||||
|
for range 50 {
|
||||||
|
m.Subscribe(id)
|
||||||
|
m.Unsubscribe(id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
func TestManager_Close(t *testing.T) {
|
func TestManager_Close(t *testing.T) {
|
||||||
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
mockClient := mocks_cups.NewMockCUPSClientInterface(t)
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ type Manager struct {
|
|||||||
client CUPSClientInterface
|
client CUPSClientInterface
|
||||||
pkHelper PkHelper
|
pkHelper PkHelper
|
||||||
subscription SubscriptionManagerInterface
|
subscription SubscriptionManagerInterface
|
||||||
|
subLifecycleMu sync.Mutex
|
||||||
stateMutex sync.RWMutex
|
stateMutex sync.RWMutex
|
||||||
subscribers syncmap.Map[string, chan CUPSState]
|
subscribers syncmap.Map[string, chan CUPSState]
|
||||||
stopChan chan struct{}
|
stopChan chan struct{}
|
||||||
|
|||||||
@@ -117,11 +117,12 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(req.Method, "cups.") {
|
if strings.HasPrefix(req.Method, "cups.") {
|
||||||
if cupsManager == nil {
|
mgr, err := ensureCupsManager()
|
||||||
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, "CUPS manager not initialized")
|
models.RespondError(conn, req.ID, "CUPS manager not initialized")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cups.HandleRequest(conn, req, cupsManager)
|
cups.HandleRequest(conn, req, mgr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -85,8 +84,8 @@ var geoClientInstance geolocation.Client
|
|||||||
const dbusClientID = "dms-dbus-client"
|
const dbusClientID = "dms-dbus-client"
|
||||||
|
|
||||||
var capabilitySubscribers syncmap.Map[string, chan ServerInfo]
|
var capabilitySubscribers syncmap.Map[string, chan ServerInfo]
|
||||||
var cupsSubscribers syncmap.Map[string, bool]
|
var cupsMu sync.Mutex
|
||||||
var cupsSubscriberCount atomic.Int32
|
var cupsSubscriberCount int
|
||||||
|
|
||||||
func getSocketDir() string {
|
func getSocketDir() string {
|
||||||
if runtime := os.Getenv("XDG_RUNTIME_DIR"); runtime != "" {
|
if runtime := os.Getenv("XDG_RUNTIME_DIR"); runtime != "" {
|
||||||
@@ -243,17 +242,55 @@ func InitializeAppPickerManager() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitializeCupsManager() error {
|
func initializeCupsManagerLocked() (bool, error) {
|
||||||
|
if cupsManager != nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
manager, err := cups.NewManager()
|
manager, err := cups.NewManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("Failed to initialize cups manager: %v", err)
|
log.Warnf("Failed to initialize cups manager: %v", err)
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cupsManager = manager
|
cupsManager = manager
|
||||||
|
|
||||||
log.Info("CUPS manager initialized")
|
log.Info("CUPS manager initialized")
|
||||||
return nil
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ensureCupsManager() (*cups.Manager, error) {
|
||||||
|
cupsMu.Lock()
|
||||||
|
created, err := initializeCupsManagerLocked()
|
||||||
|
mgr := cupsManager
|
||||||
|
cupsMu.Unlock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if created {
|
||||||
|
notifyCapabilityChange()
|
||||||
|
}
|
||||||
|
return mgr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cupsAvailable() bool {
|
||||||
|
cupsMu.Lock()
|
||||||
|
defer cupsMu.Unlock()
|
||||||
|
return cupsManager != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func releaseCupsSubscriber() {
|
||||||
|
cupsMu.Lock()
|
||||||
|
cupsSubscriberCount--
|
||||||
|
var mgr *cups.Manager
|
||||||
|
if cupsSubscriberCount == 0 && cupsManager != nil {
|
||||||
|
mgr = cupsManager
|
||||||
|
cupsManager = nil
|
||||||
|
}
|
||||||
|
cupsMu.Unlock()
|
||||||
|
if mgr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Last CUPS subscriber disconnected, shutting down CUPS manager")
|
||||||
|
mgr.Close()
|
||||||
|
notifyCapabilityChange()
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitializeBrightnessManager() error {
|
func InitializeBrightnessManager() error {
|
||||||
@@ -463,7 +500,7 @@ func getCapabilities() Capabilities {
|
|||||||
caps = append(caps, "browser")
|
caps = append(caps, "browser")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cupsManager != nil {
|
if cupsAvailable() {
|
||||||
caps = append(caps, "cups")
|
caps = append(caps, "cups")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,7 +570,7 @@ func getServerInfo() ServerInfo {
|
|||||||
caps = append(caps, "browser")
|
caps = append(caps, "browser")
|
||||||
}
|
}
|
||||||
|
|
||||||
if cupsManager != nil {
|
if cupsAvailable() {
|
||||||
caps = append(caps, "cups")
|
caps = append(caps, "cups")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -997,38 +1034,31 @@ func handleSubscribe(conn net.Conn, req models.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if shouldSubscribe("cups") {
|
if shouldSubscribe("cups") {
|
||||||
cupsSubscribers.Store(clientID+"-cups", true)
|
cupsMu.Lock()
|
||||||
count := cupsSubscriberCount.Add(1)
|
cupsSubscriberCount++
|
||||||
|
created, err := initializeCupsManagerLocked()
|
||||||
|
mgr := cupsManager
|
||||||
|
cupsMu.Unlock()
|
||||||
|
|
||||||
if count == 1 {
|
if err != nil {
|
||||||
if err := InitializeCupsManager(); err != nil {
|
log.Warnf("Failed to initialize CUPS manager for subscription: %v", err)
|
||||||
log.Warnf("Failed to initialize CUPS manager for subscription: %v", err)
|
} else if created {
|
||||||
} else {
|
notifyCapabilityChange()
|
||||||
notifyCapabilityChange()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if cupsManager != nil {
|
if mgr == nil {
|
||||||
|
releaseCupsSubscriber()
|
||||||
|
} else {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
cupsChan := cupsManager.Subscribe(clientID + "-cups")
|
cupsChan := mgr.Subscribe(clientID + "-cups")
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
defer func() {
|
defer func() {
|
||||||
cupsManager.Unsubscribe(clientID + "-cups")
|
mgr.Unsubscribe(clientID + "-cups")
|
||||||
cupsSubscribers.Delete(clientID + "-cups")
|
releaseCupsSubscriber()
|
||||||
count := cupsSubscriberCount.Add(-1)
|
|
||||||
|
|
||||||
if count == 0 {
|
|
||||||
log.Info("Last CUPS subscriber disconnected, shutting down CUPS manager")
|
|
||||||
if cupsManager != nil {
|
|
||||||
cupsManager.Close()
|
|
||||||
cupsManager = nil
|
|
||||||
notifyCapabilityChange()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
initialState := cupsManager.GetState()
|
initialState := mgr.GetState()
|
||||||
select {
|
select {
|
||||||
case eventChan <- ServiceEvent{Service: "cups", Data: initialState}:
|
case eventChan <- ServiceEvent{Service: "cups", Data: initialState}:
|
||||||
case <-stopChan:
|
case <-stopChan:
|
||||||
@@ -1338,9 +1368,12 @@ func cleanupManagers() {
|
|||||||
if appPickerManager != nil {
|
if appPickerManager != nil {
|
||||||
appPickerManager.Close()
|
appPickerManager.Close()
|
||||||
}
|
}
|
||||||
|
cupsMu.Lock()
|
||||||
if cupsManager != nil {
|
if cupsManager != nil {
|
||||||
cupsManager.Close()
|
cupsManager.Close()
|
||||||
|
cupsManager = nil
|
||||||
}
|
}
|
||||||
|
cupsMu.Unlock()
|
||||||
if brightnessManager != nil {
|
if brightnessManager != nil {
|
||||||
brightnessManager.Close()
|
brightnessManager.Close()
|
||||||
}
|
}
|
||||||
@@ -1698,10 +1731,21 @@ func Start(printDocs bool) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if err := InitializeBluezManager(); err != nil {
|
for {
|
||||||
|
err := InitializeBluezManager()
|
||||||
|
if err == nil {
|
||||||
|
notifyCapabilityChange()
|
||||||
|
return
|
||||||
|
}
|
||||||
log.Warnf("Bluez manager unavailable: %v", err)
|
log.Warnf("Bluez manager unavailable: %v", err)
|
||||||
} else {
|
if !errors.Is(err, bluez.ErrNoAdapter) {
|
||||||
notifyCapabilityChange()
|
return
|
||||||
|
}
|
||||||
|
if err := bluez.WaitForAdapter(); err != nil {
|
||||||
|
log.Warnf("Bluetooth adapter watch failed: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Info("Bluetooth adapter appeared, initializing bluez manager")
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,8 @@ func wrapInTerminal(term, title, shellCmd string, extraArgs []string) []string {
|
|||||||
case "konsole":
|
case "konsole":
|
||||||
argv = []string{term, "-p", "tabtitle=" + title}
|
argv = []string{term, "-p", "tabtitle=" + title}
|
||||||
case "gnome-terminal":
|
case "gnome-terminal":
|
||||||
argv = []string{term, "--title=" + title}
|
// --wait: the factory process otherwise returns immediately
|
||||||
|
argv = []string{term, "--wait", "--title=" + title}
|
||||||
execFlag = "--"
|
execFlag = "--"
|
||||||
default:
|
default:
|
||||||
argv = []string{term}
|
argv = []string{term}
|
||||||
|
|||||||
@@ -412,25 +412,30 @@ func (m *Manager) runCustomUpgrade(ctx context.Context, opts UpgradeOptions) {
|
|||||||
onLine := func(line string) { m.appendLog(line) }
|
onLine := func(line string) { m.appendLog(line) }
|
||||||
argv := wrapInTerminal(term, "DMS — System Update (custom)", opts.CustomCommand, opts.TerminalArgs)
|
argv := wrapInTerminal(term, "DMS — System Update (custom)", opts.CustomCommand, opts.TerminalArgs)
|
||||||
if err := Run(ctx, argv, RunOptions{OnLine: onLine}); err != nil {
|
if err := Run(ctx, argv, RunOptions{OnLine: onLine}); err != nil {
|
||||||
code := ErrCodeBackendFailed
|
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(ctx.Err(), context.DeadlineExceeded):
|
case errors.Is(ctx.Err(), context.DeadlineExceeded):
|
||||||
code = ErrCodeTimeout
|
m.failCustomUpgrade(ErrCodeTimeout, err)
|
||||||
|
return
|
||||||
case errors.Is(ctx.Err(), context.Canceled):
|
case errors.Is(ctx.Err(), context.Canceled):
|
||||||
code = ErrCodeCancelled
|
m.failCustomUpgrade(ErrCodeCancelled, err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
m.mu.Lock()
|
// exit status reflects the trailing `read`, not the update command
|
||||||
m.state.Phase = PhaseError
|
m.appendLog(fmt.Sprintf("Terminal exited early: %v", err))
|
||||||
m.state.Error = &ErrorInfo{Code: code, Message: err.Error()}
|
|
||||||
m.mu.Unlock()
|
|
||||||
m.markDirty()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.finishSuccessfulUpgrade(false)
|
m.finishSuccessfulUpgrade(false)
|
||||||
m.runRefresh(context.Background(), false)
|
m.runRefresh(context.Background(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) failCustomUpgrade(code ErrorCode, err error) {
|
||||||
|
m.mu.Lock()
|
||||||
|
m.state.Phase = PhaseError
|
||||||
|
m.state.Error = &ErrorInfo{Code: code, Message: err.Error()}
|
||||||
|
m.mu.Unlock()
|
||||||
|
m.markDirty()
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) finishSuccessfulUpgrade(clearPackages bool) {
|
func (m *Manager) finishSuccessfulUpgrade(clearPackages bool) {
|
||||||
m.appendLog("Upgrade complete.")
|
m.appendLog("Upgrade complete.")
|
||||||
|
|
||||||
|
|||||||
@@ -233,3 +233,27 @@ func TestUpgradeBackendsFiltersFlatpakOnly(t *testing.T) {
|
|||||||
t.Fatalf("upgradeBackends(mixed) = %#v, want dnf5 then flatpak", got)
|
t.Fatalf("upgradeBackends(mixed) = %#v, want dnf5 then flatpak", got)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWrapInTerminal(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
term string
|
||||||
|
wantPrefix []string
|
||||||
|
}{
|
||||||
|
{"kitty", []string{"kitty", "--class", "com.danklinux.dms", "-T", "Title"}},
|
||||||
|
{"gnome-terminal", []string{"gnome-terminal", "--wait", "--title=Title"}},
|
||||||
|
{"foot", []string{"foot", "--app-id=com.danklinux.dms", "--title=Title"}},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
got := wrapInTerminal(tt.term, "Title", "echo hi", nil)
|
||||||
|
if len(got) < len(tt.wantPrefix) || !reflect.DeepEqual(got[:len(tt.wantPrefix)], tt.wantPrefix) {
|
||||||
|
t.Errorf("wrapInTerminal(%q) = %#v, want prefix %#v", tt.term, got, tt.wantPrefix)
|
||||||
|
}
|
||||||
|
tail := got[len(got)-3:]
|
||||||
|
if tail[0] != "sh" || tail[1] != "-c" {
|
||||||
|
t.Errorf("wrapInTerminal(%q) tail = %#v, want [sh -c <cmd>]", tt.term, tail)
|
||||||
|
}
|
||||||
|
if !strings.Contains(tail[2], "echo hi") {
|
||||||
|
t.Errorf("wrapInTerminal(%q) command %q does not contain shell command", tt.term, tail[2])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -928,8 +928,8 @@ func (p *NiriWritableProvider) formatRule(rule windowrules.WindowRule) string {
|
|||||||
if a.Opacity != nil {
|
if a.Opacity != nil {
|
||||||
lines = append(lines, fmt.Sprintf(" opacity %.2f", *a.Opacity))
|
lines = append(lines, fmt.Sprintf(" opacity %.2f", *a.Opacity))
|
||||||
}
|
}
|
||||||
if a.OpenFloating != nil && *a.OpenFloating {
|
if a.OpenFloating != nil {
|
||||||
lines = append(lines, " open-floating true")
|
lines = append(lines, fmt.Sprintf(" open-floating %t", *a.OpenFloating))
|
||||||
}
|
}
|
||||||
if a.OpenMaximized != nil && *a.OpenMaximized {
|
if a.OpenMaximized != nil && *a.OpenMaximized {
|
||||||
lines = append(lines, " open-maximized true")
|
lines = append(lines, " open-maximized true")
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("CacheData")
|
readonly property var log: Log.scoped("CacheData")
|
||||||
|
|
||||||
readonly property int cacheConfigVersion: 1
|
readonly property int cacheConfigVersion: 2
|
||||||
|
|
||||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||||
|
|
||||||
@@ -19,10 +19,21 @@ Singleton {
|
|||||||
readonly property string _stateDir: Paths.strip(_stateUrl)
|
readonly property string _stateDir: Paths.strip(_stateUrl)
|
||||||
|
|
||||||
property bool _loading: false
|
property bool _loading: false
|
||||||
|
property bool _hasLoaded: false
|
||||||
|
property int _loadedCacheVersion: 0
|
||||||
|
|
||||||
|
readonly property var _pinKeys: ["brightnessDevicePins", "wifiNetworkPins", "bluetoothDevicePins", "audioInputDevicePins", "audioOutputDevicePins"]
|
||||||
|
readonly property var _dataKeys: ["wallpaperLastPath", "profileLastPath", "fileBrowserSettings"].concat(_pinKeys)
|
||||||
|
|
||||||
property string wallpaperLastPath: ""
|
property string wallpaperLastPath: ""
|
||||||
property string profileLastPath: ""
|
property string profileLastPath: ""
|
||||||
|
|
||||||
|
property var brightnessDevicePins: ({})
|
||||||
|
property var wifiNetworkPins: ({})
|
||||||
|
property var bluetoothDevicePins: ({})
|
||||||
|
property var audioInputDevicePins: ({})
|
||||||
|
property var audioOutputDevicePins: ({})
|
||||||
|
|
||||||
property var fileBrowserSettings: ({
|
property var fileBrowserSettings: ({
|
||||||
"wallpaper": {
|
"wallpaper": {
|
||||||
"lastPath": "",
|
"lastPath": "",
|
||||||
@@ -82,8 +93,46 @@ Singleton {
|
|||||||
|
|
||||||
function loadCache() {
|
function loadCache() {
|
||||||
_loading = true;
|
_loading = true;
|
||||||
parseCache(cacheFile.text());
|
try {
|
||||||
_loading = false;
|
parseCache(cacheFile.text());
|
||||||
|
} finally {
|
||||||
|
_loading = false;
|
||||||
|
_hasLoaded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function set(key, value) {
|
||||||
|
if (_dataKeys.indexOf(key) < 0) {
|
||||||
|
log.warn("Unknown cache key:", key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
root[key] = value;
|
||||||
|
saveCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
function migratePins(pins) {
|
||||||
|
if (!pins)
|
||||||
|
return;
|
||||||
|
if (!_hasLoaded)
|
||||||
|
loadCache();
|
||||||
|
if (_loadedCacheVersion >= cacheConfigVersion)
|
||||||
|
return;
|
||||||
|
|
||||||
|
let migrated = false;
|
||||||
|
for (const key of _pinKeys) {
|
||||||
|
const legacy = pins[key];
|
||||||
|
if (!legacy || Object.keys(legacy).length === 0)
|
||||||
|
continue;
|
||||||
|
if (Object.keys(root[key] || {}).length > 0)
|
||||||
|
continue;
|
||||||
|
root[key] = legacy;
|
||||||
|
migrated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!migrated)
|
||||||
|
return;
|
||||||
|
log.info("Migrated device pins from settings.json");
|
||||||
|
saveCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseCache(content) {
|
function parseCache(content) {
|
||||||
@@ -91,6 +140,7 @@ Singleton {
|
|||||||
try {
|
try {
|
||||||
if (content && content.trim()) {
|
if (content && content.trim()) {
|
||||||
const cache = JSON.parse(content);
|
const cache = JSON.parse(content);
|
||||||
|
_loadedCacheVersion = cache.configVersion || 0;
|
||||||
|
|
||||||
wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : "";
|
wallpaperLastPath = cache.wallpaperLastPath !== undefined ? cache.wallpaperLastPath : "";
|
||||||
profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : "";
|
profileLastPath = cache.profileLastPath !== undefined ? cache.profileLastPath : "";
|
||||||
@@ -126,6 +176,10 @@ Singleton {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const key of _pinKeys) {
|
||||||
|
root[key] = cache[key] !== undefined ? cache[key] : {};
|
||||||
|
}
|
||||||
|
|
||||||
if (cache.configVersion === undefined) {
|
if (cache.configVersion === undefined) {
|
||||||
migrateFromUndefinedToV1(cache);
|
migrateFromUndefinedToV1(cache);
|
||||||
cleanupUnusedKeys();
|
cleanupUnusedKeys();
|
||||||
@@ -142,12 +196,16 @@ Singleton {
|
|||||||
function saveCache() {
|
function saveCache() {
|
||||||
if (_loading)
|
if (_loading)
|
||||||
return;
|
return;
|
||||||
cacheFile.setText(JSON.stringify({
|
const data = {
|
||||||
"wallpaperLastPath": wallpaperLastPath,
|
"wallpaperLastPath": wallpaperLastPath,
|
||||||
"profileLastPath": profileLastPath,
|
"profileLastPath": profileLastPath,
|
||||||
"fileBrowserSettings": fileBrowserSettings,
|
"fileBrowserSettings": fileBrowserSettings,
|
||||||
"configVersion": cacheConfigVersion
|
"configVersion": cacheConfigVersion
|
||||||
}, null, 2));
|
};
|
||||||
|
for (const key of _pinKeys) {
|
||||||
|
data[key] = root[key];
|
||||||
|
}
|
||||||
|
cacheFile.setText(JSON.stringify(data, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
function migrateFromUndefinedToV1(cache) {
|
function migrateFromUndefinedToV1(cache) {
|
||||||
@@ -155,7 +213,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanupUnusedKeys() {
|
function cleanupUnusedKeys() {
|
||||||
const validKeys = ["wallpaperLastPath", "profileLastPath", "fileBrowserSettings", "configVersion"];
|
const validKeys = _dataKeys.concat(["configVersion"]);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const content = cacheFile.text();
|
const content = cacheFile.text();
|
||||||
|
|||||||
@@ -65,16 +65,35 @@ Singleton {
|
|||||||
|
|
||||||
function _openPopout(popout) {
|
function _openPopout(popout) {
|
||||||
if (popout.dashVisible !== undefined) {
|
if (popout.dashVisible !== undefined) {
|
||||||
if (popout.dashVisible && !popout.shouldBeVisible && !popout.isClosing)
|
let flagStayedTrue = popout.dashVisible === true;
|
||||||
|
if (popout.dashVisible && !popout.shouldBeVisible && !popout.isClosing) {
|
||||||
popout.dashVisible = false;
|
popout.dashVisible = false;
|
||||||
|
flagStayedTrue = false;
|
||||||
|
}
|
||||||
popout.dashVisible = true;
|
popout.dashVisible = true;
|
||||||
|
// Flag already true (e.g. retargeting to another monitor) won't re-fire
|
||||||
|
// the change handler, so drive the surface open explicitly.
|
||||||
|
if (flagStayedTrue)
|
||||||
|
_ensureSurfaceOpen(popout);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (popout.notificationHistoryVisible !== undefined) {
|
if (popout.notificationHistoryVisible !== undefined) {
|
||||||
|
const flagStayedTrue = popout.notificationHistoryVisible === true;
|
||||||
popout.notificationHistoryVisible = true;
|
popout.notificationHistoryVisible = true;
|
||||||
|
if (flagStayedTrue)
|
||||||
|
_ensureSurfaceOpen(popout);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
popout.open();
|
_ensureSurfaceOpen(popout);
|
||||||
|
}
|
||||||
|
|
||||||
|
function _ensureSurfaceOpen(popout) {
|
||||||
|
if (typeof popout.present === "function") {
|
||||||
|
popout.present();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof popout.open === "function")
|
||||||
|
popout.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
function _closePopout(popout) {
|
function _closePopout(popout) {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ Singleton {
|
|||||||
id: root
|
id: root
|
||||||
readonly property var log: Log.scoped("SettingsData")
|
readonly property var log: Log.scoped("SettingsData")
|
||||||
|
|
||||||
readonly property int settingsConfigVersion: 12
|
readonly property int settingsConfigVersion: 13
|
||||||
|
|
||||||
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
readonly property bool isGreeterMode: Quickshell.env("DMS_RUN_GREETER") === "1" || Quickshell.env("DMS_RUN_GREETER") === "true"
|
||||||
|
|
||||||
@@ -773,11 +773,6 @@ Singleton {
|
|||||||
property bool fadeToDpmsEnabled: true
|
property bool fadeToDpmsEnabled: true
|
||||||
property int fadeToDpmsGracePeriod: 5
|
property int fadeToDpmsGracePeriod: 5
|
||||||
property string launchPrefix: ""
|
property string launchPrefix: ""
|
||||||
property var brightnessDevicePins: ({})
|
|
||||||
property var wifiNetworkPins: ({})
|
|
||||||
property var bluetoothDevicePins: ({})
|
|
||||||
property var audioInputDevicePins: ({})
|
|
||||||
property var audioOutputDevicePins: ({})
|
|
||||||
|
|
||||||
property bool gtkThemingEnabled: false
|
property bool gtkThemingEnabled: false
|
||||||
property bool qtThemingEnabled: false
|
property bool qtThemingEnabled: false
|
||||||
@@ -1741,6 +1736,7 @@ Singleton {
|
|||||||
let obj = (txt && txt.trim()) ? JSON.parse(txt) : null;
|
let obj = (txt && txt.trim()) ? JSON.parse(txt) : null;
|
||||||
|
|
||||||
const oldVersion = obj?.configVersion ?? 0;
|
const oldVersion = obj?.configVersion ?? 0;
|
||||||
|
const legacyPins = oldVersion < 13 ? Store.extractPins(obj) : null;
|
||||||
if (oldVersion < settingsConfigVersion) {
|
if (oldVersion < settingsConfigVersion) {
|
||||||
const migrated = Store.migrateToVersion(obj, settingsConfigVersion);
|
const migrated = Store.migrateToVersion(obj, settingsConfigVersion);
|
||||||
if (migrated) {
|
if (migrated) {
|
||||||
@@ -1748,6 +1744,8 @@ Singleton {
|
|||||||
obj = migrated;
|
obj = migrated;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (legacyPins)
|
||||||
|
Qt.callLater(() => CacheData.migratePins(legacyPins));
|
||||||
|
|
||||||
if (obj?.lockScreenActiveMonitor !== undefined) {
|
if (obj?.lockScreenActiveMonitor !== undefined) {
|
||||||
var oldVal = obj.lockScreenActiveMonitor;
|
var oldVal = obj.lockScreenActiveMonitor;
|
||||||
|
|||||||
@@ -354,11 +354,6 @@ var SPEC = {
|
|||||||
fadeToDpmsEnabled: { def: true },
|
fadeToDpmsEnabled: { def: true },
|
||||||
fadeToDpmsGracePeriod: { def: 5 },
|
fadeToDpmsGracePeriod: { def: 5 },
|
||||||
launchPrefix: { def: "" },
|
launchPrefix: { def: "" },
|
||||||
brightnessDevicePins: { def: {} },
|
|
||||||
wifiNetworkPins: { def: {} },
|
|
||||||
bluetoothDevicePins: { def: {} },
|
|
||||||
audioInputDevicePins: { def: {} },
|
|
||||||
audioOutputDevicePins: { def: {} },
|
|
||||||
|
|
||||||
gtkThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
gtkThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
||||||
qtThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
qtThemingEnabled: { def: false, onChange: "regenSystemThemes" },
|
||||||
|
|||||||
@@ -2,6 +2,21 @@
|
|||||||
|
|
||||||
.import "./SettingsSpec.js" as SpecModule
|
.import "./SettingsSpec.js" as SpecModule
|
||||||
|
|
||||||
|
var PIN_KEYS = ["brightnessDevicePins", "wifiNetworkPins", "bluetoothDevicePins", "audioInputDevicePins", "audioOutputDevicePins"];
|
||||||
|
|
||||||
|
function extractPins(obj) {
|
||||||
|
if (!obj) return null;
|
||||||
|
|
||||||
|
var pins = null;
|
||||||
|
for (var i = 0; i < PIN_KEYS.length; i++) {
|
||||||
|
var value = obj[PIN_KEYS[i]];
|
||||||
|
if (!value || Object.keys(value).length === 0) continue;
|
||||||
|
if (!pins) pins = {};
|
||||||
|
pins[PIN_KEYS[i]] = value;
|
||||||
|
}
|
||||||
|
return pins;
|
||||||
|
}
|
||||||
|
|
||||||
function parse(root, jsonObj) {
|
function parse(root, jsonObj) {
|
||||||
var SPEC = SpecModule.SPEC;
|
var SPEC = SpecModule.SPEC;
|
||||||
|
|
||||||
@@ -263,6 +278,17 @@ function migrateToVersion(obj, targetVersion) {
|
|||||||
settings.configVersion = 12;
|
settings.configVersion = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentVersion < 13) {
|
||||||
|
console.info("Migrating settings from version", currentVersion, "to version 13");
|
||||||
|
console.info("Moving device and network pins to cache.json");
|
||||||
|
|
||||||
|
for (var p = 0; p < PIN_KEYS.length; p++) {
|
||||||
|
delete settings[PIN_KEYS[p]];
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.configVersion = 13;
|
||||||
|
}
|
||||||
|
|
||||||
return settings;
|
return settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,14 @@ Item {
|
|||||||
return;
|
return;
|
||||||
fadeDpmsWindowLoader.loadedWindow.cancelFade();
|
fadeDpmsWindowLoader.loadedWindow.cancelFade();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onMonitorsOffChanged() {
|
||||||
|
if (IdleService.monitorsOff)
|
||||||
|
return;
|
||||||
|
if (!fadeDpmsWindowLoader.loadedWindow)
|
||||||
|
return;
|
||||||
|
fadeDpmsWindowLoader.loadedWindow.dismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -449,7 +449,12 @@ Item {
|
|||||||
searchQuery = query;
|
searchQuery = query;
|
||||||
requestSearch();
|
requestSearch();
|
||||||
|
|
||||||
if (searchMode !== "plugins" && query.startsWith("/")) {
|
if (autoSwitchedToFiles && !query.startsWith("/")) {
|
||||||
|
restorePreviousMode();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.startsWith("/")) {
|
||||||
var prefix = Utils.parseFileSearchPrefix(query);
|
var prefix = Utils.parseFileSearchPrefix(query);
|
||||||
var explicitType = prefix && prefix.type !== null ? prefix.type : null;
|
var explicitType = prefix && prefix.type !== null ? prefix.type : null;
|
||||||
var targetType = explicitType !== null ? explicitType : (SessionData.launcherLastFileSearchType || "all");
|
var targetType = explicitType !== null ? explicitType : (SessionData.launcherLastFileSearchType || "all");
|
||||||
|
|||||||
@@ -174,9 +174,12 @@ Item {
|
|||||||
spotlightContent.controller.selectedFlatIndex = 0;
|
spotlightContent.controller.selectedFlatIndex = 0;
|
||||||
spotlightContent.controller.selectedItem = null;
|
spotlightContent.controller.selectedItem = null;
|
||||||
spotlightContent.controller.historyIndex = -1;
|
spotlightContent.controller.historyIndex = -1;
|
||||||
spotlightContent.controller.searchQuery = targetQuery;
|
if (targetQuery) {
|
||||||
|
spotlightContent.controller.setSearchQuery(targetQuery);
|
||||||
spotlightContent.controller.performSearch();
|
} else {
|
||||||
|
spotlightContent.controller.searchQuery = "";
|
||||||
|
spotlightContent.controller.performSearch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (spotlightContent.resetScroll) {
|
if (spotlightContent.resetScroll) {
|
||||||
spotlightContent.resetScroll();
|
spotlightContent.resetScroll();
|
||||||
@@ -271,7 +274,7 @@ Item {
|
|||||||
target: spotlightContent?.controller ?? null
|
target: spotlightContent?.controller ?? null
|
||||||
|
|
||||||
function onModeChanged(mode, userInitiated) {
|
function onModeChanged(mode, userInitiated) {
|
||||||
if (!userInitiated || !SettingsData.rememberLastMode || (mode !== "all" && mode !== "apps"))
|
if (!userInitiated || !SettingsData.rememberLastMode)
|
||||||
return;
|
return;
|
||||||
SessionData.setLauncherLastMode(mode);
|
SessionData.setLauncherLastMode(mode);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ DankModal {
|
|||||||
property real holdProgress: 0
|
property real holdProgress: 0
|
||||||
property bool showHoldHint: false
|
property bool showHoldHint: false
|
||||||
property bool holdFromKeyboard: false
|
property bool holdFromKeyboard: false
|
||||||
property string pendingKeyAction: ""
|
property string committedAction: ""
|
||||||
|
|
||||||
readonly property bool needsConfirmation: SettingsData.powerActionConfirm
|
readonly property bool needsConfirmation: SettingsData.powerActionConfirm
|
||||||
readonly property int holdDurationMs: SettingsData.powerActionHoldDuration * 1000
|
readonly property int holdDurationMs: SettingsData.powerActionHoldDuration * 1000
|
||||||
@@ -39,10 +39,16 @@ DankModal {
|
|||||||
return action !== "lock" && action !== "restart";
|
return action !== "lock" && action !== "restart";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function actionWakesOnKeyRelease(action) {
|
||||||
|
return action === "suspend" || action === "hibernate";
|
||||||
|
}
|
||||||
|
|
||||||
function startHold(action, actionIndex) {
|
function startHold(action, actionIndex) {
|
||||||
|
if (committedAction !== "")
|
||||||
|
return;
|
||||||
if (!needsConfirmation || !actionNeedsConfirm(action)) {
|
if (!needsConfirmation || !actionNeedsConfirm(action)) {
|
||||||
if (holdFromKeyboard) {
|
if (holdFromKeyboard && actionWakesOnKeyRelease(action)) {
|
||||||
pendingKeyAction = action;
|
commitAction(action, actionIndex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
executeAction(action);
|
executeAction(action);
|
||||||
@@ -56,7 +62,6 @@ DankModal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cancelHold() {
|
function cancelHold() {
|
||||||
pendingKeyAction = "";
|
|
||||||
if (holdAction === "")
|
if (holdAction === "")
|
||||||
return;
|
return;
|
||||||
const wasHolding = holdProgress > 0;
|
const wasHolding = holdProgress > 0;
|
||||||
@@ -76,8 +81,8 @@ DankModal {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
holdTimer.stop();
|
holdTimer.stop();
|
||||||
if (holdFromKeyboard) {
|
if (holdFromKeyboard && actionWakesOnKeyRelease(holdAction)) {
|
||||||
pendingKeyAction = holdAction;
|
commitAction(holdAction, holdActionIndex);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const action = holdAction;
|
const action = holdAction;
|
||||||
@@ -87,6 +92,25 @@ DankModal {
|
|||||||
executeAction(action);
|
executeAction(action);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function commitAction(action, actionIndex) {
|
||||||
|
holdTimer.stop();
|
||||||
|
holdAction = "";
|
||||||
|
holdActionIndex = actionIndex;
|
||||||
|
committedAction = action;
|
||||||
|
commitFallbackTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeCommittedAction() {
|
||||||
|
if (committedAction === "")
|
||||||
|
return;
|
||||||
|
commitFallbackTimer.stop();
|
||||||
|
const action = committedAction;
|
||||||
|
committedAction = "";
|
||||||
|
holdActionIndex = -1;
|
||||||
|
holdProgress = 0;
|
||||||
|
executeAction(action);
|
||||||
|
}
|
||||||
|
|
||||||
signal switchUserRequested
|
signal switchUserRequested
|
||||||
|
|
||||||
function executeAction(action) {
|
function executeAction(action) {
|
||||||
@@ -128,6 +152,12 @@ DankModal {
|
|||||||
onTriggered: root.showHoldHint = false
|
onTriggered: root.showHoldHint = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: commitFallbackTimer
|
||||||
|
interval: 5000
|
||||||
|
onTriggered: root.executeCommittedAction()
|
||||||
|
}
|
||||||
|
|
||||||
function openCentered() {
|
function openCentered() {
|
||||||
parentBounds = Qt.rect(0, 0, 0, 0);
|
parentBounds = Qt.rect(0, 0, 0, 0);
|
||||||
parentScreen = null;
|
parentScreen = null;
|
||||||
@@ -286,7 +316,8 @@ DankModal {
|
|||||||
holdProgress = 0;
|
holdProgress = 0;
|
||||||
showHoldHint = false;
|
showHoldHint = false;
|
||||||
holdFromKeyboard = false;
|
holdFromKeyboard = false;
|
||||||
pendingKeyAction = "";
|
committedAction = "";
|
||||||
|
commitFallbackTimer.stop();
|
||||||
updateVisibleActions();
|
updateVisibleActions();
|
||||||
const defaultIndex = getDefaultActionIndex();
|
const defaultIndex = getDefaultActionIndex();
|
||||||
selectedIndex = defaultIndex;
|
selectedIndex = defaultIndex;
|
||||||
@@ -309,6 +340,10 @@ DankModal {
|
|||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (committedAction !== "") {
|
||||||
|
event.accepted = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
holdFromKeyboard = true;
|
holdFromKeyboard = true;
|
||||||
if (SettingsData.powerMenuGridLayout) {
|
if (SettingsData.powerMenuGridLayout) {
|
||||||
handleGridNavigation(event, true);
|
handleGridNavigation(event, true);
|
||||||
@@ -321,6 +356,11 @@ DankModal {
|
|||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (committedAction !== "") {
|
||||||
|
event.accepted = true;
|
||||||
|
executeCommittedAction();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (SettingsData.powerMenuGridLayout) {
|
if (SettingsData.powerMenuGridLayout) {
|
||||||
handleGridNavigation(event, false);
|
handleGridNavigation(event, false);
|
||||||
} else {
|
} else {
|
||||||
@@ -331,16 +371,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_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 (pendingKeyAction !== "") {
|
cancelHold();
|
||||||
const action = pendingKeyAction;
|
|
||||||
pendingKeyAction = "";
|
|
||||||
holdAction = "";
|
|
||||||
holdActionIndex = -1;
|
|
||||||
holdProgress = 0;
|
|
||||||
executeAction(action);
|
|
||||||
} else {
|
|
||||||
cancelHold();
|
|
||||||
}
|
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -434,16 +465,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_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 (pendingKeyAction !== "") {
|
cancelHold();
|
||||||
const action = pendingKeyAction;
|
|
||||||
pendingKeyAction = "";
|
|
||||||
holdAction = "";
|
|
||||||
holdActionIndex = -1;
|
|
||||||
holdProgress = 0;
|
|
||||||
executeAction(action);
|
|
||||||
} else {
|
|
||||||
cancelHold();
|
|
||||||
}
|
|
||||||
event.accepted = true;
|
event.accepted = true;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -812,6 +834,7 @@ DankModal {
|
|||||||
|
|
||||||
Row {
|
Row {
|
||||||
id: hintRow
|
id: hintRow
|
||||||
|
readonly property bool selectedNeedsHold: root.actionNeedsConfirm(root.getActionAtIndex(root.selectedIndex))
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.bottom: parent.bottom
|
anchors.bottom: parent.bottom
|
||||||
anchors.bottomMargin: Theme.spacingS
|
anchors.bottomMargin: Theme.spacingS
|
||||||
@@ -826,7 +849,13 @@ DankModal {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: root.showHoldHint ? "warning" : "touch_app"
|
name: {
|
||||||
|
if (root.showHoldHint)
|
||||||
|
return "warning";
|
||||||
|
if (!hintRow.selectedNeedsHold)
|
||||||
|
return "bolt";
|
||||||
|
return "touch_app";
|
||||||
|
}
|
||||||
size: Theme.fontSizeSmall
|
size: Theme.fontSizeSmall
|
||||||
color: root.showHoldHint ? Theme.warning : Theme.surfaceTextSecondary
|
color: root.showHoldHint ? Theme.warning : Theme.surfaceTextSecondary
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -836,8 +865,12 @@ DankModal {
|
|||||||
readonly property real totalMs: SettingsData.powerActionHoldDuration * 1000
|
readonly property real totalMs: SettingsData.powerActionHoldDuration * 1000
|
||||||
readonly property int remainingMs: Math.ceil(totalMs * (1 - root.holdProgress))
|
readonly property int remainingMs: Math.ceil(totalMs * (1 - root.holdProgress))
|
||||||
text: {
|
text: {
|
||||||
|
if (root.committedAction !== "")
|
||||||
|
return I18n.tr("Release to confirm");
|
||||||
if (root.showHoldHint)
|
if (root.showHoldHint)
|
||||||
return I18n.tr("Hold longer to confirm");
|
return I18n.tr("Hold longer to confirm");
|
||||||
|
if (!hintRow.selectedNeedsHold)
|
||||||
|
return I18n.tr("Activates immediately");
|
||||||
if (root.holdProgress > 0) {
|
if (root.holdProgress > 0) {
|
||||||
if (totalMs < 1000)
|
if (totalMs < 1000)
|
||||||
return I18n.tr("Hold to confirm (%1 ms)").arg(remainingMs);
|
return I18n.tr("Hold to confirm (%1 ms)").arg(remainingMs);
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ FloatingWindow {
|
|||||||
signal closingModal
|
signal closingModal
|
||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
|
if (visible && !backingWindowVisible) {
|
||||||
|
visible = false;
|
||||||
|
}
|
||||||
visible = true;
|
visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +52,11 @@ FloatingWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
visible = !visible;
|
if (visible && backingWindowVisible) {
|
||||||
|
hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTabIndex(tabIndex: int) {
|
function setTabIndex(tabIndex: int) {
|
||||||
@@ -61,13 +68,12 @@ FloatingWindow {
|
|||||||
|
|
||||||
function showWithTab(tabIndex: int) {
|
function showWithTab(tabIndex: int) {
|
||||||
setTabIndex(tabIndex);
|
setTabIndex(tabIndex);
|
||||||
visible = true;
|
show();
|
||||||
}
|
}
|
||||||
|
|
||||||
function showWithTabName(tabName: string) {
|
function showWithTabName(tabName: string) {
|
||||||
var idx = sidebar.resolveTabIndex(tabName);
|
setTabIndex(sidebar.resolveTabIndex(tabName));
|
||||||
setTabIndex(idx);
|
show();
|
||||||
visible = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveTabIndex(tabName: string): int {
|
function resolveTabIndex(tabName: string): int {
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ FloatingWindow {
|
|||||||
condInitialised.triState = 0;
|
condInitialised.triState = 0;
|
||||||
opacityEnabled.checked = false;
|
opacityEnabled.checked = false;
|
||||||
opacitySlider.value = 100;
|
opacitySlider.value = 100;
|
||||||
|
floatingCond.triState = 0;
|
||||||
floatingToggle.checked = false;
|
floatingToggle.checked = false;
|
||||||
maximizedToggle.checked = false;
|
maximizedToggle.checked = false;
|
||||||
maximizedToEdgesToggle.checked = false;
|
maximizedToEdgesToggle.checked = false;
|
||||||
@@ -164,6 +165,7 @@ FloatingWindow {
|
|||||||
opacityEnabled.checked = hasOpacity;
|
opacityEnabled.checked = hasOpacity;
|
||||||
opacitySlider.value = hasOpacity ? Math.round(actions.opacity * 100) : 100;
|
opacitySlider.value = hasOpacity ? Math.round(actions.opacity * 100) : 100;
|
||||||
|
|
||||||
|
floatingCond.triState = triFromBool(actions.openFloating);
|
||||||
floatingToggle.checked = actions.openFloating || false;
|
floatingToggle.checked = actions.openFloating || false;
|
||||||
maximizedToggle.checked = actions.openMaximized || false;
|
maximizedToggle.checked = actions.openMaximized || false;
|
||||||
maximizedToEdgesToggle.checked = actions.openMaximizedToEdges || false;
|
maximizedToEdgesToggle.checked = actions.openMaximizedToEdges || false;
|
||||||
@@ -318,7 +320,9 @@ FloatingWindow {
|
|||||||
|
|
||||||
if (opacityEnabled.checked)
|
if (opacityEnabled.checked)
|
||||||
actions.opacity = opacitySlider.value / 100;
|
actions.opacity = opacitySlider.value / 100;
|
||||||
if (floatingToggle.checked)
|
if (isNiri)
|
||||||
|
applyCond(actions, "openFloating", floatingCond.triState);
|
||||||
|
else if (floatingToggle.checked)
|
||||||
actions.openFloating = true;
|
actions.openFloating = true;
|
||||||
if (maximizedToggle.checked)
|
if (maximizedToggle.checked)
|
||||||
actions.openMaximized = true;
|
actions.openMaximized = true;
|
||||||
@@ -943,6 +947,17 @@ FloatingWindow {
|
|||||||
title: I18n.tr("Window Opening")
|
title: I18n.tr("Window Opening")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Flow {
|
||||||
|
width: parent.width
|
||||||
|
spacing: Theme.spacingS
|
||||||
|
visible: isNiri
|
||||||
|
|
||||||
|
MatchCond {
|
||||||
|
id: floatingCond
|
||||||
|
label: I18n.tr("Float")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Flow {
|
Flow {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
spacing: Theme.spacingL
|
spacing: Theme.spacingL
|
||||||
@@ -950,6 +965,7 @@ FloatingWindow {
|
|||||||
CheckboxRow {
|
CheckboxRow {
|
||||||
id: floatingToggle
|
id: floatingToggle
|
||||||
label: I18n.tr("Float")
|
label: I18n.tr("Float")
|
||||||
|
visible: !isNiri
|
||||||
}
|
}
|
||||||
CheckboxRow {
|
CheckboxRow {
|
||||||
id: maximizedToggle
|
id: maximizedToggle
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ DankPopout {
|
|||||||
|
|
||||||
readonly property color _containerBg: Theme.nestedSurface
|
readonly property color _containerBg: Theme.nestedSurface
|
||||||
|
|
||||||
|
// Defer open one tick so screen-change geometry settles before the surface
|
||||||
|
// maps; a synchronous open churns the surface and loses the blur on a switch.
|
||||||
|
function present() {
|
||||||
|
Qt.callLater(open);
|
||||||
|
}
|
||||||
|
|
||||||
function openWithSection(section) {
|
function openWithSection(section) {
|
||||||
StateUtils.openWithSection(root, section);
|
StateUtils.openWithSection(root, section);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPinnedInputs() {
|
function getPinnedInputs() {
|
||||||
const pins = SettingsData.audioInputDevicePins || {};
|
const pins = CacheData.audioInputDevicePins || {};
|
||||||
return normalizePinList(pins["preferredInput"]);
|
return normalizePinList(pins["preferredInput"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,7 +315,7 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.audioInputDevicePins || {}));
|
const pins = JSON.parse(JSON.stringify(CacheData.audioInputDevicePins || {}));
|
||||||
let pinnedList = audioContent.normalizePinList(pins["preferredInput"]);
|
let pinnedList = audioContent.normalizePinList(pins["preferredInput"]);
|
||||||
const pinIndex = pinnedList.indexOf(modelData.name);
|
const pinIndex = pinnedList.indexOf(modelData.name);
|
||||||
|
|
||||||
@@ -332,7 +332,7 @@ Rectangle {
|
|||||||
else
|
else
|
||||||
delete pins["preferredInput"];
|
delete pins["preferredInput"];
|
||||||
|
|
||||||
SettingsData.set("audioInputDevicePins", pins);
|
CacheData.set("audioInputDevicePins", pins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,7 +169,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPinnedOutputs() {
|
function getPinnedOutputs() {
|
||||||
const pins = SettingsData.audioOutputDevicePins || {};
|
const pins = CacheData.audioOutputDevicePins || {};
|
||||||
return normalizePinList(pins["preferredOutput"]);
|
return normalizePinList(pins["preferredOutput"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,7 +324,7 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.audioOutputDevicePins || {}));
|
const pins = JSON.parse(JSON.stringify(CacheData.audioOutputDevicePins || {}));
|
||||||
let pinnedList = audioContent.normalizePinList(pins["preferredOutput"]);
|
let pinnedList = audioContent.normalizePinList(pins["preferredOutput"]);
|
||||||
const pinIndex = pinnedList.indexOf(modelData.name);
|
const pinIndex = pinnedList.indexOf(modelData.name);
|
||||||
|
|
||||||
@@ -341,7 +341,7 @@ Rectangle {
|
|||||||
else
|
else
|
||||||
delete pins["preferredOutput"];
|
delete pins["preferredOutput"];
|
||||||
|
|
||||||
SettingsData.set("audioOutputDevicePins", pins);
|
CacheData.set("audioOutputDevicePins", pins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPinnedDevices() {
|
function getPinnedDevices() {
|
||||||
const pins = SettingsData.bluetoothDevicePins || {};
|
const pins = CacheData.bluetoothDevicePins || {};
|
||||||
return normalizePinList(pins["preferredDevice"]);
|
return normalizePinList(pins["preferredDevice"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,7 +400,7 @@ Rectangle {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.bluetoothDevicePins || {}));
|
const pins = JSON.parse(JSON.stringify(CacheData.bluetoothDevicePins || {}));
|
||||||
let pinnedList = root.normalizePinList(pins["preferredDevice"]);
|
let pinnedList = root.normalizePinList(pins["preferredDevice"]);
|
||||||
const pinIndex = pinnedList.indexOf(pairedDelegate.modelData.address);
|
const pinIndex = pinnedList.indexOf(pairedDelegate.modelData.address);
|
||||||
|
|
||||||
@@ -418,7 +418,7 @@ Rectangle {
|
|||||||
delete pins["preferredDevice"];
|
delete pins["preferredDevice"];
|
||||||
}
|
}
|
||||||
|
|
||||||
SettingsData.set("bluetoothDevicePins", pins);
|
CacheData.set("bluetoothDevicePins", pins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ Rectangle {
|
|||||||
|
|
||||||
const pinKey = getScreenPinKey();
|
const pinKey = getScreenPinKey();
|
||||||
if (pinKey.length > 0) {
|
if (pinKey.length > 0) {
|
||||||
const pins = SettingsData.brightnessDevicePins || {};
|
const pins = CacheData.brightnessDevicePins || {};
|
||||||
const pinnedDevice = pins[pinKey];
|
const pinnedDevice = pins[pinKey];
|
||||||
if (pinnedDevice && pinnedDevice.length > 0) {
|
if (pinnedDevice && pinnedDevice.length > 0) {
|
||||||
const found = devices.find(d => d.name === pinnedDevice);
|
const found = devices.find(d => d.name === pinnedDevice);
|
||||||
@@ -85,12 +85,12 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
const pinKey = getScreenPinKey();
|
const pinKey = getScreenPinKey();
|
||||||
if (pinKey.length > 0) {
|
if (pinKey.length > 0) {
|
||||||
const pins = SettingsData.brightnessDevicePins || {};
|
const pins = CacheData.brightnessDevicePins || {};
|
||||||
const existing = pins[pinKey];
|
const existing = pins[pinKey];
|
||||||
if (existing && existing !== deviceName) {
|
if (existing && existing !== deviceName) {
|
||||||
const next = JSON.parse(JSON.stringify(pins));
|
const next = JSON.parse(JSON.stringify(pins));
|
||||||
delete next[pinKey];
|
delete next[pinKey];
|
||||||
SettingsData.set("brightnessDevicePins", next);
|
CacheData.set("brightnessDevicePins", next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root.currentDeviceName = deviceName;
|
root.currentDeviceName = deviceName;
|
||||||
@@ -106,7 +106,7 @@ Rectangle {
|
|||||||
const pinKey = getScreenPinKey();
|
const pinKey = getScreenPinKey();
|
||||||
if (!pinKey || !deviceName)
|
if (!pinKey || !deviceName)
|
||||||
return false;
|
return false;
|
||||||
const pins = SettingsData.brightnessDevicePins || {};
|
const pins = CacheData.brightnessDevicePins || {};
|
||||||
return pins[pinKey] === deviceName;
|
return pins[pinKey] === deviceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,13 +114,13 @@ Rectangle {
|
|||||||
const pinKey = getScreenPinKey();
|
const pinKey = getScreenPinKey();
|
||||||
if (!pinKey || !deviceName)
|
if (!pinKey || !deviceName)
|
||||||
return;
|
return;
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.brightnessDevicePins || {}));
|
const pins = JSON.parse(JSON.stringify(CacheData.brightnessDevicePins || {}));
|
||||||
if (pins[pinKey] === deviceName) {
|
if (pins[pinKey] === deviceName) {
|
||||||
delete pins[pinKey];
|
delete pins[pinKey];
|
||||||
} else {
|
} else {
|
||||||
pins[pinKey] = deviceName;
|
pins[pinKey] = deviceName;
|
||||||
}
|
}
|
||||||
SettingsData.set("brightnessDevicePins", pins);
|
CacheData.set("brightnessDevicePins", pins);
|
||||||
}
|
}
|
||||||
|
|
||||||
implicitHeight: {
|
implicitHeight: {
|
||||||
@@ -269,7 +269,7 @@ Rectangle {
|
|||||||
|
|
||||||
readonly property bool selected: !!(modelData && modelData.name === root.currentDeviceName)
|
readonly property bool selected: !!(modelData && modelData.name === root.currentDeviceName)
|
||||||
readonly property bool devicePinnedHere: {
|
readonly property bool devicePinnedHere: {
|
||||||
SettingsData.brightnessDevicePins;
|
CacheData.brightnessDevicePins;
|
||||||
return root.isDevicePinnedToScreen(modelData ? modelData.name : "");
|
return root.isDevicePinnedToScreen(modelData ? modelData.name : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ Rectangle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPinnedNetworks() {
|
function getPinnedNetworks() {
|
||||||
const pins = SettingsData.wifiNetworkPins || {};
|
const pins = CacheData.wifiNetworkPins || {};
|
||||||
return normalizePinList(pins["preferredWifi"]);
|
return normalizePinList(pins["preferredWifi"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -710,7 +710,7 @@ Rectangle {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
onPressed: mouse => pinRipple.trigger(mouse.x, mouse.y)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}));
|
const pins = JSON.parse(JSON.stringify(CacheData.wifiNetworkPins || {}));
|
||||||
let pinnedList = root.normalizePinList(pins["preferredWifi"]);
|
let pinnedList = root.normalizePinList(pins["preferredWifi"]);
|
||||||
const pinIndex = pinnedList.indexOf(modelData.ssid);
|
const pinIndex = pinnedList.indexOf(modelData.ssid);
|
||||||
|
|
||||||
@@ -727,7 +727,7 @@ Rectangle {
|
|||||||
else
|
else
|
||||||
delete pins["preferredWifi"];
|
delete pins["preferredWifi"];
|
||||||
|
|
||||||
SettingsData.set("wifiNetworkPins", pins);
|
CacheData.set("wifiNetworkPins", pins);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Row {
|
|||||||
if (screenName && screenName.length > 0) {
|
if (screenName && screenName.length > 0) {
|
||||||
const screen = Quickshell.screens.find(s => s.name === screenName);
|
const screen = Quickshell.screens.find(s => s.name === screenName);
|
||||||
const pinKey = screen ? SettingsData.getScreenDisplayName(screen) : screenName;
|
const pinKey = screen ? SettingsData.getScreenDisplayName(screen) : screenName;
|
||||||
const pins = SettingsData.brightnessDevicePins || {};
|
const pins = CacheData.brightnessDevicePins || {};
|
||||||
const pinnedDevice = pins[pinKey];
|
const pinnedDevice = pins[pinKey];
|
||||||
if (pinnedDevice && pinnedDevice.length > 0) {
|
if (pinnedDevice && pinnedDevice.length > 0) {
|
||||||
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice);
|
const found = DisplayService.devices.find(dev => dev.name === pinnedDevice);
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ Item {
|
|||||||
property var centerWidgets: []
|
property var centerWidgets: []
|
||||||
property int totalWidgets: 0
|
property int totalWidgets: 0
|
||||||
property real totalSize: 0
|
property real totalSize: 0
|
||||||
|
property real contentStart: 0
|
||||||
|
property real contentSize: 0
|
||||||
|
|
||||||
function updateLayout() {
|
function updateLayout() {
|
||||||
if (SettingsData.centeringMode === "geometric") {
|
if (SettingsData.centeringMode === "geometric") {
|
||||||
@@ -36,6 +38,25 @@ Item {
|
|||||||
} else {
|
} else {
|
||||||
applyIndexLayout();
|
applyIndexLayout();
|
||||||
}
|
}
|
||||||
|
updateContentExtent();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateContentExtent() {
|
||||||
|
if (centerWidgets.length === 0) {
|
||||||
|
contentStart = 0;
|
||||||
|
contentSize = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let start = Infinity;
|
||||||
|
let end = -Infinity;
|
||||||
|
for (const widget of centerWidgets) {
|
||||||
|
const pos = isVertical ? widget.y : widget.x;
|
||||||
|
const size = isVertical ? widget.height : widget.width;
|
||||||
|
start = Math.min(start, pos);
|
||||||
|
end = Math.max(end, pos + size);
|
||||||
|
}
|
||||||
|
contentStart = start;
|
||||||
|
contentSize = end - start;
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyGeometricLayout() {
|
function applyGeometricLayout() {
|
||||||
|
|||||||
@@ -833,16 +833,30 @@ PanelWindow {
|
|||||||
const pos = section.mapToItem(barWindow.contentItem, 0, 0);
|
const pos = section.mapToItem(barWindow.contentItem, 0, 0);
|
||||||
const implW = section.implicitWidth || 0;
|
const implW = section.implicitWidth || 0;
|
||||||
const implH = section.implicitHeight || 0;
|
const implH = section.implicitHeight || 0;
|
||||||
|
const contentSize = isCenter ? (section.contentSize || 0) : 0;
|
||||||
|
|
||||||
const offsetX = isCenter && !barWindow.isVertical ? (section.width - implW) / 2 : 0;
|
let offsetX = isCenter && !barWindow.isVertical ? (section.width - implW) / 2 : 0;
|
||||||
const offsetY = !barWindow.isVertical ? (section.height - implH) / 2 : (isCenter ? (section.height - implH) / 2 : 0);
|
let offsetY = !barWindow.isVertical ? (section.height - implH) / 2 : (isCenter ? (section.height - implH) / 2 : 0);
|
||||||
|
let w = implW;
|
||||||
|
let h = implH;
|
||||||
|
|
||||||
|
// index centering lays content out asymmetrically; use the real extent
|
||||||
|
if (contentSize > 0) {
|
||||||
|
if (barWindow.isVertical) {
|
||||||
|
offsetY = section.contentStart;
|
||||||
|
h = contentSize;
|
||||||
|
} else {
|
||||||
|
offsetX = section.contentStart;
|
||||||
|
w = contentSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const edgePad = 2;
|
const edgePad = 2;
|
||||||
return {
|
return {
|
||||||
"x": pos.x + offsetX - edgePad,
|
"x": pos.x + offsetX - edgePad,
|
||||||
"y": pos.y + offsetY - edgePad,
|
"y": pos.y + offsetY - edgePad,
|
||||||
"w": implW + edgePad * 2,
|
"w": w + edgePad * 2,
|
||||||
"h": implH + edgePad * 2
|
"h": h + edgePad * 2
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,8 +63,8 @@ BasePill {
|
|||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
acceptedButtons: Qt.LeftButton | Qt.RightButton
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => root.triggerRipple(this, mouse.x, mouse.y)
|
onPressed: function (mouse) {
|
||||||
onClicked: function (mouse) {
|
root.triggerRipple(this, mouse.x, mouse.y);
|
||||||
switch (mouse.button) {
|
switch (mouse.button) {
|
||||||
case Qt.RightButton:
|
case Qt.RightButton:
|
||||||
openContextMenu();
|
openContextMenu();
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ BasePill {
|
|||||||
const pinKey = getScreenPinKey();
|
const pinKey = getScreenPinKey();
|
||||||
if (!pinKey)
|
if (!pinKey)
|
||||||
return "";
|
return "";
|
||||||
const pins = SettingsData.brightnessDevicePins || {};
|
const pins = CacheData.brightnessDevicePins || {};
|
||||||
return pins[pinKey] || "";
|
return pins[pinKey] || "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,44 +35,42 @@ BasePill {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onRightClicked: (rx, ry) => {
|
onRightClicked: {
|
||||||
const screen = root.parentScreen || Screen;
|
const screen = root.parentScreen || Screen;
|
||||||
if (!screen)
|
if (!screen)
|
||||||
return;
|
return;
|
||||||
const globalPos = root.visualContent.mapToItem(null, 0, 0);
|
|
||||||
const isVertical = root.axis?.isVertical ?? false;
|
const isVertical = root.axis?.isVertical ?? false;
|
||||||
const edge = root.axis?.edge ?? "top";
|
const edge = root.axis?.edge ?? "top";
|
||||||
const gap = Math.max(Theme.spacingXS, root.barSpacing ?? Theme.spacingXS);
|
const gap = Math.max(Theme.spacingXS, root.barSpacing ?? Theme.spacingXS);
|
||||||
const barOffset = root.barThickness + root.barSpacing + gap;
|
const barOffset = root.barThickness + root.barSpacing + gap;
|
||||||
|
const localPos = root.visualContent.mapToItem(null, root.visualContent.width / 2, root.visualContent.height / 2);
|
||||||
|
|
||||||
let anchorX;
|
let anchorX;
|
||||||
let anchorY;
|
let anchorY;
|
||||||
let anchorEdge;
|
|
||||||
if (isVertical) {
|
if (isVertical) {
|
||||||
anchorY = globalPos.y - (screen.y || 0) + root.visualContent.height / 2;
|
anchorX = edge === "left" ? barOffset : screen.width - barOffset;
|
||||||
if (edge === "left") {
|
anchorY = localPos.y;
|
||||||
anchorX = barOffset;
|
|
||||||
anchorEdge = "top";
|
|
||||||
} else {
|
|
||||||
anchorX = screen.width - barOffset;
|
|
||||||
anchorEdge = "top";
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
anchorX = globalPos.x - (screen.x || 0) + root.visualContent.width / 2;
|
anchorX = localPos.x;
|
||||||
if (edge === "bottom") {
|
anchorY = edge === "bottom" ? screen.height - barOffset : barOffset;
|
||||||
anchorY = screen.height - barOffset;
|
|
||||||
anchorEdge = "bottom";
|
|
||||||
} else {
|
|
||||||
anchorY = barOffset;
|
|
||||||
anchorEdge = "top";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dndPopupLoader.active = true;
|
dndPopupLoader.active = true;
|
||||||
const popup = dndPopupLoader.item;
|
const popup = dndPopupLoader.item;
|
||||||
if (!popup)
|
if (!popup)
|
||||||
return;
|
return;
|
||||||
popup.showAt(anchorX, anchorY, screen, anchorEdge);
|
|
||||||
|
popup.showAt(anchorX, anchorY, isVertical, edge, screen);
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.MiddleButton
|
||||||
|
onPressed: mouse => {
|
||||||
|
root.triggerRipple(this, mouse.x, mouse.y);
|
||||||
|
SessionData.setDoNotDisturb(!SessionData.doNotDisturb);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
|
|||||||
@@ -1250,13 +1250,8 @@ BasePill {
|
|||||||
|
|
||||||
if (root.isVerticalOrientation) {
|
if (root.isVerticalOrientation) {
|
||||||
const edge = root.axis?.edge;
|
const edge = root.axis?.edge;
|
||||||
if (edge === "left") {
|
const targetX = edge === "left" ? overflowMenu.anchorPos.x : overflowMenu.anchorPos.x - alignedWidth;
|
||||||
const targetX = overflowMenu.anchorPos.x;
|
return Math.max(10, Math.min(overflowMenu.width - alignedWidth - 10, targetX));
|
||||||
return Math.max(left, Math.min(right, targetX));
|
|
||||||
} else {
|
|
||||||
const targetX = overflowMenu.anchorPos.x - alignedWidth;
|
|
||||||
return Math.max(left, Math.min(right, targetX));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const want = overflowMenu.anchorPos.x - alignedWidth / 2;
|
const want = overflowMenu.anchorPos.x - alignedWidth / 2;
|
||||||
return Math.max(left, Math.min(right, want));
|
return Math.max(left, Math.min(right, want));
|
||||||
@@ -1271,13 +1266,8 @@ BasePill {
|
|||||||
const want = overflowMenu.anchorPos.y - alignedHeight / 2;
|
const want = overflowMenu.anchorPos.y - alignedHeight / 2;
|
||||||
return Math.max(top, Math.min(bottom, want));
|
return Math.max(top, Math.min(bottom, want));
|
||||||
} else {
|
} else {
|
||||||
if (root.isAtBottom) {
|
const targetY = root.isAtBottom ? overflowMenu.anchorPos.y - alignedHeight : overflowMenu.anchorPos.y;
|
||||||
const targetY = overflowMenu.anchorPos.y - alignedHeight;
|
return Math.max(10, Math.min(overflowMenu.height - alignedHeight - 10, targetY));
|
||||||
return Math.max(top, Math.min(bottom, targetY));
|
|
||||||
} else {
|
|
||||||
const targetY = overflowMenu.anchorPos.y;
|
|
||||||
return Math.max(top, Math.min(bottom, targetY));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})(), overflowMenu.dpr)
|
})(), overflowMenu.dpr)
|
||||||
|
|
||||||
@@ -1770,13 +1760,8 @@ BasePill {
|
|||||||
|
|
||||||
if (menuRoot.isVertical) {
|
if (menuRoot.isVertical) {
|
||||||
const edge = menuRoot.axis?.edge;
|
const edge = menuRoot.axis?.edge;
|
||||||
if (edge === "left") {
|
const targetX = edge === "left" ? menuWindow.anchorPos.x : menuWindow.anchorPos.x - alignedWidth;
|
||||||
const targetX = menuWindow.anchorPos.x;
|
return Math.max(10, Math.min(menuWindow.width - alignedWidth - 10, targetX));
|
||||||
return Math.max(left, Math.min(right, targetX));
|
|
||||||
} else {
|
|
||||||
const targetX = menuWindow.anchorPos.x - alignedWidth;
|
|
||||||
return Math.max(left, Math.min(right, targetX));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const want = menuWindow.anchorPos.x - alignedWidth / 2;
|
const want = menuWindow.anchorPos.x - alignedWidth / 2;
|
||||||
return Math.max(left, Math.min(right, want));
|
return Math.max(left, Math.min(right, want));
|
||||||
@@ -1791,13 +1776,8 @@ BasePill {
|
|||||||
const want = menuWindow.anchorPos.y - alignedHeight / 2;
|
const want = menuWindow.anchorPos.y - alignedHeight / 2;
|
||||||
return Math.max(top, Math.min(bottom, want));
|
return Math.max(top, Math.min(bottom, want));
|
||||||
} else {
|
} else {
|
||||||
if (menuRoot.isAtBottom) {
|
const targetY = menuRoot.isAtBottom ? menuWindow.anchorPos.y - alignedHeight : menuWindow.anchorPos.y;
|
||||||
const targetY = menuWindow.anchorPos.y - alignedHeight;
|
return Math.max(10, Math.min(menuWindow.height - alignedHeight - 10, targetY));
|
||||||
return Math.max(top, Math.min(bottom, targetY));
|
|
||||||
} else {
|
|
||||||
const targetY = menuWindow.anchorPos.y;
|
|
||||||
return Math.max(top, Math.min(bottom, targetY));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})(), menuWindow.dpr)
|
})(), menuWindow.dpr)
|
||||||
|
|
||||||
|
|||||||
@@ -1079,7 +1079,7 @@ Rectangle {
|
|||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: I18n.tr("Add a task...", "placeholder in the new-task input field")
|
text: I18n.tr("Add a task...", "placeholder in the new-task input field")
|
||||||
color: Theme.onSurface_38
|
color: Theme.outlineButton
|
||||||
visible: taskInput.text.length === 0
|
visible: taskInput.text.length === 0
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQuick.Controls
|
|
||||||
import qs.Common
|
import qs.Common
|
||||||
import qs.Services
|
import qs.Services
|
||||||
import qs.Widgets
|
import qs.Widgets
|
||||||
@@ -19,6 +18,7 @@ Card {
|
|||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
visible: !WeatherService.weather.available
|
visible: !WeatherService.weather.available
|
||||||
|
z: 1
|
||||||
|
|
||||||
DankSpinner {
|
DankSpinner {
|
||||||
size: 24
|
size: 24
|
||||||
@@ -42,9 +42,9 @@ Card {
|
|||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
DankButton {
|
||||||
text: I18n.tr("Refresh")
|
text: I18n.tr("Refresh")
|
||||||
flat: true
|
buttonHeight: 32
|
||||||
visible: !WeatherService.weather.loading
|
visible: !WeatherService.weather.loading
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
onClicked: WeatherService.forceRefresh()
|
onClicked: WeatherService.forceRefresh()
|
||||||
|
|||||||
@@ -141,52 +141,27 @@ Item {
|
|||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankActionButton {
|
||||||
id: refreshButtonTwo
|
id: refreshButtonTwo
|
||||||
name: "refresh"
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.withAlpha(Theme.surfaceText, 0.4)
|
|
||||||
anchors.top: parent.top
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
smoothTransform: isRefreshing
|
iconName: isRefreshing ? "" : "refresh"
|
||||||
|
iconColor: Theme.withAlpha(Theme.surfaceText, 0.4)
|
||||||
property bool isRefreshing: false
|
tooltipText: I18n.tr("Refresh Weather")
|
||||||
|
tooltipSide: "left"
|
||||||
enabled: !isRefreshing
|
enabled: !isRefreshing
|
||||||
|
|
||||||
MouseArea {
|
property bool isRefreshing: false
|
||||||
id: refreshButtonMouseAreaTwo
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor
|
|
||||||
enabled: parent.enabled
|
|
||||||
|
|
||||||
Timer {
|
onClicked: {
|
||||||
id: hoverDelayTwo
|
isRefreshing = true;
|
||||||
interval: 300
|
WeatherService.forceRefresh();
|
||||||
repeat: false
|
refreshTimerTwo.restart();
|
||||||
onTriggered: {
|
|
||||||
refreshButtonTooltipTwo.show(I18n.tr("Refresh Weather"), refreshButtonTwo, 0, 0, "left");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onEntered: {
|
|
||||||
hoverDelayTwo.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: {
|
|
||||||
hoverDelayTwo.stop();
|
|
||||||
refreshButtonTooltipTwo.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
refreshButtonTwo.isRefreshing = true;
|
|
||||||
WeatherService.forceRefresh();
|
|
||||||
refreshTimerTwo.restart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTooltipV2 {
|
DankSpinner {
|
||||||
id: refreshButtonTooltipTwo
|
anchors.centerIn: parent
|
||||||
|
size: refreshButtonTwo.iconSize
|
||||||
|
visible: refreshButtonTwo.isRefreshing
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -194,14 +169,6 @@ Item {
|
|||||||
interval: 2000
|
interval: 2000
|
||||||
onTriggered: refreshButtonTwo.isRefreshing = false
|
onTriggered: refreshButtonTwo.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
RotationAnimator on rotation {
|
|
||||||
running: refreshButtonTwo.isRefreshing
|
|
||||||
from: 0
|
|
||||||
to: 360
|
|
||||||
duration: 1000
|
|
||||||
loops: Animation.Infinite
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -864,52 +831,28 @@ Item {
|
|||||||
onClicked: SessionData.setWeatherHourlyDetailed(!SessionData.weatherHourlyDetailed)
|
onClicked: SessionData.setWeatherHourlyDetailed(!SessionData.weatherHourlyDetailed)
|
||||||
}
|
}
|
||||||
|
|
||||||
DankIcon {
|
DankActionButton {
|
||||||
id: refreshButton
|
id: refreshButton
|
||||||
name: "refresh"
|
|
||||||
size: Theme.iconSize - 4
|
|
||||||
color: Theme.withAlpha(Theme.surfaceText, 0.4)
|
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
smoothTransform: isRefreshing
|
iconName: isRefreshing ? "" : "refresh"
|
||||||
|
iconColor: Theme.withAlpha(Theme.surfaceText, 0.4)
|
||||||
property bool isRefreshing: false
|
tooltipText: I18n.tr("Refresh Weather")
|
||||||
|
tooltipSide: "left"
|
||||||
enabled: !isRefreshing
|
enabled: !isRefreshing
|
||||||
|
|
||||||
MouseArea {
|
property bool isRefreshing: false
|
||||||
id: refreshButtonMouseArea
|
|
||||||
anchors.fill: parent
|
|
||||||
hoverEnabled: true
|
|
||||||
cursorShape: parent.enabled ? Qt.PointingHandCursor : Qt.ForbiddenCursor
|
|
||||||
enabled: parent.enabled
|
|
||||||
|
|
||||||
Timer {
|
onClicked: {
|
||||||
id: hoverDelay
|
isRefreshing = true;
|
||||||
interval: 300
|
WeatherService.forceRefresh();
|
||||||
repeat: false
|
refreshTimer.restart();
|
||||||
onTriggered: {
|
|
||||||
refreshButtonTooltip.show(I18n.tr("Refresh Weather"), refreshButton, 0, 0, "left");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onEntered: {
|
|
||||||
hoverDelay.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
onExited: {
|
|
||||||
hoverDelay.stop();
|
|
||||||
refreshButtonTooltip.hide();
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
refreshButton.isRefreshing = true;
|
|
||||||
WeatherService.forceRefresh();
|
|
||||||
refreshTimer.restart();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DankTooltipV2 {
|
DankSpinner {
|
||||||
id: refreshButtonTooltip
|
anchors.centerIn: parent
|
||||||
|
size: refreshButton.iconSize
|
||||||
|
visible: refreshButton.isRefreshing
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
@@ -917,14 +860,6 @@ Item {
|
|||||||
interval: 2000
|
interval: 2000
|
||||||
onTriggered: refreshButton.isRefreshing = false
|
onTriggered: refreshButton.isRefreshing = false
|
||||||
}
|
}
|
||||||
|
|
||||||
RotationAnimator on rotation {
|
|
||||||
running: refreshButton.isRefreshing
|
|
||||||
from: 0
|
|
||||||
to: 360
|
|
||||||
duration: 1000
|
|
||||||
loops: Animation.Infinite
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -438,20 +438,55 @@ Variants {
|
|||||||
parent: dock.contentItem
|
parent: dock.contentItem
|
||||||
visible: false
|
visible: false
|
||||||
readonly property bool expanded: dock.reveal
|
readonly property bool expanded: dock.reveal
|
||||||
|
readonly property bool chrome: dock.usesConnectedFrameChrome
|
||||||
|
readonly property bool atEndEdge: SettingsData.dockPosition === SettingsData.Position.Bottom || SettingsData.dockPosition === SettingsData.Position.Right
|
||||||
|
readonly property real innerReach: borderThickness + (SettingsData.appsDockEnlargeOnHover ? animationHeadroom : 0)
|
||||||
|
readonly property real bodyX: dockBackground.x + dockContainer.x + dockMouseArea.x + dockCore.x + dockSlide.x
|
||||||
|
readonly property real bodyY: dockBackground.y + dockContainer.y + dockMouseArea.y + dockCore.y + dockSlide.y
|
||||||
x: {
|
x: {
|
||||||
const baseX = dockCore.x + dockMouseArea.x;
|
if (chrome) {
|
||||||
if (isVertical && SettingsData.dockPosition === SettingsData.Position.Right)
|
const baseX = dockCore.x + dockMouseArea.x;
|
||||||
return baseX - (expanded ? animationHeadroom + borderThickness + dock.horizontalConnectorExtent : 0);
|
if (isVertical && atEndEdge)
|
||||||
return baseX - (expanded ? borderThickness + dock.horizontalConnectorExtent : 0);
|
return baseX - (expanded ? animationHeadroom + borderThickness + dock.horizontalConnectorExtent : 0);
|
||||||
|
return baseX - (expanded ? borderThickness + dock.horizontalConnectorExtent : 0);
|
||||||
|
}
|
||||||
|
if (!isVertical)
|
||||||
|
return dockCore.x + dockMouseArea.x - (expanded ? borderThickness : 0);
|
||||||
|
if (!atEndEdge)
|
||||||
|
return 0;
|
||||||
|
return expanded ? Math.min(bodyX - innerReach, dock.width - 1) : dock.width - 1;
|
||||||
}
|
}
|
||||||
y: {
|
y: {
|
||||||
const baseY = dockCore.y + dockMouseArea.y;
|
if (chrome) {
|
||||||
if (!isVertical && SettingsData.dockPosition === SettingsData.Position.Bottom)
|
const baseY = dockCore.y + dockMouseArea.y;
|
||||||
return baseY - (expanded ? animationHeadroom + borderThickness + dock.verticalConnectorExtent : 0);
|
if (!isVertical && atEndEdge)
|
||||||
return baseY - (expanded ? borderThickness + dock.verticalConnectorExtent : 0);
|
return baseY - (expanded ? animationHeadroom + borderThickness + dock.verticalConnectorExtent : 0);
|
||||||
|
return baseY - (expanded ? borderThickness + dock.verticalConnectorExtent : 0);
|
||||||
|
}
|
||||||
|
if (isVertical)
|
||||||
|
return dockCore.y + dockMouseArea.y - (expanded ? borderThickness : 0);
|
||||||
|
if (!atEndEdge)
|
||||||
|
return 0;
|
||||||
|
return expanded ? Math.min(bodyY - innerReach, dock.height - 1) : dock.height - 1;
|
||||||
|
}
|
||||||
|
width: {
|
||||||
|
if (chrome)
|
||||||
|
return dockMouseArea.width + (isVertical && expanded ? animationHeadroom : 0) + (expanded ? borderThickness * 2 + dock.horizontalConnectorExtent * 2 : 0);
|
||||||
|
if (!isVertical)
|
||||||
|
return dockMouseArea.width + (expanded ? borderThickness * 2 : 0);
|
||||||
|
if (!expanded)
|
||||||
|
return 1;
|
||||||
|
return atEndEdge ? dock.width - x : Math.max(bodyX + dockBackground.width + innerReach, 1);
|
||||||
|
}
|
||||||
|
height: {
|
||||||
|
if (chrome)
|
||||||
|
return dockMouseArea.height + (!isVertical && expanded ? animationHeadroom : 0) + (expanded ? borderThickness * 2 + dock.verticalConnectorExtent * 2 : 0);
|
||||||
|
if (isVertical)
|
||||||
|
return dockMouseArea.height + (expanded ? borderThickness * 2 : 0);
|
||||||
|
if (!expanded)
|
||||||
|
return 1;
|
||||||
|
return atEndEdge ? dock.height - y : Math.max(bodyY + dockBackground.height + innerReach, 1);
|
||||||
}
|
}
|
||||||
width: dockMouseArea.width + (isVertical && expanded ? animationHeadroom : 0) + (expanded ? borderThickness * 2 + dock.horizontalConnectorExtent * 2 : 0)
|
|
||||||
height: dockMouseArea.height + (!isVertical && expanded ? animationHeadroom : 0) + (expanded ? borderThickness * 2 + dock.verticalConnectorExtent * 2 : 0)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mask: Region {
|
mask: Region {
|
||||||
|
|||||||
@@ -479,6 +479,8 @@ Item {
|
|||||||
|
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
id: delegateItem
|
id: delegateItem
|
||||||
|
required property var modelData
|
||||||
|
required property int index
|
||||||
|
|
||||||
property var dockButton: {
|
property var dockButton: {
|
||||||
switch (itemData.type) {
|
switch (itemData.type) {
|
||||||
@@ -599,7 +601,7 @@ Item {
|
|||||||
height: delegateItem.height
|
height: delegateItem.height
|
||||||
actualIconSize: root.iconSize
|
actualIconSize: root.iconSize
|
||||||
dockApps: root
|
dockApps: root
|
||||||
index: model.index
|
index: delegateItem.index
|
||||||
}
|
}
|
||||||
|
|
||||||
DockTrashButton {
|
DockTrashButton {
|
||||||
@@ -624,7 +626,7 @@ Item {
|
|||||||
appData: itemData
|
appData: itemData
|
||||||
contextMenu: root.contextMenu
|
contextMenu: root.contextMenu
|
||||||
dockApps: root
|
dockApps: root
|
||||||
index: model.index
|
index: delegateItem.index
|
||||||
parentDockScreen: root.dockScreen
|
parentDockScreen: root.dockScreen
|
||||||
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
|
showWindowTitle: itemData?.type === "window" || itemData?.type === "grouped"
|
||||||
windowTitle: {
|
windowTitle: {
|
||||||
|
|||||||
@@ -1773,6 +1773,7 @@ Item {
|
|||||||
const idx = GreeterState.sessionList.indexOf(value);
|
const idx = GreeterState.sessionList.indexOf(value);
|
||||||
if (idx < 0)
|
if (idx < 0)
|
||||||
return;
|
return;
|
||||||
|
GreeterState.sessionManuallySelected = true;
|
||||||
GreeterState.currentSessionIndex = idx;
|
GreeterState.currentSessionIndex = idx;
|
||||||
GreeterState.selectedSession = GreeterState.sessionExecs[idx];
|
GreeterState.selectedSession = GreeterState.sessionExecs[idx];
|
||||||
GreeterState.selectedSessionPath = GreeterState.sessionPaths[idx];
|
GreeterState.selectedSessionPath = GreeterState.sessionPaths[idx];
|
||||||
@@ -1785,6 +1786,8 @@ Item {
|
|||||||
property string currentSessionName: GreeterState.sessionList[GreeterState.currentSessionIndex] || ""
|
property string currentSessionName: GreeterState.sessionList[GreeterState.currentSessionIndex] || ""
|
||||||
|
|
||||||
function finalizeSessionSelection() {
|
function finalizeSessionSelection() {
|
||||||
|
if (GreeterState.sessionManuallySelected)
|
||||||
|
return;
|
||||||
if (GreeterState.sessionList.length === 0)
|
if (GreeterState.sessionList.length === 0)
|
||||||
return;
|
return;
|
||||||
if (!GreetdMemory.memoryReady)
|
if (!GreetdMemory.memoryReady)
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Singleton {
|
|||||||
property var sessionPaths: []
|
property var sessionPaths: []
|
||||||
property var sessionDesktopIds: []
|
property var sessionDesktopIds: []
|
||||||
property int currentSessionIndex: 0
|
property int currentSessionIndex: 0
|
||||||
|
property bool sessionManuallySelected: false
|
||||||
property var availableUsers: []
|
property var availableUsers: []
|
||||||
property int selectedUserIndex: -1
|
property int selectedUserIndex: -1
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import QtQuick
|
|||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Wayland
|
import Quickshell.Wayland
|
||||||
import qs.Common
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property bool active: false
|
property bool active: false
|
||||||
|
property bool _completed: false
|
||||||
|
|
||||||
signal fadeCompleted
|
signal fadeCompleted
|
||||||
signal fadeCancelled
|
signal fadeCancelled
|
||||||
@@ -35,7 +37,8 @@ PanelWindow {
|
|||||||
opacity: 0
|
opacity: 0
|
||||||
|
|
||||||
onOpacityChanged: {
|
onOpacityChanged: {
|
||||||
if (opacity >= 0.99 && root.active) {
|
if (opacity >= 0.99 && root.active && !root._completed) {
|
||||||
|
root._completed = true;
|
||||||
root.fadeCompleted();
|
root.fadeCompleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,6 +61,7 @@ PanelWindow {
|
|||||||
function startFade() {
|
function startFade() {
|
||||||
if (!SettingsData.fadeToDpmsEnabled)
|
if (!SettingsData.fadeToDpmsEnabled)
|
||||||
return;
|
return;
|
||||||
|
_completed = false;
|
||||||
active = true;
|
active = true;
|
||||||
fadeOverlay.opacity = 0.0;
|
fadeOverlay.opacity = 0.0;
|
||||||
fadeSeq.stop();
|
fadeSeq.stop();
|
||||||
@@ -65,10 +69,24 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cancelFade() {
|
function cancelFade() {
|
||||||
|
dismiss();
|
||||||
|
fadeCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
|
function dismiss() {
|
||||||
fadeSeq.stop();
|
fadeSeq.stop();
|
||||||
fadeOverlay.opacity = 0.0;
|
fadeOverlay.opacity = 0.0;
|
||||||
active = false;
|
active = false;
|
||||||
fadeCancelled();
|
_completed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: IdleService
|
||||||
|
function onIsShellLockedChanged() {
|
||||||
|
if (IdleService.isShellLocked)
|
||||||
|
return;
|
||||||
|
root.dismiss();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
|||||||
@@ -27,11 +27,14 @@ Item {
|
|||||||
function hide() {
|
function hide() {
|
||||||
if (isKeyboardActive && keyboard !== null) {
|
if (isKeyboardActive && keyboard !== null) {
|
||||||
keyboard.destroy();
|
keyboard.destroy();
|
||||||
|
keyboard = null;
|
||||||
isKeyboardActive = false;
|
isKeyboardActive = false;
|
||||||
} else
|
} else
|
||||||
log.debug("The keyboard is already hidden");
|
log.debug("The keyboard is already hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component.onDestruction: hide()
|
||||||
|
|
||||||
// private
|
// private
|
||||||
property Item keyboard: null
|
property Item keyboard: null
|
||||||
Component {
|
Component {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import qs.Services
|
|||||||
Scope {
|
Scope {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property var log: Log.scoped("Lock")
|
||||||
|
|
||||||
property string sharedPasswordBuffer: ""
|
property string sharedPasswordBuffer: ""
|
||||||
property bool shouldLock: false
|
property bool shouldLock: false
|
||||||
|
|
||||||
@@ -21,26 +23,13 @@ Scope {
|
|||||||
|
|
||||||
onShouldLockChanged: {
|
onShouldLockChanged: {
|
||||||
IdleService.isShellLocked = shouldLock;
|
IdleService.isShellLocked = shouldLock;
|
||||||
if (shouldLock && lockPowerOffArmed) {
|
|
||||||
lockStateCheck.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: lockStateCheck
|
|
||||||
interval: 100
|
|
||||||
repeat: false
|
|
||||||
onTriggered: {
|
|
||||||
if (sessionLock.locked && lockPowerOffArmed) {
|
|
||||||
pendingLock = false;
|
|
||||||
lockPowerOffArmed = false;
|
|
||||||
beginPowerOff();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
property bool lockInitiatedLocally: false
|
property bool lockInitiatedLocally: false
|
||||||
property bool pendingLock: false
|
property bool pendingLock: false
|
||||||
|
readonly property int maxLockRetries: 3
|
||||||
|
property int lockRetryAttempts: 0
|
||||||
|
property bool lockRetryPending: false
|
||||||
property bool lockPowerOffArmed: false
|
property bool lockPowerOffArmed: false
|
||||||
property bool lockWakeAllowed: false
|
property bool lockWakeAllowed: false
|
||||||
property bool customLockerSpawned: false
|
property bool customLockerSpawned: false
|
||||||
@@ -116,6 +105,24 @@ Scope {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resetLockRetry() {
|
||||||
|
lockRetryAttempts = 0;
|
||||||
|
lockRetryPending = false;
|
||||||
|
lockRetryTimer.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleLockLost() {
|
||||||
|
if (lockRetryAttempts >= maxLockRetries) {
|
||||||
|
log.error("Compositor refused session lock", maxLockRetries, "times - resetting lock state");
|
||||||
|
forceReset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lockRetryAttempts++;
|
||||||
|
lockRetryPending = true;
|
||||||
|
lockRetryTimer.restart();
|
||||||
|
log.warn("Session lock lost while lock requested - retry", lockRetryAttempts, "/", maxLockRetries);
|
||||||
|
}
|
||||||
|
|
||||||
function lock() {
|
function lock() {
|
||||||
if (SettingsData.customPowerActionLock?.length > 0) {
|
if (SettingsData.customPowerActionLock?.length > 0) {
|
||||||
spawnCustomLocker();
|
spawnCustomLocker();
|
||||||
@@ -124,6 +131,7 @@ Scope {
|
|||||||
if (shouldLock || pendingLock)
|
if (shouldLock || pendingLock)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
resetLockRetry();
|
||||||
lockInitiatedLocally = true;
|
lockInitiatedLocally = true;
|
||||||
lockPowerOffArmed = powerOffOnLock;
|
lockPowerOffArmed = powerOffOnLock;
|
||||||
|
|
||||||
@@ -139,7 +147,7 @@ Scope {
|
|||||||
|
|
||||||
function lockAndOutputsOff() {
|
function lockAndOutputsOff() {
|
||||||
IdleService.lockPowerOffRequested = true;
|
IdleService.lockPowerOffRequested = true;
|
||||||
if (sessionLock.locked) {
|
if (sessionLock.secure) {
|
||||||
beginPowerOff();
|
beginPowerOff();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -150,6 +158,7 @@ Scope {
|
|||||||
function unlock() {
|
function unlock() {
|
||||||
if (!shouldLock)
|
if (!shouldLock)
|
||||||
return;
|
return;
|
||||||
|
resetLockRetry();
|
||||||
lockInitiatedLocally = false;
|
lockInitiatedLocally = false;
|
||||||
notifyLoginctl(false);
|
notifyLoginctl(false);
|
||||||
shouldLock = false;
|
shouldLock = false;
|
||||||
@@ -160,6 +169,7 @@ Scope {
|
|||||||
pendingLock = false;
|
pendingLock = false;
|
||||||
shouldLock = false;
|
shouldLock = false;
|
||||||
customLockerSpawned = false;
|
customLockerSpawned = false;
|
||||||
|
resetLockRetry();
|
||||||
resetPowerOffFade();
|
resetPowerOffFade();
|
||||||
IdleService.lockPowerOffRequested = false;
|
IdleService.lockPowerOffRequested = false;
|
||||||
}
|
}
|
||||||
@@ -198,6 +208,12 @@ Scope {
|
|||||||
shouldLock = false;
|
shouldLock = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onSessionResumed() {
|
||||||
|
if (!shouldLock || sessionLock.locked)
|
||||||
|
return;
|
||||||
|
resumeRelockTimer.restart();
|
||||||
|
}
|
||||||
|
|
||||||
function onLoginctlStateChanged() {
|
function onLoginctlStateChanged() {
|
||||||
if (SessionService.active && pendingLock) {
|
if (SessionService.active && pendingLock) {
|
||||||
pendingLock = false;
|
pendingLock = false;
|
||||||
@@ -239,7 +255,7 @@ Scope {
|
|||||||
WlSessionLock {
|
WlSessionLock {
|
||||||
id: sessionLock
|
id: sessionLock
|
||||||
|
|
||||||
locked: shouldLock
|
locked: shouldLock && !lockRetryPending
|
||||||
|
|
||||||
WlSessionLockSurface {
|
WlSessionLockSurface {
|
||||||
id: lockSurface
|
id: lockSurface
|
||||||
@@ -312,16 +328,23 @@ Scope {
|
|||||||
Connections {
|
Connections {
|
||||||
target: sessionLock
|
target: sessionLock
|
||||||
|
|
||||||
function onLockedChanged() {
|
function onSecureChanged() {
|
||||||
notifyLockedHint(sessionLock.locked);
|
notifyLockedHint(sessionLock.secure);
|
||||||
if (sessionLock.locked) {
|
if (!sessionLock.secure)
|
||||||
pendingLock = false;
|
|
||||||
if (lockPowerOffArmed && powerOffOnLock)
|
|
||||||
beginPowerOff();
|
|
||||||
lockPowerOffArmed = false;
|
|
||||||
return;
|
return;
|
||||||
}
|
IdleService.dismissFadeToLock();
|
||||||
|
lockRetryAttempts = 0;
|
||||||
|
pendingLock = false;
|
||||||
|
if (lockPowerOffArmed && powerOffOnLock)
|
||||||
|
beginPowerOff();
|
||||||
|
lockPowerOffArmed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLockedChanged() {
|
||||||
|
if (sessionLock.locked)
|
||||||
|
return;
|
||||||
|
if (shouldLock && (IdleService.monitorsOff || lockPowerOffArmed || IdleService.lockPowerOffRequested))
|
||||||
|
return;
|
||||||
lockWakeAllowed = false;
|
lockWakeAllowed = false;
|
||||||
resetPowerOffFade();
|
resetPowerOffFade();
|
||||||
if (IdleService.monitorsOff && powerOffOnLock) {
|
if (IdleService.monitorsOff && powerOffOnLock) {
|
||||||
@@ -329,6 +352,8 @@ Scope {
|
|||||||
CompositorService.powerOnMonitors();
|
CompositorService.powerOnMonitors();
|
||||||
}
|
}
|
||||||
IdleService.lockPowerOffRequested = false;
|
IdleService.lockPowerOffRequested = false;
|
||||||
|
if (shouldLock && !lockRetryPending)
|
||||||
|
handleLockLost();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,13 +385,15 @@ Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isLocked(): bool {
|
function isLocked(): bool {
|
||||||
return sessionLock.locked;
|
return sessionLock.secure;
|
||||||
}
|
}
|
||||||
|
|
||||||
function status(): string {
|
function status(): string {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
shouldLock: root.shouldLock,
|
shouldLock: root.shouldLock,
|
||||||
sessionLockLocked: sessionLock.locked,
|
sessionLockLocked: sessionLock.locked,
|
||||||
|
sessionLockSecure: sessionLock.secure,
|
||||||
|
lockRetryAttempts: root.lockRetryAttempts,
|
||||||
lockInitiatedLocally: root.lockInitiatedLocally,
|
lockInitiatedLocally: root.lockInitiatedLocally,
|
||||||
pendingLock: root.pendingLock,
|
pendingLock: root.pendingLock,
|
||||||
loginctlLocked: SessionService.locked,
|
loginctlLocked: SessionService.locked,
|
||||||
@@ -395,6 +422,29 @@ Scope {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: lockRetryTimer
|
||||||
|
interval: 1000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: root.lockRetryPending = false
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: resumeRelockTimer
|
||||||
|
interval: 1000
|
||||||
|
repeat: false
|
||||||
|
onTriggered: {
|
||||||
|
if (!root.shouldLock || sessionLock.locked)
|
||||||
|
return;
|
||||||
|
if (IdleService.monitorsOff || root.lockPowerOffArmed || IdleService.lockPowerOffRequested)
|
||||||
|
return;
|
||||||
|
root.log.warn("Session lock dead after resume - re-locking");
|
||||||
|
root.resetLockRetry();
|
||||||
|
root.lockRetryPending = true;
|
||||||
|
root.lockRetryPending = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: dpmsReapplyTimer
|
id: dpmsReapplyTimer
|
||||||
interval: 100
|
interval: 100
|
||||||
@@ -407,7 +457,7 @@ Scope {
|
|||||||
interval: 200
|
interval: 200
|
||||||
repeat: false
|
repeat: false
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (!sessionLock.locked)
|
if (!sessionLock.secure)
|
||||||
return;
|
return;
|
||||||
if (!powerOffOnLock)
|
if (!powerOffOnLock)
|
||||||
return;
|
return;
|
||||||
@@ -426,7 +476,7 @@ Scope {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
enabled: sessionLock.locked
|
enabled: sessionLock.secure
|
||||||
hoverEnabled: enabled
|
hoverEnabled: enabled
|
||||||
onPressed: lockWakeDebounce.restart()
|
onPressed: lockWakeDebounce.restart()
|
||||||
onPositionChanged: lockWakeDebounce.restart()
|
onPositionChanged: lockWakeDebounce.restart()
|
||||||
@@ -435,10 +485,10 @@ Scope {
|
|||||||
|
|
||||||
FocusScope {
|
FocusScope {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: sessionLock.locked
|
focus: sessionLock.secure
|
||||||
|
|
||||||
Keys.onPressed: event => {
|
Keys.onPressed: event => {
|
||||||
if (!sessionLock.locked)
|
if (!sessionLock.secure)
|
||||||
return;
|
return;
|
||||||
lockWakeDebounce.restart();
|
lockWakeDebounce.restart();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ Item {
|
|||||||
property int hyprlandLayoutCount: 0
|
property int hyprlandLayoutCount: 0
|
||||||
property bool lockerReadySent: false
|
property bool lockerReadySent: false
|
||||||
property bool lockerReadyArmed: false
|
property bool lockerReadyArmed: false
|
||||||
|
property var sessionLock: null
|
||||||
readonly property bool hasCustomWallpaper: SettingsData.lockScreenWallpaperPath !== ""
|
readonly property bool hasCustomWallpaper: SettingsData.lockScreenWallpaperPath !== ""
|
||||||
readonly property string lockFontFamily: SettingsData.lockScreenFontFamily
|
readonly property string lockFontFamily: SettingsData.lockScreenFontFamily
|
||||||
|
|
||||||
@@ -111,6 +112,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function sendLockerReadyOnce() {
|
function sendLockerReadyOnce() {
|
||||||
|
if (root.demoMode)
|
||||||
|
return;
|
||||||
if (lockerReadySent)
|
if (lockerReadySent)
|
||||||
return;
|
return;
|
||||||
if (root.unlocking)
|
if (root.unlocking)
|
||||||
@@ -133,12 +136,27 @@ Item {
|
|||||||
return;
|
return;
|
||||||
if (!root.visible || root.opacity <= 0)
|
if (!root.visible || root.opacity <= 0)
|
||||||
return;
|
return;
|
||||||
|
// Don't report ready until the compositor has confirmed the session is
|
||||||
|
// locked (ext-session-lock `locked` event). Qt's afterRendering fires
|
||||||
|
// before the lock surface is committed/presented, so releasing the sleep
|
||||||
|
// inhibitor on it lets the machine freeze with the desktop still on screen,
|
||||||
|
// which then flashes on resume. secure=true guarantees the desktop is hidden.
|
||||||
|
if (root.sessionLock && !root.sessionLock.secure)
|
||||||
|
return;
|
||||||
Qt.callLater(() => {
|
Qt.callLater(() => {
|
||||||
if (root.visible && root.opacity > 0 && !root.unlocking)
|
if (root.visible && root.opacity > 0 && !root.unlocking)
|
||||||
sendLockerReadyOnce();
|
sendLockerReadyOnce();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root.sessionLock
|
||||||
|
enabled: target !== null
|
||||||
|
function onSecureChanged() {
|
||||||
|
root.maybeSend();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: root.Window.window
|
target: root.Window.window
|
||||||
enabled: target !== null
|
enabled: target !== null
|
||||||
@@ -778,7 +796,7 @@ Item {
|
|||||||
return "passkey";
|
return "passkey";
|
||||||
if (pam.fprint.tries >= SettingsData.maxFprintTries)
|
if (pam.fprint.tries >= SettingsData.maxFprintTries)
|
||||||
return "fingerprint_off";
|
return "fingerprint_off";
|
||||||
if (pam.fprint.active)
|
if (pam.fprint.active || pam.fprint.retrying)
|
||||||
return "fingerprint";
|
return "fingerprint";
|
||||||
if (pam.u2f.active)
|
if (pam.u2f.active)
|
||||||
return "passkey";
|
return "passkey";
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ FocusScope {
|
|||||||
|
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
demoMode: false
|
demoMode: false
|
||||||
|
sessionLock: root.lock
|
||||||
pam: root.pam
|
pam: root.pam
|
||||||
passwordBuffer: root.sharedPasswordBuffer
|
passwordBuffer: root.sharedPasswordBuffer
|
||||||
screenName: root.screenName
|
screenName: root.screenName
|
||||||
|
|||||||
@@ -262,9 +262,11 @@ Scope {
|
|||||||
property bool available: SettingsData.lockFingerprintReady
|
property bool available: SettingsData.lockFingerprintReady
|
||||||
property int tries
|
property int tries
|
||||||
property int errorTries
|
property int errorTries
|
||||||
|
property bool retrying: false
|
||||||
|
|
||||||
function checkAvail(): void {
|
function checkAvail(): void {
|
||||||
if (!available || !SettingsData.enableFprint || !root.lockSecured || root.fprintSuppressedByPrimaryPam) {
|
if (!available || !SettingsData.enableFprint || !root.lockSecured || root.fprintSuppressedByPrimaryPam) {
|
||||||
|
retrying = false;
|
||||||
abort();
|
abort();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -273,6 +275,7 @@ Scope {
|
|||||||
|
|
||||||
tries = 0;
|
tries = 0;
|
||||||
errorTries = 0;
|
errorTries = 0;
|
||||||
|
retrying = false;
|
||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -285,6 +288,7 @@ Scope {
|
|||||||
|
|
||||||
switch (res) {
|
switch (res) {
|
||||||
case PamResult.Success:
|
case PamResult.Success:
|
||||||
|
retrying = false;
|
||||||
if (!root.unlockInProgress) {
|
if (!root.unlockInProgress) {
|
||||||
passwd.abort();
|
passwd.abort();
|
||||||
root.proceedAfterPrimaryAuth();
|
root.proceedAfterPrimaryAuth();
|
||||||
@@ -293,13 +297,16 @@ Scope {
|
|||||||
case PamResult.Error:
|
case PamResult.Error:
|
||||||
errorTries++;
|
errorTries++;
|
||||||
if (errorTries < 200) {
|
if (errorTries < 200) {
|
||||||
|
retrying = true;
|
||||||
abort();
|
abort();
|
||||||
errorRetry.restart();
|
errorRetry.restart();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
retrying = false;
|
||||||
abort();
|
abort();
|
||||||
return;
|
return;
|
||||||
case PamResult.MaxTries:
|
case PamResult.MaxTries:
|
||||||
|
retrying = false;
|
||||||
tries++;
|
tries++;
|
||||||
if (tries < SettingsData.maxFprintTries) {
|
if (tries < SettingsData.maxFprintTries) {
|
||||||
root.fprintState = "fail";
|
root.fprintState = "fail";
|
||||||
@@ -418,7 +425,7 @@ Scope {
|
|||||||
Timer {
|
Timer {
|
||||||
id: errorRetry
|
id: errorRetry
|
||||||
|
|
||||||
interval: 1500
|
interval: Math.min(1500 * Math.pow(2, Math.max(0, fprint.errorTries - 1)), 30000)
|
||||||
onTriggered: fprint.start()
|
onTriggered: fprint.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,14 +31,16 @@ PanelWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
property point anchorPos: Qt.point(0, 0)
|
property point anchorPos: Qt.point(0, 0)
|
||||||
property string anchorEdge: "top"
|
property bool isVertical: false
|
||||||
|
property string edge: "top"
|
||||||
visible: false
|
visible: false
|
||||||
|
|
||||||
function showAt(x, y, targetScreen, edge) {
|
function showAt(x, y, vertical, barEdge, targetScreen) {
|
||||||
if (targetScreen)
|
if (targetScreen)
|
||||||
root.screen = targetScreen;
|
root.screen = targetScreen;
|
||||||
anchorPos = Qt.point(x, y);
|
anchorPos = Qt.point(x, y);
|
||||||
anchorEdge = edge || "top";
|
isVertical = vertical ?? false;
|
||||||
|
edge = barEdge ?? "top";
|
||||||
visible = true;
|
visible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,21 +68,19 @@ PanelWindow {
|
|||||||
visible: root.visible
|
visible: root.visible
|
||||||
|
|
||||||
x: {
|
x: {
|
||||||
const left = 10;
|
if (root.isVertical) {
|
||||||
const right = root.width - width - 10;
|
if (root.edge === "left")
|
||||||
const want = root.anchorPos.x - width / 2;
|
return Math.min(root.width - width - 10, root.anchorPos.x);
|
||||||
return Math.max(left, Math.min(right, want));
|
return Math.max(10, root.anchorPos.x - width);
|
||||||
|
}
|
||||||
|
return Math.max(10, Math.min(root.width - width - 10, root.anchorPos.x - width / 2));
|
||||||
}
|
}
|
||||||
y: {
|
y: {
|
||||||
switch (root.anchorEdge) {
|
if (root.isVertical)
|
||||||
case "bottom":
|
|
||||||
return Math.max(10, root.anchorPos.y - height);
|
|
||||||
case "left":
|
|
||||||
case "right":
|
|
||||||
return Math.max(10, Math.min(root.height - height - 10, root.anchorPos.y - height / 2));
|
return Math.max(10, Math.min(root.height - height - 10, root.anchorPos.y - height / 2));
|
||||||
default:
|
if (root.edge === "bottom")
|
||||||
return Math.min(root.height - height - 10, root.anchorPos.y);
|
return Math.max(10, root.anchorPos.y - height);
|
||||||
}
|
return Math.min(root.height - height - 10, root.anchorPos.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDismissed: root.closeMenu()
|
onDismissed: root.closeMenu()
|
||||||
|
|||||||
@@ -61,6 +61,11 @@ DankPopout {
|
|||||||
notificationHistoryVisible = !notificationHistoryVisible;
|
notificationHistoryVisible = !notificationHistoryVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Re-open without toggling the flag (used when retargeting to another monitor).
|
||||||
|
function present() {
|
||||||
|
openSized();
|
||||||
|
}
|
||||||
|
|
||||||
function openSized() {
|
function openSized() {
|
||||||
if (!notificationHistoryVisible)
|
if (!notificationHistoryVisible)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ PanelWindow {
|
|||||||
blurWidth: !win._finalized && !win.connectedFrameMode ? innerW * s : 0
|
blurWidth: !win._finalized && !win.connectedFrameMode ? innerW * s : 0
|
||||||
blurHeight: !win._finalized && !win.connectedFrameMode ? innerH * s : 0
|
blurHeight: !win._finalized && !win.connectedFrameMode ? innerH * s : 0
|
||||||
blurRadius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
|
blurRadius: win.connectedFrameMode ? Theme.connectedSurfaceRadius : Theme.cornerRadius
|
||||||
|
// Slide-out translates the card past the surface edge; intersecting with the
|
||||||
|
// resting bounds keeps the committed region on the visible content instead of
|
||||||
|
// leaving a stray blur sliver at the edge (#2917).
|
||||||
|
clipEnabled: true
|
||||||
|
clipX: content.x + content.cardInset
|
||||||
|
clipY: content.y + content.cardInset
|
||||||
|
clipWidth: innerW
|
||||||
|
clipHeight: innerH
|
||||||
}
|
}
|
||||||
|
|
||||||
WlrLayershell.namespace: "dms:notification-popup"
|
WlrLayershell.namespace: "dms:notification-popup"
|
||||||
|
|||||||
@@ -375,6 +375,8 @@ Item {
|
|||||||
visible: DesktopService.autostartAvailable
|
visible: DesktopService.autostartAvailable
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "autostartAddEntry"
|
||||||
|
tags: ["autostart", "add", "entry", "command", "startup"]
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "add_circle"
|
iconName: "add_circle"
|
||||||
title: I18n.tr("Add Entry")
|
title: I18n.tr("Add Entry")
|
||||||
@@ -764,6 +766,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "autostartTrayIconFix"
|
||||||
|
tags: ["tray", "icons", "fix", "systemd"]
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "handyman"
|
iconName: "handyman"
|
||||||
title: I18n.tr("Tray Icon Fix")
|
title: I18n.tr("Tray Icon Fix")
|
||||||
|
|||||||
@@ -463,6 +463,7 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "barEnable"
|
||||||
iconName: selectedBarConfig?.enabled ? "visibility" : "visibility_off"
|
iconName: selectedBarConfig?.enabled ? "visibility" : "visibility_off"
|
||||||
title: I18n.tr("Enable Bar")
|
title: I18n.tr("Enable Bar")
|
||||||
visible: !dankBarTab.appearanceOnly && selectedBarId !== "default"
|
visible: !dankBarTab.appearanceOnly && selectedBarId !== "default"
|
||||||
@@ -651,6 +652,8 @@ Item {
|
|||||||
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barAutoHide"
|
||||||
|
tags: ["autohide", "auto-hide", "reveal", "intellihide"]
|
||||||
text: I18n.tr("Auto-hide")
|
text: I18n.tr("Auto-hide")
|
||||||
description: I18n.tr("Automatically hide the bar when the pointer moves away")
|
description: I18n.tr("Automatically hide the bar when the pointer moves away")
|
||||||
checked: selectedBarConfig?.autoHide ?? false
|
checked: selectedBarConfig?.autoHide ?? false
|
||||||
@@ -700,6 +703,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barAutoHideStrict"
|
||||||
|
tags: ["autohide", "strict", "popout"]
|
||||||
width: parent.width - parent.leftPadding
|
width: parent.width - parent.leftPadding
|
||||||
text: I18n.tr("Strict auto-hide", "Dank bar setting: hide the bar when the pointer leaves even if a menu or bar popover is still open")
|
text: I18n.tr("Strict auto-hide", "Dank bar setting: hide the bar when the pointer leaves even if a menu or bar popover is still open")
|
||||||
description: I18n.tr("Hide the bar when the pointer leaves even if a popout is still open")
|
description: I18n.tr("Hide the bar when the pointer leaves even if a popout is still open")
|
||||||
@@ -713,6 +718,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barHideWhenWindowsOpen"
|
||||||
|
tags: ["hide", "windows", "empty", "workspace"]
|
||||||
width: parent.width - parent.leftPadding
|
width: parent.width - parent.leftPadding
|
||||||
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
|
visible: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isMango
|
||||||
text: I18n.tr("Hide When Windows Open")
|
text: I18n.tr("Hide When Windows Open")
|
||||||
@@ -734,6 +741,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barManualVisibility"
|
||||||
|
tags: ["manual", "show", "hide", "ipc", "toggle"]
|
||||||
text: I18n.tr("Manual Show/Hide")
|
text: I18n.tr("Manual Show/Hide")
|
||||||
description: I18n.tr("Toggle bar visibility manually via IPC")
|
description: I18n.tr("Toggle bar visibility manually via IPC")
|
||||||
checked: selectedBarConfig?.visible ?? true
|
checked: selectedBarConfig?.visible ?? true
|
||||||
@@ -753,6 +762,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barClickThrough"
|
||||||
|
tags: ["clickthrough", "click", "through", "mouse", "input", "mask", "passthrough"]
|
||||||
text: I18n.tr("Click Through")
|
text: I18n.tr("Click Through")
|
||||||
description: I18n.tr("Mouse clicks pass through the bar to windows behind it")
|
description: I18n.tr("Mouse clicks pass through the bar to windows behind it")
|
||||||
checked: selectedBarConfig?.clickThrough ?? false
|
checked: selectedBarConfig?.clickThrough ?? false
|
||||||
@@ -910,6 +921,8 @@ Item {
|
|||||||
|
|
||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
id: exclusiveZoneSlider
|
id: exclusiveZoneSlider
|
||||||
|
settingKey: "barExclusiveZone"
|
||||||
|
tags: ["exclusive", "zone", "reserved", "offset"]
|
||||||
visible: !SettingsData.frameEnabled
|
visible: !SettingsData.frameEnabled
|
||||||
text: I18n.tr("Exclusive Zone Offset")
|
text: I18n.tr("Exclusive Zone Offset")
|
||||||
description: I18n.tr("Fine-tune the space reserved for the bar from the screen edge")
|
description: I18n.tr("Fine-tune the space reserved for the bar from the screen edge")
|
||||||
@@ -933,6 +946,8 @@ Item {
|
|||||||
|
|
||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
id: sizeSlider
|
id: sizeSlider
|
||||||
|
settingKey: "barSize"
|
||||||
|
tags: ["size", "thickness", "height", "inner"]
|
||||||
visible: !SettingsData.frameEnabled
|
visible: !SettingsData.frameEnabled
|
||||||
text: I18n.tr("Size")
|
text: I18n.tr("Size")
|
||||||
description: I18n.tr("Adjust the bar height via inner padding")
|
description: I18n.tr("Adjust the bar height via inner padding")
|
||||||
@@ -1154,6 +1169,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barSquareCorners"
|
||||||
|
tags: ["square", "corners", "rounding"]
|
||||||
text: I18n.tr("Square Corners")
|
text: I18n.tr("Square Corners")
|
||||||
description: I18n.tr("Remove corner rounding from the bar")
|
description: I18n.tr("Remove corner rounding from the bar")
|
||||||
visible: !SettingsData.frameEnabled
|
visible: !SettingsData.frameEnabled
|
||||||
@@ -1164,6 +1181,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barNoBackground"
|
||||||
|
tags: ["transparent", "background", "invisible"]
|
||||||
text: I18n.tr("No Background")
|
text: I18n.tr("No Background")
|
||||||
description: I18n.tr("Make the bar background fully transparent")
|
description: I18n.tr("Make the bar background fully transparent")
|
||||||
visible: !SettingsData.frameEnabled
|
visible: !SettingsData.frameEnabled
|
||||||
@@ -1174,6 +1193,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barMaximizeWidgetIcons"
|
||||||
|
tags: ["maximize", "icons", "stretch"]
|
||||||
text: I18n.tr("Maximize Widget Icons")
|
text: I18n.tr("Maximize Widget Icons")
|
||||||
description: I18n.tr("Stretch widget icons to fill the available bar height")
|
description: I18n.tr("Stretch widget icons to fill the available bar height")
|
||||||
checked: selectedBarConfig?.maximizeWidgetIcons ?? false
|
checked: selectedBarConfig?.maximizeWidgetIcons ?? false
|
||||||
@@ -1183,6 +1204,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barMaximizeWidgetText"
|
||||||
|
tags: ["maximize", "text", "stretch"]
|
||||||
text: I18n.tr("Maximize Widget Text")
|
text: I18n.tr("Maximize Widget Text")
|
||||||
description: I18n.tr("Stretch widget text to fill the available bar height")
|
description: I18n.tr("Stretch widget text to fill the available bar height")
|
||||||
checked: selectedBarConfig?.maximizeWidgetText ?? false
|
checked: selectedBarConfig?.maximizeWidgetText ?? false
|
||||||
@@ -1192,6 +1215,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barRemoveWidgetPadding"
|
||||||
|
tags: ["padding", "compact", "widgets"]
|
||||||
text: I18n.tr("Remove Widget Padding")
|
text: I18n.tr("Remove Widget Padding")
|
||||||
description: I18n.tr("Remove inner padding from all widgets")
|
description: I18n.tr("Remove inner padding from all widgets")
|
||||||
checked: selectedBarConfig?.removeWidgetPadding ?? false
|
checked: selectedBarConfig?.removeWidgetPadding ?? false
|
||||||
@@ -1208,6 +1233,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "barGothCorners"
|
||||||
|
tags: ["goth", "corners", "concave", "cutout"]
|
||||||
text: I18n.tr("Goth Corners")
|
text: I18n.tr("Goth Corners")
|
||||||
description: I18n.tr("Apply inverse concave corner cutouts to the bar")
|
description: I18n.tr("Apply inverse concave corner cutouts to the bar")
|
||||||
visible: !SettingsData.frameEnabled
|
visible: !SettingsData.frameEnabled
|
||||||
@@ -1302,6 +1329,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleCard {
|
SettingsToggleCard {
|
||||||
|
settingKey: "barMaximizeDetection"
|
||||||
|
tags: ["maximize", "gaps", "border", "fullscreen"]
|
||||||
iconName: "fit_screen"
|
iconName: "fit_screen"
|
||||||
title: I18n.tr("Maximize Detection")
|
title: I18n.tr("Maximize Detection")
|
||||||
description: I18n.tr("Remove gaps and border when windows are maximized")
|
description: I18n.tr("Remove gaps and border when windows are maximized")
|
||||||
@@ -1847,6 +1876,8 @@ Item {
|
|||||||
|
|
||||||
SettingsToggleCard {
|
SettingsToggleCard {
|
||||||
iconName: "mouse"
|
iconName: "mouse"
|
||||||
|
settingKey: "barScrollWheel"
|
||||||
|
tags: ["scroll", "wheel", "workspace", "axis"]
|
||||||
title: I18n.tr("Scroll Wheel")
|
title: I18n.tr("Scroll Wheel")
|
||||||
description: I18n.tr("Control workspaces and columns by scrolling on the bar")
|
description: I18n.tr("Control workspaces and columns by scrolling on the bar")
|
||||||
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
visible: !dankBarTab.appearanceOnly && selectedBarConfig?.enabled
|
||||||
|
|||||||
@@ -267,6 +267,8 @@ Item {
|
|||||||
spacing: Theme.spacingXL
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "defaultAppsInternet"
|
||||||
|
tags: ["browser", "mail", "email", "web"]
|
||||||
title: I18n.tr("Internet", "Internet")
|
title: I18n.tr("Internet", "Internet")
|
||||||
iconName: "public"
|
iconName: "public"
|
||||||
|
|
||||||
@@ -293,6 +295,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "defaultAppsUtilities"
|
||||||
|
tags: ["file", "manager", "terminal", "editor"]
|
||||||
title: I18n.tr("Utilities", "Utilities")
|
title: I18n.tr("Utilities", "Utilities")
|
||||||
iconName: "terminal"
|
iconName: "terminal"
|
||||||
|
|
||||||
@@ -317,6 +321,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "defaultAppsDocuments"
|
||||||
|
tags: ["pdf", "text", "reader", "office"]
|
||||||
title: I18n.tr("Documents", "Documents")
|
title: I18n.tr("Documents", "Documents")
|
||||||
iconName: "edit_document"
|
iconName: "edit_document"
|
||||||
|
|
||||||
@@ -335,6 +341,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "defaultAppsMultimedia"
|
||||||
|
tags: ["image", "video", "music", "viewer", "player"]
|
||||||
title: I18n.tr("Multimedia", "Multimedia")
|
title: I18n.tr("Multimedia", "Multimedia")
|
||||||
iconName: "movie"
|
iconName: "movie"
|
||||||
AppSelector {
|
AppSelector {
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ Item {
|
|||||||
spacing: Theme.spacingXL
|
spacing: Theme.spacingXL
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "desktopWidgetsManage"
|
||||||
|
tags: ["desktop", "widgets", "clock", "conky"]
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "widgets"
|
iconName: "widgets"
|
||||||
title: I18n.tr("Desktop Widgets")
|
title: I18n.tr("Desktop Widgets")
|
||||||
@@ -203,6 +205,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsCard {
|
SettingsCard {
|
||||||
|
settingKey: "desktopWidgetGroups"
|
||||||
|
tags: ["groups", "profiles", "layouts"]
|
||||||
width: parent.width
|
width: parent.width
|
||||||
iconName: "folder"
|
iconName: "folder"
|
||||||
title: I18n.tr("Groups")
|
title: I18n.tr("Groups")
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ Singleton {
|
|||||||
readonly property var wlrOutputs: WlrOutputService.outputs
|
readonly property var wlrOutputs: WlrOutputService.outputs
|
||||||
property var outputs: ({})
|
property var outputs: ({})
|
||||||
property var savedOutputs: ({})
|
property var savedOutputs: ({})
|
||||||
|
property var savedParsedOutputs: ({})
|
||||||
property var allOutputs: buildAllOutputsMap()
|
property var allOutputs: buildAllOutputsMap()
|
||||||
|
|
||||||
property var includeStatus: ({
|
property var includeStatus: ({
|
||||||
@@ -911,15 +912,18 @@ Singleton {
|
|||||||
const paths = getConfigPaths();
|
const paths = getConfigPaths();
|
||||||
if (!paths) {
|
if (!paths) {
|
||||||
savedOutputs = {};
|
savedOutputs = {};
|
||||||
|
savedParsedOutputs = {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Proc.runCommand("load-saved-outputs", ["cat", paths.outputsFile], (content, exitCode) => {
|
Proc.runCommand("load-saved-outputs", ["cat", paths.outputsFile], (content, exitCode) => {
|
||||||
if (exitCode !== 0 || !content.trim()) {
|
if (exitCode !== 0 || !content.trim()) {
|
||||||
savedOutputs = {};
|
savedOutputs = {};
|
||||||
|
savedParsedOutputs = {};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const parsed = parseOutputsConfig(content);
|
const parsed = parseOutputsConfig(content);
|
||||||
|
savedParsedOutputs = parsed;
|
||||||
const filtered = filterDisconnectedOnly(parsed);
|
const filtered = filterDisconnectedOnly(parsed);
|
||||||
savedOutputs = filtered;
|
savedOutputs = filtered;
|
||||||
|
|
||||||
@@ -1092,20 +1096,7 @@ Singleton {
|
|||||||
const name = match[1];
|
const name = match[1];
|
||||||
const body = match[2];
|
const body = match[2];
|
||||||
|
|
||||||
if (body.trim() === "off") {
|
const disabled = /^\s*off\s*$/m.test(body);
|
||||||
result[name] = {
|
|
||||||
"name": name,
|
|
||||||
"disabled": true,
|
|
||||||
"logical": {
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
"scale": 1.0,
|
|
||||||
"transform": "Normal"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
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.]+)/);
|
||||||
@@ -1115,6 +1106,7 @@ Singleton {
|
|||||||
|
|
||||||
result[name] = {
|
result[name] = {
|
||||||
"name": name,
|
"name": name,
|
||||||
|
"disabled": disabled,
|
||||||
"logical": {
|
"logical": {
|
||||||
"x": posMatch ? parseInt(posMatch[1]) : 0,
|
"x": posMatch ? parseInt(posMatch[1]) : 0,
|
||||||
"y": posMatch ? parseInt(posMatch[2]) : 0,
|
"y": posMatch ? parseInt(posMatch[2]) : 0,
|
||||||
@@ -1778,6 +1770,41 @@ Singleton {
|
|||||||
return normalized;
|
return normalized;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function findSavedParsedOutput(outputName) {
|
||||||
|
const output = outputs[outputName];
|
||||||
|
const candidates = [outputName];
|
||||||
|
if (output?.make && output?.model) {
|
||||||
|
candidates.push(output.make + " " + output.model + " " + (output.serial || "Unknown"));
|
||||||
|
candidates.push(output.make + " " + output.model);
|
||||||
|
}
|
||||||
|
for (const savedName in savedParsedOutputs) {
|
||||||
|
if (candidates.includes(savedName.trim()))
|
||||||
|
return savedParsedOutputs[savedName];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A disabled wlr head reports no current mode, scale 0 and position 0,0 —
|
||||||
|
// overlay the last written config so rewrites don't discard real settings.
|
||||||
|
function overlaySavedDisabledOutput(entry, outputName) {
|
||||||
|
const saved = findSavedParsedOutput(outputName);
|
||||||
|
if (!saved)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const savedMode = saved.modes?.[saved.current_mode ?? 0];
|
||||||
|
if (savedMode && !entry.configured_mode)
|
||||||
|
entry.configured_mode = savedMode.width + "x" + savedMode.height + "@" + (savedMode.refresh_rate / 1000).toFixed(3);
|
||||||
|
if (saved.vrr_enabled !== undefined)
|
||||||
|
entry.vrr_enabled = saved.vrr_enabled;
|
||||||
|
if (!saved.logical || !entry.logical)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entry.logical.x = saved.logical.x;
|
||||||
|
entry.logical.y = saved.logical.y;
|
||||||
|
entry.logical.scale = saved.logical.scale;
|
||||||
|
entry.logical.transform = saved.logical.transform;
|
||||||
|
}
|
||||||
|
|
||||||
function buildOutputsWithPendingChanges() {
|
function buildOutputsWithPendingChanges() {
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|
||||||
@@ -1787,7 +1814,10 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const outputName in outputs) {
|
for (const outputName in outputs) {
|
||||||
result[outputName] = JSON.parse(JSON.stringify(outputs[outputName]));
|
const entry = JSON.parse(JSON.stringify(outputs[outputName]));
|
||||||
|
if (entry.enabled === false)
|
||||||
|
overlaySavedDisabledOutput(entry, outputName);
|
||||||
|
result[outputName] = entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const outputName in pendingChanges) {
|
for (const outputName in pendingChanges) {
|
||||||
@@ -1799,6 +1829,8 @@ Singleton {
|
|||||||
result[outputName].logical.y = changes.position.y;
|
result[outputName].logical.y = changes.position.y;
|
||||||
}
|
}
|
||||||
if (changes.mode !== undefined && result[outputName].modes) {
|
if (changes.mode !== undefined && result[outputName].modes) {
|
||||||
|
if (result[outputName].configured_mode)
|
||||||
|
result[outputName].configured_mode = changes.mode;
|
||||||
for (var i = 0; i < result[outputName].modes.length; i++) {
|
for (var i = 0; i < result[outputName].modes.length; i++) {
|
||||||
if (formatMode(result[outputName].modes[i]) === changes.mode) {
|
if (formatMode(result[outputName].modes[i]) === changes.mode) {
|
||||||
result[outputName].current_mode = i;
|
result[outputName].current_mode = i;
|
||||||
|
|||||||
@@ -618,6 +618,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsSliderRow {
|
SettingsSliderRow {
|
||||||
|
settingKey: "dockExclusiveZone"
|
||||||
|
tags: ["exclusive", "zone", "reserved", "offset"]
|
||||||
text: I18n.tr("Exclusive Zone Offset")
|
text: I18n.tr("Exclusive Zone Offset")
|
||||||
visible: !root.connectedFrameModeActive || root.connectedPersistentDockActive
|
visible: !root.connectedFrameModeActive || root.connectedPersistentDockActive
|
||||||
value: SettingsData.dockBottomGap
|
value: SettingsData.dockBottomGap
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ Item {
|
|||||||
settingKey: "mediaPlayer"
|
settingKey: "mediaPlayer"
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaWaveProgress"
|
||||||
|
tags: ["wave", "progress", "animated"]
|
||||||
text: I18n.tr("Wave Progress Bars")
|
text: I18n.tr("Wave Progress Bars")
|
||||||
description: I18n.tr("Use animated wave progress bars for media playback")
|
description: I18n.tr("Use animated wave progress bars for media playback")
|
||||||
checked: SettingsData.waveProgressEnabled
|
checked: SettingsData.waveProgressEnabled
|
||||||
@@ -45,6 +47,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaScrollTitle"
|
||||||
|
tags: ["scroll", "title", "marquee"]
|
||||||
text: I18n.tr("Scroll song title")
|
text: I18n.tr("Scroll song title")
|
||||||
description: I18n.tr("Scroll title if it doesn't fit in widget")
|
description: I18n.tr("Scroll title if it doesn't fit in widget")
|
||||||
checked: SettingsData.scrollTitleEnabled
|
checked: SettingsData.scrollTitleEnabled
|
||||||
@@ -52,6 +56,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaVisualizer"
|
||||||
|
tags: ["visualizer", "cava", "spectrum"]
|
||||||
text: I18n.tr("Audio Visualizer")
|
text: I18n.tr("Audio Visualizer")
|
||||||
description: I18n.tr("Show cava audio visualizer in media widget")
|
description: I18n.tr("Show cava audio visualizer in media widget")
|
||||||
checked: SettingsData.audioVisualizerEnabled
|
checked: SettingsData.audioVisualizerEnabled
|
||||||
@@ -59,6 +65,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaAdaptiveWidth"
|
||||||
|
tags: ["adaptive", "width", "shrink"]
|
||||||
text: I18n.tr("Adaptive Media Width")
|
text: I18n.tr("Adaptive Media Width")
|
||||||
description: I18n.tr("Shrink the media widget to fit shorter song titles while still respecting the configured maximum size")
|
description: I18n.tr("Shrink the media widget to fit shorter song titles while still respecting the configured maximum size")
|
||||||
checked: SettingsData.mediaAdaptiveWidthEnabled
|
checked: SettingsData.mediaAdaptiveWidthEnabled
|
||||||
@@ -66,6 +74,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaAlbumArtAccent"
|
||||||
|
tags: ["album", "art", "accent", "colors"]
|
||||||
text: I18n.tr("Use album art accent")
|
text: I18n.tr("Use album art accent")
|
||||||
description: I18n.tr("Use colors extracted from album art instead of system theme colors")
|
description: I18n.tr("Use colors extracted from album art instead of system theme colors")
|
||||||
checked: SettingsData.mediaUseAlbumArtAccent
|
checked: SettingsData.mediaUseAlbumArtAccent
|
||||||
@@ -136,6 +146,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "mediaDeviceScrollVolume"
|
||||||
|
tags: ["device", "scroll", "volume"]
|
||||||
text: I18n.tr("Device list scroll volume")
|
text: I18n.tr("Device list scroll volume")
|
||||||
description: I18n.tr("Allow adjusting device volume by scrolling on the right half of items in the device list")
|
description: I18n.tr("Allow adjusting device volume by scrolling on the right half of items in the device list")
|
||||||
checked: SettingsData.audioDeviceScrollVolumeEnabled
|
checked: SettingsData.audioDeviceScrollVolumeEnabled
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ Item {
|
|||||||
SettingsCard {
|
SettingsCard {
|
||||||
tab: "mux"
|
tab: "mux"
|
||||||
tags: ["mux", "session", "filter", "exclude", "hide"]
|
tags: ["mux", "session", "filter", "exclude", "hide"]
|
||||||
|
settingKey: "muxSessionFilter"
|
||||||
title: I18n.tr("Session Filter")
|
title: I18n.tr("Session Filter")
|
||||||
iconName: "filter_list"
|
iconName: "filter_list"
|
||||||
|
|
||||||
|
|||||||
@@ -55,12 +55,12 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPinnedWifiNetworks() {
|
function getPinnedWifiNetworks() {
|
||||||
const pins = SettingsData.wifiNetworkPins || {};
|
const pins = CacheData.wifiNetworkPins || {};
|
||||||
return normalizePinList(pins["preferredWifi"]);
|
return normalizePinList(pins["preferredWifi"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleWifiPin(ssid) {
|
function toggleWifiPin(ssid) {
|
||||||
const pins = JSON.parse(JSON.stringify(SettingsData.wifiNetworkPins || {}));
|
const pins = JSON.parse(JSON.stringify(CacheData.wifiNetworkPins || {}));
|
||||||
let pinnedList = normalizePinList(pins["preferredWifi"]);
|
let pinnedList = normalizePinList(pins["preferredWifi"]);
|
||||||
const pinIndex = pinnedList.indexOf(ssid);
|
const pinIndex = pinnedList.indexOf(ssid);
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ Item {
|
|||||||
else
|
else
|
||||||
delete pins["preferredWifi"];
|
delete pins["preferredWifi"];
|
||||||
|
|
||||||
SettingsData.set("wifiNetworkPins", pins);
|
CacheData.set("wifiNetworkPins", pins);
|
||||||
}
|
}
|
||||||
|
|
||||||
property var forgetNetworkConfirm: ConfirmModal {}
|
property var forgetNetworkConfirm: ConfirmModal {}
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsDropdownRow {
|
SettingsDropdownRow {
|
||||||
|
settingKey: "systemUpdaterCheckInterval"
|
||||||
|
tags: ["interval", "poll", "frequency"]
|
||||||
text: I18n.tr("Check interval")
|
text: I18n.tr("Check interval")
|
||||||
description: I18n.tr("How often the server polls for new updates.")
|
description: I18n.tr("How often the server polls for new updates.")
|
||||||
options: root.intervalOptions.map(o => o.label).concat([root.customIntervalLabel])
|
options: root.intervalOptions.map(o => o.label).concat([root.customIntervalLabel])
|
||||||
@@ -157,6 +159,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "systemUpdaterCheckOnStart"
|
||||||
|
tags: ["startup", "check", "boot"]
|
||||||
text: I18n.tr("Check on startup")
|
text: I18n.tr("Check on startup")
|
||||||
description: I18n.tr("When enabled, checks updates on startup. When disabled, only the interval above or a manual refresh runs a check.")
|
description: I18n.tr("When enabled, checks updates on startup. When disabled, only the interval above or a manual refresh runs a check.")
|
||||||
checked: SettingsData.updaterCheckOnStart
|
checked: SettingsData.updaterCheckOnStart
|
||||||
@@ -164,6 +168,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "systemUpdaterFlatpak"
|
||||||
|
tags: ["flatpak", "include"]
|
||||||
text: I18n.tr("Include Flatpak updates")
|
text: I18n.tr("Include Flatpak updates")
|
||||||
description: I18n.tr("Apply Flatpak updates alongside system updates when running 'Update All'.")
|
description: I18n.tr("Apply Flatpak updates alongside system updates when running 'Update All'.")
|
||||||
visible: (SystemUpdateService.backends || []).some(b => b.repo === "flatpak")
|
visible: (SystemUpdateService.backends || []).some(b => b.repo === "flatpak")
|
||||||
@@ -172,6 +178,8 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "systemUpdaterAUR"
|
||||||
|
tags: ["aur", "paru", "yay"]
|
||||||
text: I18n.tr("Include AUR updates")
|
text: I18n.tr("Include AUR updates")
|
||||||
description: I18n.tr("Run paru/yay with AUR enabled when 'Update All' is clicked.")
|
description: I18n.tr("Run paru/yay with AUR enabled when 'Update All' is clicked.")
|
||||||
visible: (SystemUpdateService.backends || []).some(b => b.id === "paru" || b.id === "yay")
|
visible: (SystemUpdateService.backends || []).some(b => b.id === "paru" || b.id === "yay")
|
||||||
@@ -328,6 +336,8 @@ Item {
|
|||||||
settingKey: "systemUpdaterAdvanced"
|
settingKey: "systemUpdaterAdvanced"
|
||||||
|
|
||||||
SettingsToggleRow {
|
SettingsToggleRow {
|
||||||
|
settingKey: "systemUpdaterCustomCommand"
|
||||||
|
tags: ["custom", "command", "terminal"]
|
||||||
text: I18n.tr("Use Custom Command")
|
text: I18n.tr("Use Custom Command")
|
||||||
description: I18n.tr("Open a terminal and run a custom command instead of the in-shell upgrade flow.")
|
description: I18n.tr("Open a terminal and run a custom command instead of the in-shell upgrade flow.")
|
||||||
checked: SettingsData.updaterUseCustomCommand
|
checked: SettingsData.updaterUseCustomCommand
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ Item {
|
|||||||
property string matugenPreviewRequestKey: ""
|
property string matugenPreviewRequestKey: ""
|
||||||
property var installedRegistryThemes: []
|
property var installedRegistryThemes: []
|
||||||
property var templateDetection: []
|
property var templateDetection: []
|
||||||
|
readonly property var neovimDarkBaseThemes: ["aquarium", "ashes", "aylin", "ayu_dark", "bearded-arc", "carbonfox", "catppuccin", "chadracula", "chadracula-evondev", "chadtain", "chocolate", "darcula-dark", "dark_horizon", "decay", "default-dark", "doomchad", "eldritch", "embark", "everblush", "everforest", "falcon", "flexoki", "flouromachine", "gatekeeper", "github_dark", "gruvbox", "gruvchad", "hiberbee", "horizon", "jabuti", "jellybeans", "kanagawa", "kanagawa-dragon", "material-darker", "material-deep-ocean", "melange", "midnight_breeze", "mito-laser", "monekai", "monochrome", "mountain", "neofusion", "nightfox", "nightlamp", "nightowl", "nord", "obsidian-ember", "oceanic-next", "onedark", "onenord", "oxocarbon", "palenight", "pastelDark", "pastelbeans", "penumbra_dark", "poimandres", "radium", "rosepine", "rxyhn", "scaryforest", "seoul256_dark", "solarized_dark", "solarized_osaka", "starlight", "sweetpastel", "tokyodark", "tokyonight", "tomorrow_night", "tundra", "vesper", "vscode_dark", "wombat", "yoru", "zenburn"]
|
||||||
|
readonly property var neovimLightBaseThemes: ["ayu_light", "blossom_light", "catppuccin-latte", "default-light", "everforest_light", "flex-light", "flexoki-light", "github_light", "gruvbox_light", "material-lighter", "nano-light", "oceanic-light", "one_light", "onenord_light", "penumbra_light", "rosepine-dawn", "seoul256_light", "solarized_light", "sunrise_breeze", "vscode_light"]
|
||||||
readonly property var matugenSchemeColorMap: {
|
readonly property var matugenSchemeColorMap: {
|
||||||
const map = {};
|
const map = {};
|
||||||
const mode = SessionData.isLightMode ? "light" : "dark";
|
const mode = SessionData.isLightMode ? "light" : "dark";
|
||||||
@@ -2666,7 +2668,7 @@ Item {
|
|||||||
description: "Base to derive dark theme from"
|
description: "Base to derive dark theme from"
|
||||||
visible: neovimThemeToggle.visible && neovimThemeToggle.checked
|
visible: neovimThemeToggle.visible && neovimThemeToggle.checked
|
||||||
currentValue: SettingsData.matugenTemplateNeovimSettings?.dark?.baseTheme ?? "github_dark"
|
currentValue: SettingsData.matugenTemplateNeovimSettings?.dark?.baseTheme ?? "github_dark"
|
||||||
options: ["aquarium", "ashes", "aylin", "ayu_dark", "bearded-arc", "carbonfox", "catppuccin", "chadracula", "chadracula-evondev", "chadtain", "chocolate", "darcula-dark", "dark_horizon", "decay", "default-dark", "doomchad", "eldritch", "embark", "everblush", "everforest", "falcon", "flexoki", "flouromachine", "gatekeeper", "github_dark", "gruvbox", "gruvchad", "hiberbee", "horizon", "jabuti", "jellybeans", "kanagawa", "kanagawa-dragon", "material-darker", "material-deep-ocean", "melange", "midnight_breeze", "mito-laser", "monekai", "monochrome", "mountain", "neofusion", "nightfox", "nightlamp", "nightowl", "nord", "obsidian-ember", "oceanic-next", "onedark", "onenord", "oxocarbon", "palenight", "pastelDark", "pastelbeans", "penumbra_dark", "poimandres", "radium", "rosepine", "rxyhn", "scaryforest", "seoul256_dark", "solarized_dark", "solarized_osaka", "starlight", "sweetpastel", "tokyodark", "tokyonight", "tomorrow_night", "tundra", "vesper", "vscode_dark", "wombat", "yoru", "zenburn"]
|
options: themeColorsTab.neovimDarkBaseThemes.concat(themeColorsTab.neovimLightBaseThemes)
|
||||||
enableFuzzySearch: true
|
enableFuzzySearch: true
|
||||||
onValueChanged: value => {
|
onValueChanged: value => {
|
||||||
const settings = SettingsData.matugenTemplateNeovimSettings;
|
const settings = SettingsData.matugenTemplateNeovimSettings;
|
||||||
@@ -2683,7 +2685,7 @@ Item {
|
|||||||
description: "Base to derive light theme from"
|
description: "Base to derive light theme from"
|
||||||
visible: neovimThemeToggle.visible && neovimThemeToggle.checked
|
visible: neovimThemeToggle.visible && neovimThemeToggle.checked
|
||||||
currentValue: SettingsData.matugenTemplateNeovimSettings?.light?.baseTheme ?? "github_light"
|
currentValue: SettingsData.matugenTemplateNeovimSettings?.light?.baseTheme ?? "github_light"
|
||||||
options: ["ayu_light", "blossom_light", "catppuccin-latte", "default-light", "everforest_light", "flex-light", "flexoki-light", "github_light", "gruvbox_light", "material-lighter", "nano-light", "oceanic-light", "one_light", "onenord_light", "penumbra_light", "rosepine-dawn", "seoul256_light", "solarized_light", "sunrise_breeze", "vscode_light"]
|
options: themeColorsTab.neovimLightBaseThemes.concat(themeColorsTab.neovimDarkBaseThemes)
|
||||||
enableFuzzySearch: true
|
enableFuzzySearch: true
|
||||||
onValueChanged: value => {
|
onValueChanged: value => {
|
||||||
const settings = SettingsData.matugenTemplateNeovimSettings;
|
const settings = SettingsData.matugenTemplateNeovimSettings;
|
||||||
|
|||||||
@@ -1199,16 +1199,18 @@ Item {
|
|||||||
width: parent.width - Theme.spacingM * 2
|
width: parent.width - Theme.spacingM * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
DankButtonGroup {
|
DankFilterChips {
|
||||||
id: transitionGroup
|
id: transitionGroup
|
||||||
width: parent.width - Theme.spacingM * 2
|
width: parent.width - Theme.spacingM * 2
|
||||||
selectionMode: "multi"
|
multiSelect: true
|
||||||
model: SessionData.availableWallpaperTransitions.filter(t => t !== "none")
|
model: SessionData.availableWallpaperTransitions.filter(t => t !== "none").map(t => ({
|
||||||
initialSelection: SessionData.includedTransitions
|
"value": t,
|
||||||
currentSelection: SessionData.includedTransitions
|
"label": t.replace(/\b\w/g, c => c.toUpperCase())
|
||||||
|
}))
|
||||||
|
selectedValues: SessionData.includedTransitions
|
||||||
|
|
||||||
onSelectionChanged: (index, selected) => {
|
onSelectionToggled: (index, selected) => {
|
||||||
const transition = model[index];
|
const transition = model[index].value;
|
||||||
let newIncluded = SessionData.includedTransitions.slice();
|
let newIncluded = SessionData.includedTransitions.slice();
|
||||||
|
|
||||||
if (selected && !newIncluded.includes(transition)) {
|
if (selected && !newIncluded.includes(transition)) {
|
||||||
|
|||||||
@@ -167,7 +167,13 @@ FloatingWindow {
|
|||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height - searchField.height - Theme.spacingM
|
height: parent.height - searchField.height - Theme.spacingM
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
model: root.filteredApps
|
// Start with no model. It is (re)bound in show() so that each open
|
||||||
|
// gets a fresh QQmlDelegateModel, avoiding a crash where a stale
|
||||||
|
// incubation queue from a previous open races with a background
|
||||||
|
// refreshApplications() replacing the underlying DesktopEntries
|
||||||
|
// QObjectModel (SIGSEGV in QQmlIncubatorPrivate::incubate via
|
||||||
|
// libQt6QmlModels). See hide()/onVisibleChanged() for the teardown.
|
||||||
|
model: null
|
||||||
clip: true
|
clip: true
|
||||||
|
|
||||||
delegate: Rectangle {
|
delegate: Rectangle {
|
||||||
@@ -316,11 +322,21 @@ FloatingWindow {
|
|||||||
|
|
||||||
function show() {
|
function show() {
|
||||||
updateFilteredApps();
|
updateFilteredApps();
|
||||||
|
// Rebind the model reactively (search relies on filteredApps changes flowing
|
||||||
|
// through), and do it after populating so the ListView starts incubating from
|
||||||
|
// a fully-formed array rather than [].
|
||||||
|
appList.model = Qt.binding(() => root.filteredApps);
|
||||||
visible = true;
|
visible = true;
|
||||||
Qt.callLater(() => searchField.forceActiveFocus());
|
Qt.callLater(() => searchField.forceActiveFocus());
|
||||||
}
|
}
|
||||||
|
|
||||||
function hide() {
|
function hide() {
|
||||||
|
// Drop the model before clearing visible, so the ListView releases its
|
||||||
|
// QQmlDelegateModel (and any in-flight incubators) synchronously on this
|
||||||
|
// frame. This is the crux of the fix: a later show() will allocate a fresh
|
||||||
|
// DelegateModel instead of reusing one whose incubation queue may hold
|
||||||
|
// references invalidated by a concurrent refreshApplications().
|
||||||
|
appList.model = null;
|
||||||
visible = false;
|
visible = false;
|
||||||
searchQuery = "";
|
searchQuery = "";
|
||||||
filteredApps = [];
|
filteredApps = [];
|
||||||
@@ -330,6 +346,9 @@ FloatingWindow {
|
|||||||
|
|
||||||
onVisibleChanged: {
|
onVisibleChanged: {
|
||||||
if (!visible) {
|
if (!visible) {
|
||||||
|
// Guard against visibility being cleared without going through hide()
|
||||||
|
// (e.g. window manager close, FloatingWindow.onClosed -> hide()).
|
||||||
|
appList.model = null;
|
||||||
searchQuery = "";
|
searchQuery = "";
|
||||||
filteredApps = [];
|
filteredApps = [];
|
||||||
selectedIndex = -1;
|
selectedIndex = -1;
|
||||||
|
|||||||
@@ -847,7 +847,7 @@ EOFCONFIG
|
|||||||
|
|
||||||
const maxVol = root.sinkMaxVolume;
|
const maxVol = root.sinkMaxVolume;
|
||||||
const clampedVolume = Math.max(0, Math.min(maxVol, percentage));
|
const clampedVolume = Math.max(0, Math.min(maxVol, percentage));
|
||||||
Quickshell.execDetached(["wpctl", "set-volume", "-l", String(maxVol / 100), "@DEFAULT_AUDIO_SINK@", String(clampedVolume / 100)]);
|
root.sink.audio.volume = clampedVolume / 100;
|
||||||
return `Volume set to ${clampedVolume}%`;
|
return `Volume set to ${clampedVolume}%`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -857,14 +857,16 @@ EOFCONFIG
|
|||||||
}
|
}
|
||||||
|
|
||||||
function adjustDefaultSinkVolume(step, direction) {
|
function adjustDefaultSinkVolume(step, direction) {
|
||||||
|
const audio = root.sink.audio;
|
||||||
const maxVol = root.sinkMaxVolume;
|
const maxVol = root.sinkMaxVolume;
|
||||||
const stepValue = outputVolumeStep(step);
|
const stepValue = outputVolumeStep(step);
|
||||||
const currentVolume = Math.round(root.sink.audio.volume * 100);
|
const currentVolume = Math.round(audio.volume * 100);
|
||||||
const newVolume = Math.max(0, Math.min(maxVol, currentVolume + direction * stepValue));
|
const newVolume = Math.max(0, Math.min(maxVol, currentVolume + direction * stepValue));
|
||||||
const suffix = direction > 0 ? "+" : "-";
|
|
||||||
|
|
||||||
Quickshell.execDetached(["wpctl", "set-mute", "@DEFAULT_AUDIO_SINK@", "0"]);
|
if (audio.muted)
|
||||||
Quickshell.execDetached(["wpctl", "set-volume", "-l", String(maxVol / 100), "@DEFAULT_AUDIO_SINK@", `${stepValue}%${suffix}`]);
|
audio.muted = false;
|
||||||
|
|
||||||
|
audio.volume = newVolume / 100;
|
||||||
return newVolume;
|
return newVolume;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,22 @@ Singleton {
|
|||||||
interval: 500
|
interval: 500
|
||||||
repeat: false
|
repeat: false
|
||||||
running: true
|
running: true
|
||||||
onTriggered: root.suppressSound = false
|
onTriggered: {
|
||||||
|
root.suppressSound = false;
|
||||||
|
root.applyPowerProfile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyPowerProfile() {
|
||||||
|
if (!batteryAvailable)
|
||||||
|
return;
|
||||||
|
const profileValue = isPluggedIn ? SettingsData.acProfileName : SettingsData.batteryProfileName;
|
||||||
|
if (profileValue === "")
|
||||||
|
return;
|
||||||
|
const targetProfile = parseInt(profileValue);
|
||||||
|
if (isNaN(targetProfile) || PowerProfiles.profile === targetProfile)
|
||||||
|
return;
|
||||||
|
PowerProfiles.profile = targetProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")
|
readonly property string preferredBatteryOverride: Quickshell.env("DMS_PREFERRED_BATTERY")
|
||||||
@@ -206,14 +221,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const profileValue = BatteryService.isPluggedIn ? SettingsData.acProfileName : SettingsData.batteryProfileName;
|
applyPowerProfile();
|
||||||
|
|
||||||
if (profileValue !== "") {
|
|
||||||
const targetProfile = parseInt(profileValue);
|
|
||||||
if (!isNaN(targetProfile) && PowerProfiles.profile !== targetProfile) {
|
|
||||||
PowerProfiles.profile = targetProfile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
previousPluggedState = isPluggedIn;
|
previousPluggedState = isPluggedIn;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ Item {
|
|||||||
property string socketPath: ""
|
property string socketPath: ""
|
||||||
readonly property bool socketFound: socketPath.length > 0
|
readonly property bool socketFound: socketPath.length > 0
|
||||||
property bool connected: false
|
property bool connected: false
|
||||||
property bool binaryExists: false
|
readonly property bool binaryExists: launchKind.length > 0
|
||||||
property bool binaryChecked: false
|
property bool binaryChecked: false
|
||||||
|
// "dcal" for a host install, "flatpak" for a sandboxed one, "" for neither.
|
||||||
|
property string launchKind: ""
|
||||||
|
|
||||||
property var calendars: []
|
property var calendars: []
|
||||||
property var events: []
|
property var events: []
|
||||||
@@ -55,18 +57,20 @@ Item {
|
|||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: binaryCheck
|
id: binaryCheck
|
||||||
command: ["sh", "-c", "command -v dcal"]
|
command: ["sh", "-c", "if command -v dcal >/dev/null 2>&1; then echo dcal; elif command -v flatpak >/dev/null 2>&1 && flatpak info com.danklinux.dankcalendar >/dev/null 2>&1; then echo flatpak; fi"]
|
||||||
running: false
|
running: false
|
||||||
onExited: code => {
|
stdout: StdioCollector {
|
||||||
root.binaryExists = (code === 0);
|
onStreamFinished: {
|
||||||
root.binaryChecked = true;
|
root.launchKind = text.trim();
|
||||||
|
root.binaryChecked = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Process {
|
Process {
|
||||||
id: discoverProcess
|
id: discoverProcess
|
||||||
running: false
|
running: false
|
||||||
command: ["sh", "-c", "s=\"${DANKCAL_SOCKET:-}\"; if [ -S \"$s\" ]; then echo \"$s\"; exit 0; fi; for f in \"${XDG_RUNTIME_DIR:-/tmp}\"/dankcal-*.sock /tmp/dankcal-*.sock; do [ -S \"$f\" ] || continue; p=$(basename \"$f\" .sock); p=${p#dankcal-}; if kill -0 \"$p\" 2>/dev/null; then echo \"$f\"; exit 0; fi; done"]
|
command: ["sh", "-c", "s=\"${DANKCAL_SOCKET:-}\"; if [ -S \"$s\" ]; then echo \"$s\"; exit 0; fi; for f in \"${XDG_RUNTIME_DIR:-/tmp}\"/app/com.danklinux.dankcalendar/dankcal-*.sock; do if [ -S \"$f\" ]; then echo \"$f\"; exit 0; fi; done; for f in \"${XDG_RUNTIME_DIR:-/tmp}\"/dankcal-*.sock /tmp/dankcal-*.sock; do [ -S \"$f\" ] || continue; p=$(basename \"$f\" .sock); p=${p#dankcal-}; if kill -0 \"$p\" 2>/dev/null; then echo \"$f\"; exit 0; fi; done"]
|
||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: {
|
onStreamFinished: {
|
||||||
@@ -98,9 +102,16 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function launch() {
|
function launch() {
|
||||||
if (!binaryExists)
|
switch (launchKind) {
|
||||||
|
case "dcal":
|
||||||
|
Quickshell.execDetached(["dcal", "run", "-d", "--hidden"]);
|
||||||
|
break;
|
||||||
|
case "flatpak":
|
||||||
|
Quickshell.execDetached(["flatpak", "run", "com.danklinux.dankcalendar", "run", "-d", "--hidden"]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
return;
|
return;
|
||||||
Quickshell.execDetached(["dcal", "run", "-d", "--hidden"]);
|
}
|
||||||
if (enabled && !connected)
|
if (enabled && !connected)
|
||||||
discoverProcess.running = true;
|
discoverProcess.running = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -511,7 +511,7 @@ Singleton {
|
|||||||
return MangoService.filterCurrentDisplay(toplevels, screenName);
|
return MangoService.filterCurrentDisplay(toplevels, screenName);
|
||||||
if (isHyprland)
|
if (isHyprland)
|
||||||
return filterHyprlandCurrentDisplaySafe(toplevels, screenName);
|
return filterHyprlandCurrentDisplaySafe(toplevels, screenName);
|
||||||
return toplevels;
|
return toplevels.filter(t => _toplevelOnScreen(t, screenName));
|
||||||
}
|
}
|
||||||
|
|
||||||
function _screenName(screenOrName) {
|
function _screenName(screenOrName) {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ Singleton {
|
|||||||
return "wifi_1_bar";
|
return "wifi_1_bar";
|
||||||
}
|
}
|
||||||
|
|
||||||
property string userPreference: "auto"
|
readonly property string userPreference: SettingsData.networkPreference
|
||||||
property bool isConnecting: false
|
property bool isConnecting: false
|
||||||
property string connectingSSID: ""
|
property string connectingSSID: ""
|
||||||
property string connectionError: ""
|
property string connectionError: ""
|
||||||
@@ -187,7 +187,6 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
root.userPreference = SettingsData.networkPreference;
|
|
||||||
lastConnectedVpnUuid = SessionData.vpnLastConnected || "";
|
lastConnectedVpnUuid = SessionData.vpnLastConnected || "";
|
||||||
if (socketPath && socketPath.length > 0) {
|
if (socketPath && socketPath.length > 0) {
|
||||||
checkDMSCapabilities();
|
checkDMSCapabilities();
|
||||||
@@ -672,7 +671,6 @@ Singleton {
|
|||||||
function setNetworkPreference(preference) {
|
function setNetworkPreference(preference) {
|
||||||
if (!networkAvailable)
|
if (!networkAvailable)
|
||||||
return;
|
return;
|
||||||
userPreference = preference;
|
|
||||||
changingPreference = true;
|
changingPreference = true;
|
||||||
targetPreference = preference;
|
targetPreference = preference;
|
||||||
SettingsData.set("networkPreference", preference);
|
SettingsData.set("networkPreference", preference);
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ Singleton {
|
|||||||
property bool screensaverInhibited: false
|
property bool screensaverInhibited: false
|
||||||
property var screensaverInhibitors: []
|
property var screensaverInhibitors: []
|
||||||
|
|
||||||
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "wallpaper", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "location", "sysupdate"]
|
property var activeSubscriptions: ["network", "network.credentials", "loginctl", "freedesktop", "freedesktop.screensaver", "gamma", "theme.auto", "wallpaper", "bluetooth", "bluetooth.pairing", "brightness", "wlroutput", "evdev", "browser", "dbus", "clipboard", "sysupdate"]
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (socketPath && socketPath.length > 0) {
|
if (socketPath && socketPath.length > 0) {
|
||||||
@@ -335,6 +335,7 @@ Singleton {
|
|||||||
const data = response.result.data;
|
const data = response.result.data;
|
||||||
|
|
||||||
if (service === "server") {
|
if (service === "server") {
|
||||||
|
const prevCapabilities = capabilities;
|
||||||
apiVersion = data.apiVersion || 0;
|
apiVersion = data.apiVersion || 0;
|
||||||
cliVersion = data.cliVersion || "";
|
cliVersion = data.cliVersion || "";
|
||||||
capabilities = data.capabilities || [];
|
capabilities = data.capabilities || [];
|
||||||
@@ -346,6 +347,12 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
capabilitiesReceived();
|
capabilitiesReceived();
|
||||||
|
|
||||||
|
const capabilitiesChanged = prevCapabilities.length !== capabilities.length || capabilities.some(c => !prevCapabilities.includes(c));
|
||||||
|
if (prevCapabilities.length > 0 && capabilitiesChanged) {
|
||||||
|
log.info("Capabilities changed, resubscribing");
|
||||||
|
subscribe(activeSubscriptions);
|
||||||
|
}
|
||||||
} else if (service === "network") {
|
} else if (service === "network") {
|
||||||
networkStateUpdate(data);
|
networkStateUpdate(data);
|
||||||
} else if (service === "network.credentials") {
|
} else if (service === "network.credentials") {
|
||||||
|
|||||||
@@ -386,7 +386,7 @@ Singleton {
|
|||||||
if (!focusedScreen)
|
if (!focusedScreen)
|
||||||
return "";
|
return "";
|
||||||
|
|
||||||
const pins = SettingsData.brightnessDevicePins || {};
|
const pins = CacheData.brightnessDevicePins || {};
|
||||||
const screenKey = SettingsData.getScreenDisplayName(focusedScreen);
|
const screenKey = SettingsData.getScreenDisplayName(focusedScreen);
|
||||||
if (!screenKey)
|
if (!screenKey)
|
||||||
return "";
|
return "";
|
||||||
|
|||||||
@@ -80,6 +80,8 @@ Singleton {
|
|||||||
respectInhibitors: root.respectInhibitors
|
respectInhibitors: root.respectInhibitors
|
||||||
enabled: false
|
enabled: false
|
||||||
onIsIdleChanged: {
|
onIsIdleChanged: {
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
if (isIdle) {
|
if (isIdle) {
|
||||||
if (SettingsData.fadeToDpmsEnabled) {
|
if (SettingsData.fadeToDpmsEnabled) {
|
||||||
root.fadeToDpmsRequested();
|
root.fadeToDpmsRequested();
|
||||||
@@ -101,6 +103,8 @@ Singleton {
|
|||||||
respectInhibitors: root.respectInhibitors
|
respectInhibitors: root.respectInhibitors
|
||||||
enabled: false
|
enabled: false
|
||||||
onIsIdleChanged: {
|
onIsIdleChanged: {
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
if (isIdle) {
|
if (isIdle) {
|
||||||
root.requestMonitorOff();
|
root.requestMonitorOff();
|
||||||
} else {
|
} else {
|
||||||
@@ -115,6 +119,8 @@ Singleton {
|
|||||||
respectInhibitors: root.respectInhibitors
|
respectInhibitors: root.respectInhibitors
|
||||||
enabled: false
|
enabled: false
|
||||||
onIsIdleChanged: {
|
onIsIdleChanged: {
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
if (isIdle) {
|
if (isIdle) {
|
||||||
if (SettingsData.fadeToLockEnabled) {
|
if (SettingsData.fadeToLockEnabled) {
|
||||||
root.fadeToLockRequested();
|
root.fadeToLockRequested();
|
||||||
@@ -135,6 +141,8 @@ Singleton {
|
|||||||
respectInhibitors: root.respectInhibitors
|
respectInhibitors: root.respectInhibitors
|
||||||
enabled: false
|
enabled: false
|
||||||
onIsIdleChanged: {
|
onIsIdleChanged: {
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
if (isIdle)
|
if (isIdle)
|
||||||
root.requestSuspend();
|
root.requestSuspend();
|
||||||
}
|
}
|
||||||
@@ -149,6 +157,8 @@ Singleton {
|
|||||||
respectInhibitors: false
|
respectInhibitors: false
|
||||||
enabled: root.enabled && root.isShellLocked && root.monitorsOff && (SettingsData.lockScreenPowerOffMonitorsOnLock || root.lockPowerOffRequested)
|
enabled: root.enabled && root.isShellLocked && root.monitorsOff && (SettingsData.lockScreenPowerOffMonitorsOnLock || root.lockPowerOffRequested)
|
||||||
onIsIdleChanged: {
|
onIsIdleChanged: {
|
||||||
|
if (!enabled)
|
||||||
|
return;
|
||||||
if (!isIdle && root.monitorsOff)
|
if (!isIdle && root.monitorsOff)
|
||||||
root.requestMonitorOn();
|
root.requestMonitorOn();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
|
import qs.Common
|
||||||
|
import qs.Services
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
|
readonly property bool wantsLocation: SettingsData.weatherEnabled && SettingsData.useAutoLocation
|
||||||
readonly property bool locationAvailable: DMSService.isConnected && DMSService.capabilities.includes("location")
|
readonly property bool locationAvailable: DMSService.isConnected && DMSService.capabilities.includes("location")
|
||||||
readonly property bool valid: latitude !== 0 || longitude !== 0
|
readonly property bool valid: latitude !== 0 || longitude !== 0
|
||||||
|
|
||||||
@@ -15,19 +18,46 @@ Singleton {
|
|||||||
|
|
||||||
signal locationChanged(var data)
|
signal locationChanged(var data)
|
||||||
|
|
||||||
onLocationAvailableChanged: {
|
onWantsLocationChanged: {
|
||||||
if (locationAvailable && !valid)
|
if (wantsLocation) {
|
||||||
getState();
|
ensureSubscription();
|
||||||
|
} else if (DMSService.activeSubscriptions.includes("location")) {
|
||||||
|
DMSService.removeSubscription("location");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onLocationAvailableChanged: ensureSubscription()
|
||||||
|
|
||||||
|
Component.onCompleted: ensureSubscription()
|
||||||
|
|
||||||
Connections {
|
Connections {
|
||||||
target: DMSService
|
target: DMSService
|
||||||
|
|
||||||
function onLocationStateUpdate(data) {
|
function onConnectionStateChanged() {
|
||||||
if (!locationAvailable)
|
if (DMSService.isConnected)
|
||||||
return;
|
root.ensureSubscription();
|
||||||
handleStateUpdate(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onLocationStateUpdate(data) {
|
||||||
|
if (!root.wantsLocation)
|
||||||
|
return;
|
||||||
|
root.handleStateUpdate(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureSubscription() {
|
||||||
|
if (!wantsLocation)
|
||||||
|
return;
|
||||||
|
if (!locationAvailable)
|
||||||
|
return;
|
||||||
|
if (DMSService.activeSubscriptions.includes("location"))
|
||||||
|
return;
|
||||||
|
if (DMSService.activeSubscriptions.includes("all"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
DMSService.addSubscription("location");
|
||||||
|
if (!valid)
|
||||||
|
getState();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleStateUpdate(data) {
|
function handleStateUpdate(data) {
|
||||||
@@ -42,6 +72,8 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getState() {
|
function getState() {
|
||||||
|
if (!wantsLocation)
|
||||||
|
return;
|
||||||
if (!locationAvailable)
|
if (!locationAvailable)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -467,7 +467,7 @@ Singleton {
|
|||||||
if (now - root._lastWatchedReloadAt < 700)
|
if (now - root._lastWatchedReloadAt < 700)
|
||||||
return;
|
return;
|
||||||
root._lastWatchedReloadAt = now;
|
root._lastWatchedReloadAt = now;
|
||||||
root.reloadConfig(true, false);
|
root.reloadConfig(false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reloadConfig(showToast, suppressWatch) {
|
function reloadConfig(showToast, suppressWatch) {
|
||||||
|
|||||||
@@ -1487,8 +1487,6 @@ window-rule {
|
|||||||
|
|
||||||
if (outputSettings.disabled) {
|
if (outputSettings.disabled) {
|
||||||
kdlContent += ` off\n`;
|
kdlContent += ` off\n`;
|
||||||
kdlContent += `}\n\n`;
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (output.configured_mode) {
|
if (output.configured_mode) {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import Quickshell.Services.Polkit
|
||||||
|
|
||||||
|
PolkitAgent {}
|
||||||
@@ -3,7 +3,6 @@ pragma ComponentBehavior: Bound
|
|||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Services.Polkit
|
|
||||||
|
|
||||||
Singleton {
|
Singleton {
|
||||||
id: root
|
id: root
|
||||||
@@ -12,10 +11,13 @@ Singleton {
|
|||||||
readonly property bool disablePolkitIntegration: Quickshell.env("DMS_DISABLE_POLKIT") === "1"
|
readonly property bool disablePolkitIntegration: Quickshell.env("DMS_DISABLE_POLKIT") === "1"
|
||||||
|
|
||||||
readonly property bool polkitAvailable: !disablePolkitIntegration
|
readonly property bool polkitAvailable: !disablePolkitIntegration
|
||||||
readonly property alias agent: polkitAgentInstance
|
readonly property var agent: polkitAgentLoader.item
|
||||||
|
|
||||||
PolkitAgent {
|
Loader {
|
||||||
id: polkitAgentInstance
|
id: polkitAgentLoader
|
||||||
|
active: root.polkitAvailable
|
||||||
|
asynchronous: false
|
||||||
|
source: "PolkitAgentInstance.qml"
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
|||||||
@@ -425,7 +425,7 @@ Singleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function closeSettings() {
|
function closeSettings() {
|
||||||
settingsModal?.close();
|
settingsModal?.hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSettings() {
|
function toggleSettings() {
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
v1.5.2
|
v1.5.3
|
||||||
|
|||||||
@@ -7,12 +7,15 @@ Flow {
|
|||||||
|
|
||||||
property var model: []
|
property var model: []
|
||||||
property int currentIndex: 0
|
property int currentIndex: 0
|
||||||
|
property bool multiSelect: false
|
||||||
|
property var selectedValues: []
|
||||||
property int chipHeight: 32
|
property int chipHeight: 32
|
||||||
property int chipPadding: Theme.spacingM
|
property int chipPadding: Theme.spacingM
|
||||||
property bool showCheck: true
|
property bool showCheck: true
|
||||||
property bool showCounts: true
|
property bool showCounts: true
|
||||||
|
|
||||||
signal selectionChanged(int index)
|
signal selectionChanged(int index)
|
||||||
|
signal selectionToggled(int index, bool selected)
|
||||||
|
|
||||||
spacing: Theme.spacingS
|
spacing: Theme.spacingS
|
||||||
width: parent ? parent.width : 400
|
width: parent ? parent.width : 400
|
||||||
@@ -25,7 +28,8 @@ Flow {
|
|||||||
required property var modelData
|
required property var modelData
|
||||||
required property int index
|
required property int index
|
||||||
|
|
||||||
property bool selected: index === root.currentIndex
|
property var value: typeof modelData === "string" ? modelData : (modelData.value !== undefined ? modelData.value : (modelData.label || ""))
|
||||||
|
property bool selected: root.multiSelect ? root.selectedValues.includes(value) : (index === root.currentIndex)
|
||||||
property bool hovered: mouseArea.containsMouse
|
property bool hovered: mouseArea.containsMouse
|
||||||
property bool pressed: mouseArea.pressed
|
property bool pressed: mouseArea.pressed
|
||||||
property string label: typeof modelData === "string" ? modelData : (modelData.label || "")
|
property string label: typeof modelData === "string" ? modelData : (modelData.label || "")
|
||||||
@@ -99,6 +103,10 @@ Flow {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onPressed: mouse => chipRipple.trigger(mouse.x, mouse.y)
|
onPressed: mouse => chipRipple.trigger(mouse.x, mouse.y)
|
||||||
onClicked: {
|
onClicked: {
|
||||||
|
if (root.multiSelect) {
|
||||||
|
root.selectionToggled(chip.index, !chip.selected);
|
||||||
|
return;
|
||||||
|
}
|
||||||
root.currentIndex = chip.index;
|
root.currentIndex = chip.index;
|
||||||
root.selectionChanged(chip.index);
|
root.selectionChanged(chip.index);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,6 +79,9 @@ Item {
|
|||||||
"rightBar": 0
|
"rightBar": 0
|
||||||
})
|
})
|
||||||
property var screen: null
|
property var screen: null
|
||||||
|
// Output the surface binds to; retargeted only in open() while hidden. A live
|
||||||
|
// screen change on a mapped surface loses the popup blur on niri.
|
||||||
|
property var surfaceScreen: null
|
||||||
readonly property bool frameGapStandaloneActive: CompositorService.frameConfiguredForScreen(screen) && !CompositorService.usesConnectedFrameChromeForScreen(screen)
|
readonly property bool frameGapStandaloneActive: CompositorService.frameConfiguredForScreen(screen) && !CompositorService.usesConnectedFrameChromeForScreen(screen)
|
||||||
readonly property bool fluidStandaloneActive: Theme.isDirectionalEffect
|
readonly property bool fluidStandaloneActive: Theme.isDirectionalEffect
|
||||||
readonly property bool backgroundDismissWindowRequired: backgroundInteractive
|
readonly property bool backgroundDismissWindowRequired: backgroundInteractive
|
||||||
@@ -133,8 +136,6 @@ Item {
|
|||||||
signal popoutClosed
|
signal popoutClosed
|
||||||
signal backgroundClicked
|
signal backgroundClicked
|
||||||
|
|
||||||
property var _lastOpenedScreen: null
|
|
||||||
|
|
||||||
property int effectiveBarPosition: 0
|
property int effectiveBarPosition: 0
|
||||||
property real effectiveBarBottomGap: 0
|
property real effectiveBarBottomGap: 0
|
||||||
readonly property string autoBarShadowDirection: {
|
readonly property string autoBarShadowDirection: {
|
||||||
@@ -338,14 +339,14 @@ Item {
|
|||||||
_frozenMaskWidth = maskWidth;
|
_frozenMaskWidth = maskWidth;
|
||||||
_frozenMaskHeight = maskHeight;
|
_frozenMaskHeight = maskHeight;
|
||||||
|
|
||||||
const screenChanged = _lastOpenedScreen !== null && _lastOpenedScreen !== screen;
|
const screenChanged = surfaceScreen !== null && surfaceScreen !== screen;
|
||||||
if (screenChanged) {
|
if (screenChanged) {
|
||||||
// Hide on this tick so Qt actually tears down the wl_surface; the show
|
// Unmap before retargeting so the surface is destroyed and recreated
|
||||||
// gets deferred below so the unmap is processed before the remap.
|
// on the new output rather than live-migrated.
|
||||||
contentWindow.visible = false;
|
contentWindow.visible = false;
|
||||||
backgroundWindow.visible = false;
|
backgroundWindow.visible = false;
|
||||||
}
|
}
|
||||||
_lastOpenedScreen = screen;
|
surfaceScreen = screen;
|
||||||
|
|
||||||
if (contentContainer && !shouldBeVisible) {
|
if (contentContainer && !shouldBeVisible) {
|
||||||
// Snap morph closed only on a fresh open; on screen-change re-open we stay at 1
|
// Snap morph closed only on a fresh open; on screen-change re-open we stay at 1
|
||||||
@@ -559,7 +560,7 @@ Item {
|
|||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: backgroundWindow
|
id: backgroundWindow
|
||||||
screen: root.screen
|
screen: root.surfaceScreen
|
||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
// Skip buffer updates when there's nothing to render. Briefly flipped
|
// Skip buffer updates when there's nothing to render. Briefly flipped
|
||||||
@@ -625,7 +626,7 @@ Item {
|
|||||||
|
|
||||||
PanelWindow {
|
PanelWindow {
|
||||||
id: contentWindow
|
id: contentWindow
|
||||||
screen: root.screen
|
screen: root.surfaceScreen
|
||||||
visible: false
|
visible: false
|
||||||
color: "transparent"
|
color: "transparent"
|
||||||
readonly property bool closeVisualActive: root.shouldBeVisible || root.isClosing
|
readonly property bool closeVisualActive: root.shouldBeVisible || root.isClosing
|
||||||
|
|||||||
@@ -131,7 +131,6 @@
|
|||||||
@define-color content_view_bg @view_bg_color;
|
@define-color content_view_bg @view_bg_color;
|
||||||
@define-color text_view_bg @view_bg_color;
|
@define-color text_view_bg @view_bg_color;
|
||||||
* {
|
* {
|
||||||
padding: 0;
|
|
||||||
-GtkToolButton-icon-spacing: 4;
|
-GtkToolButton-icon-spacing: 4;
|
||||||
-GtkTextView-error-underline-color: @error_bg_color;
|
-GtkTextView-error-underline-color: @error_bg_color;
|
||||||
-GtkScrolledWindow-scrollbar-spacing: 0;
|
-GtkScrolledWindow-scrollbar-spacing: 0;
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ if mode ~= nil then
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if settings.terminalsAlwaysDark then
|
||||||
|
vim.o.background = "dark"
|
||||||
|
end
|
||||||
|
|
||||||
local current_file_path = debug.getinfo(1, "S").source:sub(2)
|
local current_file_path = debug.getinfo(1, "S").source:sub(2)
|
||||||
local theme_base = deepGet(settings, { "matugenTemplateNeovimSettings", vim.o.background, "baseTheme" })
|
local theme_base = deepGet(settings, { "matugenTemplateNeovimSettings", vim.o.background, "baseTheme" })
|
||||||
or ("github_" .. vim.o.background)
|
or ("github_" .. vim.o.background)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user