1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-31 08:52:49 -05:00

Compare commits

...

5 Commits

Author SHA1 Message Date
Lucas
8838fd67b9 nix: add dev-shell (#944)
* nix: add dev-shell

* docs: add Nix dev shell in contributing docs
2025-12-08 12:22:07 +01:00
Lucas
c570e20308 nix: use quickshell from source by default in greeter (#941) 2025-12-08 07:37:29 +01:00
bbedward
0a00ef39e3 ipc: fix bar widget IPCs when screens change 2025-12-07 23:15:24 -05:00
bbedward
9a08b81214 dankinstall: swap to systemd by default, use 90-dms.conf for vars 2025-12-07 22:51:22 -05:00
bbedward
c617ae26a2 niri: fix some keybind tab issues
- Fix args for screenshot
- move-column stuff is focus=true by default
- Parsing fixes
part of #914
2025-12-07 22:41:01 -05:00
21 changed files with 423 additions and 216 deletions

37
.gitignore vendored
View File

@@ -102,39 +102,6 @@ go.work.sum
# .idea/ # .idea/
# .vscode/ # .vscode/
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Code coverage profiles and other test artifacts
*.out
coverage.*
*.coverprofile
profile.cov
# Dependency directories (remove the comment below to include it)
# vendor/
# Go workspace file
go.work
go.work.sum
# env file
.env
# Editor/IDE
# .idea/
# .vscode/
bin/ bin/
# Extracted source trees in Ubuntu package directories # Extracted source trees in Ubuntu package directories
@@ -142,3 +109,7 @@ distro/ubuntu/*/dms-git-repo/
distro/ubuntu/*/DankMaterialShell-*/ distro/ubuntu/*/DankMaterialShell-*/
distro/ubuntu/danklinux/*/dsearch-*/ distro/ubuntu/danklinux/*/dsearch-*/
distro/ubuntu/danklinux/*/dgop-*/ distro/ubuntu/danklinux/*/dgop-*/
# direnv
.envrc
.direnv/

View File

@@ -12,6 +12,21 @@ Enable pre-commit hooks to catch CI failures before pushing:
git config core.hooksPath .githooks git config core.hooksPath .githooks
``` ```
### Nix Development Shell
If you have Nix installed with flakes enabled, you can use the provided development shell which includes all necessary dependencies:
```bash
nix develop
```
This will provide:
- Go 1.24 toolchain (go, gopls, delve, go-tools) and GNU Make
- Quickshell and required QML packages
- Properly configured QML2_IMPORT_PATH
The dev shell automatically creates the `.qmlls.ini` file in the `quickshell/` directory.
## VSCode Setup ## VSCode Setup
This is a monorepo, the easiest thing to do is to open an editor in either `quickshell`, `core`, or both depending on which part of the project you are working on. This is a monorepo, the easiest thing to do is to open an editor in either `quickshell`, `core`, or both depending on which part of the project you are working on.

View File

@@ -435,7 +435,7 @@ func TestHyprlandConfigDeployment(t *testing.T) {
content, err := os.ReadFile(result.Path) content, err := os.ReadFile(result.Path)
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, string(content), "# MONITOR CONFIG") assert.Contains(t, string(content), "# MONITOR CONFIG")
assert.Contains(t, string(content), "bind = $mod, T, exec, ghostty") assert.Contains(t, string(content), "bind = $mod, T, exec, $TERMINAL")
assert.Contains(t, string(content), "exec-once = ") assert.Contains(t, string(content), "exec-once = ")
}) })
@@ -471,7 +471,7 @@ general {
require.NoError(t, err) require.NoError(t, err)
assert.Contains(t, string(newContent), "monitor = DP-1, 1920x1080@144") assert.Contains(t, string(newContent), "monitor = DP-1, 1920x1080@144")
assert.Contains(t, string(newContent), "monitor = HDMI-A-1, 3840x2160@60") assert.Contains(t, string(newContent), "monitor = HDMI-A-1, 3840x2160@60")
assert.Contains(t, string(newContent), "bind = $mod, T, exec, kitty") assert.Contains(t, string(newContent), "bind = $mod, T, exec, $TERMINAL")
assert.NotContains(t, string(newContent), "monitor = eDP-2") assert.NotContains(t, string(newContent), "monitor = eDP-2")
}) })
} }
@@ -487,14 +487,11 @@ func TestNiriConfigStructure(t *testing.T) {
func TestHyprlandConfigStructure(t *testing.T) { func TestHyprlandConfigStructure(t *testing.T) {
assert.Contains(t, HyprlandConfig, "# MONITOR CONFIG") assert.Contains(t, HyprlandConfig, "# MONITOR CONFIG")
assert.Contains(t, HyprlandConfig, "# ENVIRONMENT VARS")
assert.Contains(t, HyprlandConfig, "# STARTUP APPS") assert.Contains(t, HyprlandConfig, "# STARTUP APPS")
assert.Contains(t, HyprlandConfig, "# INPUT CONFIG") assert.Contains(t, HyprlandConfig, "# INPUT CONFIG")
assert.Contains(t, HyprlandConfig, "# KEYBINDINGS") assert.Contains(t, HyprlandConfig, "# KEYBINDINGS")
assert.Contains(t, HyprlandConfig, "{{POLKIT_AGENT_PATH}}") assert.Contains(t, HyprlandConfig, "{{POLKIT_AGENT_PATH}}")
assert.Contains(t, HyprlandConfig, "{{TERMINAL_COMMAND}}") assert.Contains(t, HyprlandConfig, "bind = $mod, T, exec, $TERMINAL")
assert.Contains(t, HyprlandConfig, "exec-once = dms run")
assert.Contains(t, HyprlandConfig, "bind = $mod, T, exec,")
assert.Contains(t, HyprlandConfig, "bind = $mod, space, exec, dms ipc call spotlight toggle") assert.Contains(t, HyprlandConfig, "bind = $mod, space, exec, dms ipc call spotlight toggle")
assert.Contains(t, HyprlandConfig, "windowrule {") assert.Contains(t, HyprlandConfig, "windowrule {")
assert.Contains(t, HyprlandConfig, "match:class = ^(com\\.mitchellh\\.ghostty)$") assert.Contains(t, HyprlandConfig, "match:class = ^(com\\.mitchellh\\.ghostty)$")

View File

@@ -7,20 +7,10 @@
# monitor = eDP-2, 2560x1600@239.998993, 2560x0, 1, vrr, 1 # monitor = eDP-2, 2560x1600@239.998993, 2560x0, 1, vrr, 1
monitor = , preferred,auto,auto monitor = , preferred,auto,auto
# ==================
# ENVIRONMENT VARS
# ==================
env = QT_QPA_PLATFORM,wayland
env = ELECTRON_OZONE_PLATFORM_HINT,auto
env = QT_QPA_PLATFORMTHEME,gtk3
env = QT_QPA_PLATFORMTHEME_QT6,gtk3
env = TERMINAL,{{TERMINAL_COMMAND}}
# ================== # ==================
# STARTUP APPS # STARTUP APPS
# ================== # ==================
exec-once = bash -c "wl-paste --watch cliphist store &" exec-once = bash -c "wl-paste --watch cliphist store &"
exec-once = dms run
exec-once = {{POLKIT_AGENT_PATH}} exec-once = {{POLKIT_AGENT_PATH}}
# ================== # ==================
@@ -233,7 +223,7 @@ layerrule {
$mod = SUPER $mod = SUPER
# === Application Launchers === # === Application Launchers ===
bind = $mod, T, exec, {{TERMINAL_COMMAND}} bind = $mod, T, exec, $TERMINAL
bind = $mod, space, exec, dms ipc call spotlight toggle bind = $mod, space, exec, dms ipc call spotlight toggle
bind = $mod, V, exec, dms ipc call clipboard toggle bind = $mod, V, exec, dms ipc call clipboard toggle
bind = $mod, M, exec, dms ipc call processlist focusOrToggle bind = $mod, M, exec, dms ipc call processlist focusOrToggle

View File

@@ -116,15 +116,9 @@ overview {
// See the binds section below for more spawn examples. // See the binds section below for more spawn examples.
// This line starts waybar, a commonly used bar for Wayland compositors. // This line starts waybar, a commonly used bar for Wayland compositors.
spawn-at-startup "bash" "-c" "wl-paste --watch cliphist store &" spawn-at-startup "bash" "-c" "wl-paste --watch cliphist store &"
spawn-at-startup "dms" "run"
spawn-at-startup "{{POLKIT_AGENT_PATH}}" spawn-at-startup "{{POLKIT_AGENT_PATH}}"
environment { environment {
XDG_CURRENT_DESKTOP "niri" XDG_CURRENT_DESKTOP "niri"
QT_QPA_PLATFORM "wayland"
ELECTRON_OZONE_PLATFORM_HINT "auto"
QT_QPA_PLATFORMTHEME "gtk3"
QT_QPA_PLATFORMTHEME_QT6 "gtk3"
TERMINAL "{{TERMINAL_COMMAND}}"
} }
hotkey-overlay { hotkey-overlay {
skip-at-startup skip-at-startup

View File

@@ -357,6 +357,15 @@ func (a *ArchDistribution) InstallPackages(ctx context.Context, dependencies []d
LogOutput: "Starting post-installation configuration...", LogOutput: "Starting post-installation configuration...",
} }
terminal := a.DetectTerminalFromDeps(dependencies)
if err := a.WriteEnvironmentConfig(terminal); err != nil {
a.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
}
if err := a.EnableDMSService(ctx); err != nil {
a.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
}
// Phase 7: Complete // Phase 7: Complete
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
Phase: PhaseComplete, Phase: PhaseComplete,

View File

@@ -549,6 +549,68 @@ func (b *BaseDistribution) runWithProgressStepTimeout(cmd *exec.Cmd, progressCha
} }
} }
func (b *BaseDistribution) DetectTerminalFromDeps(dependencies []deps.Dependency) deps.Terminal {
for _, dep := range dependencies {
switch dep.Name {
case "ghostty":
return deps.TerminalGhostty
case "kitty":
return deps.TerminalKitty
case "alacritty":
return deps.TerminalAlacritty
}
}
return deps.TerminalGhostty
}
func (b *BaseDistribution) WriteEnvironmentConfig(terminal deps.Terminal) error {
homeDir, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get home directory: %w", err)
}
envDir := filepath.Join(homeDir, ".config", "environment.d")
if err := os.MkdirAll(envDir, 0755); err != nil {
return fmt.Errorf("failed to create environment.d directory: %w", err)
}
var terminalCmd string
switch terminal {
case deps.TerminalGhostty:
terminalCmd = "ghostty"
case deps.TerminalKitty:
terminalCmd = "kitty"
case deps.TerminalAlacritty:
terminalCmd = "alacritty"
default:
terminalCmd = "ghostty"
}
content := fmt.Sprintf(`QT_QPA_PLATFORM=wayland
ELECTRON_OZONE_PLATFORM_HINT=auto
QT_QPA_PLATFORMTHEME=gtk3
QT_QPA_PLATFORMTHEME_QT6=gtk3
TERMINAL=%s
`, terminalCmd)
envFile := filepath.Join(envDir, "90-dms.conf")
if err := os.WriteFile(envFile, []byte(content), 0644); err != nil {
return fmt.Errorf("failed to write environment config: %w", err)
}
b.log(fmt.Sprintf("Wrote environment config to %s", envFile))
return nil
}
func (b *BaseDistribution) EnableDMSService(ctx context.Context) error {
cmd := exec.CommandContext(ctx, "systemctl", "--user", "enable", "--now", "dms")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to enable dms service: %w", err)
}
b.log("Enabled dms systemd user service")
return nil
}
// installDMSBinary installs the DMS binary from GitHub releases // installDMSBinary installs the DMS binary from GitHub releases
func (b *BaseDistribution) installDMSBinary(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error { func (b *BaseDistribution) installDMSBinary(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
b.log("Installing/updating DMS binary...") b.log("Installing/updating DMS binary...")

View File

@@ -333,6 +333,15 @@ func (d *DebianDistribution) InstallPackages(ctx context.Context, dependencies [
LogOutput: "Starting post-installation configuration...", LogOutput: "Starting post-installation configuration...",
} }
terminal := d.DetectTerminalFromDeps(dependencies)
if err := d.WriteEnvironmentConfig(terminal); err != nil {
d.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
}
if err := d.EnableDMSService(ctx); err != nil {
d.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
}
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
Phase: PhaseComplete, Phase: PhaseComplete,
Progress: 1.0, Progress: 1.0,

View File

@@ -357,6 +357,15 @@ func (f *FedoraDistribution) InstallPackages(ctx context.Context, dependencies [
LogOutput: "Starting post-installation configuration...", LogOutput: "Starting post-installation configuration...",
} }
terminal := f.DetectTerminalFromDeps(dependencies)
if err := f.WriteEnvironmentConfig(terminal); err != nil {
f.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
}
if err := f.EnableDMSService(ctx); err != nil {
f.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
}
// Phase 7: Complete // Phase 7: Complete
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
Phase: PhaseComplete, Phase: PhaseComplete,

View File

@@ -451,6 +451,15 @@ func (g *GentooDistribution) InstallPackages(ctx context.Context, dependencies [
LogOutput: "Starting post-installation configuration...", LogOutput: "Starting post-installation configuration...",
} }
terminal := g.DetectTerminalFromDeps(dependencies)
if err := g.WriteEnvironmentConfig(terminal); err != nil {
g.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
}
if err := g.EnableDMSService(ctx); err != nil {
g.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
}
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
Phase: PhaseComplete, Phase: PhaseComplete,
Progress: 1.0, Progress: 1.0,

View File

@@ -372,6 +372,15 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
LogOutput: "Starting post-installation configuration...", LogOutput: "Starting post-installation configuration...",
} }
terminal := o.DetectTerminalFromDeps(dependencies)
if err := o.WriteEnvironmentConfig(terminal); err != nil {
o.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
}
if err := o.EnableDMSService(ctx); err != nil {
o.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
}
// Complete // Complete
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
Phase: PhaseComplete, Phase: PhaseComplete,

View File

@@ -352,6 +352,15 @@ func (u *UbuntuDistribution) InstallPackages(ctx context.Context, dependencies [
LogOutput: "Starting post-installation configuration...", LogOutput: "Starting post-installation configuration...",
} }
terminal := u.DetectTerminalFromDeps(dependencies)
if err := u.WriteEnvironmentConfig(terminal); err != nil {
u.log(fmt.Sprintf("Warning: failed to write environment config: %v", err))
}
if err := u.EnableDMSService(ctx); err != nil {
u.log(fmt.Sprintf("Warning: failed to enable dms service: %v", err))
}
// Phase 7: Complete // Phase 7: Complete
progressChan <- InstallProgressMsg{ progressChan <- InstallProgressMsg{
Phase: PhaseComplete, Phase: PhaseComplete,

View File

@@ -371,6 +371,18 @@ func (n *NiriProvider) buildActionNode(action string) *document.Node {
node.SetName(parts[0]) node.SetName(parts[0])
for _, arg := range parts[1:] { for _, arg := range parts[1:] {
if strings.Contains(arg, "=") {
kv := strings.SplitN(arg, "=", 2)
switch kv[1] {
case "true":
node.AddProperty(kv[0], true, "")
case "false":
node.AddProperty(kv[0], false, "")
default:
node.AddProperty(kv[0], kv[1], "")
}
continue
}
node.AddArgument(arg, "") node.AddArgument(arg, "")
} }
return node return node
@@ -379,7 +391,7 @@ func (n *NiriProvider) buildActionNode(action string) *document.Node {
func (n *NiriProvider) parseActionParts(action string) []string { func (n *NiriProvider) parseActionParts(action string) []string {
var parts []string var parts []string
var current strings.Builder var current strings.Builder
var inQuote, escaped bool var inQuote, escaped, wasQuoted bool
for _, r := range action { for _, r := range action {
switch { switch {
@@ -389,17 +401,19 @@ func (n *NiriProvider) parseActionParts(action string) []string {
case r == '\\': case r == '\\':
escaped = true escaped = true
case r == '"': case r == '"':
wasQuoted = true
inQuote = !inQuote inQuote = !inQuote
case r == ' ' && !inQuote: case r == ' ' && !inQuote:
if current.Len() > 0 { if current.Len() > 0 || wasQuoted {
parts = append(parts, current.String()) parts = append(parts, current.String())
current.Reset() current.Reset()
wasQuoted = false
} }
default: default:
current.WriteRune(r) current.WriteRune(r)
} }
} }
if current.Len() > 0 { if current.Len() > 0 || wasQuoted {
parts = append(parts, current.String()) parts = append(parts, current.String())
} }
return parts return parts
@@ -508,6 +522,10 @@ func (n *NiriProvider) writeBindNode(sb *strings.Builder, bind *overrideBind, in
sb.WriteString(" ") sb.WriteString(" ")
n.writeArg(sb, arg.ValueString(), forceQuote) n.writeArg(sb, arg.ValueString(), forceQuote)
} }
if child.Properties.Exist() {
sb.WriteString(" ")
sb.WriteString(strings.TrimLeft(child.Properties.String(), " "))
}
} }
sb.WriteString("; }\n") sb.WriteString("; }\n")
} }

View File

@@ -265,6 +265,11 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin
for _, arg := range actionNode.Arguments { for _, arg := range actionNode.Arguments {
args = append(args, arg.ValueString()) args = append(args, arg.ValueString())
} }
if actionNode.Properties != nil {
if val, ok := actionNode.Properties.Get("focus"); ok {
args = append(args, "focus="+val.String())
}
}
} }
var description string var description string

View File

@@ -602,8 +602,24 @@ func TestNiriParseActionWithProperties(t *testing.T) {
for _, kb := range result.Section.Keybinds { for _, kb := range result.Section.Keybinds {
switch kb.Action { switch kb.Action {
case "move-column-to-workspace": case "move-column-to-workspace":
if len(kb.Args) != 1 { if len(kb.Args) != 2 {
t.Errorf("move-column-to-workspace should have 1 arg, got %d", len(kb.Args)) t.Errorf("move-column-to-workspace should have 2 args (index + focus), got %d", len(kb.Args))
}
hasIndex := false
hasFocus := false
for _, arg := range kb.Args {
if arg == "1" || arg == "2" {
hasIndex = true
}
if arg == "focus=false" {
hasFocus = true
}
}
if !hasIndex {
t.Errorf("move-column-to-workspace missing index arg")
}
if !hasFocus {
t.Errorf("move-column-to-workspace missing focus=false arg")
} }
case "next-window": case "next-window":
if kb.Key != "Tab" { if kb.Key != "Tab" {

View File

@@ -61,7 +61,9 @@ in {
''; '';
}; };
quickshell = { quickshell = {
package = lib.mkPackageOption pkgs "quickshell" {}; package = lib.mkPackageOption dmsPkgs "quickshell" {
extraDescription = "The quickshell package to use (defaults to be built from source, in the commit 26531f due to unreleased features used by DMS).";
};
}; };
logs.save = lib.mkEnableOption "saving logs from DMS greeter to file"; logs.save = lib.mkEnableOption "saving logs from DMS greeter to file";
logs.path = lib.mkOption { logs.path = lib.mkOption {

View File

@@ -52,7 +52,8 @@
+ "_" + "_"
+ (self.shortRev or "dirty"); + (self.shortRev or "dirty");
in { in {
dms-shell = pkgs.buildGoModule (let dms-shell = pkgs.buildGoModule (
let
rootSrc = ./.; rootSrc = ./.;
in { in {
inherit version; inherit version;
@@ -68,9 +69,9 @@
"-X main.Version=${version}" "-X main.Version=${version}"
]; ];
nativeBuildInputs = [ nativeBuildInputs = with pkgs; [
pkgs.installShellFiles installShellFiles
pkgs.makeWrapper .makeWrapper
]; ];
postInstall = '' postInstall = ''
@@ -112,7 +113,8 @@
mainProgram = "dms"; mainProgram = "dms";
platforms = pkgs.lib.platforms.linux; platforms = pkgs.lib.platforms.linux;
}; };
}); }
);
default = self.packages.${system}.dms-shell; default = self.packages.${system}.dms-shell;
} }
@@ -125,5 +127,38 @@
nixosModules.dankMaterialShell = mkModuleWithDmsPkgs ./distro/nix/nixos.nix; nixosModules.dankMaterialShell = mkModuleWithDmsPkgs ./distro/nix/nixos.nix;
nixosModules.greeter = mkModuleWithDmsPkgs ./distro/nix/greeter.nix; nixosModules.greeter = mkModuleWithDmsPkgs ./distro/nix/greeter.nix;
devShells = forEachSystem (
system: pkgs: let
qmlPkgs =
[
quickshell.packages.${system}.default
]
++ (with pkgs.kdePackages; [
qtdeclarative
kirigami.unwrapped
sonnet
qtmultimedia
]);
in {
default = pkgs.mkShell {
buildInputs = with pkgs;
[
go_1_24
gopls
delve
go-tools
gnumake
]
++ qmlPkgs;
shellHook = ''
touch quickshell/.qmlls.ini 2>/dev/null
'';
QML2_IMPORT_PATH = pkgs.lib.concatStringsSep ":" (map (o: "${o}/lib/qt-6/qml") qmlPkgs);
};
}
);
}; };
} }

View File

@@ -197,14 +197,26 @@ const ACTION_ARGS = {
{ name: "focus", type: "bool", label: "Follow focus", default: false } { name: "focus", type: "bool", label: "Follow focus", default: false }
] ]
}, },
"move-column-to-workspace-down": {
args: [{ name: "focus", type: "bool", label: "Follow focus", default: false }]
},
"move-column-to-workspace-up": {
args: [{ name: "focus", type: "bool", label: "Follow focus", default: false }]
},
"screenshot": { "screenshot": {
args: [{ name: "opts", type: "screenshot", label: "Options" }] args: [{ name: "show-pointer", type: "bool", label: "Show pointer" }]
}, },
"screenshot-screen": { "screenshot-screen": {
args: [{ name: "opts", type: "screenshot", label: "Options" }] args: [
{ name: "show-pointer", type: "bool", label: "Show pointer" },
{ name: "write-to-disk", type: "bool", label: "Save to disk" }
]
}, },
"screenshot-window": { "screenshot-window": {
args: [{ name: "opts", type: "screenshot", label: "Options" }] args: [
{ name: "show-pointer", type: "bool", label: "Show pointer" },
{ name: "write-to-disk", type: "bool", label: "Save to disk" }
]
} }
}; };
@@ -288,11 +300,12 @@ function getActionLabel(action) {
if (!action) if (!action)
return ""; return "";
const dmsAct = findDmsAction(action); var dmsAct = findDmsAction(action);
if (dmsAct) if (dmsAct)
return dmsAct.label; return dmsAct.label;
const compAct = findCompositorAction(action); var base = action.split(" ")[0];
var compAct = findCompositorAction(base);
if (compAct) if (compAct)
return compAct.label; return compAct.label;
@@ -337,7 +350,8 @@ function isValidAction(action) {
function isKnownCompositorAction(action) { function isKnownCompositorAction(action) {
if (!action) if (!action)
return false; return false;
return findCompositorAction(action) !== null; var base = action.split(" ")[0];
return findCompositorAction(base) !== null;
} }
function buildSpawnAction(command, args) { function buildSpawnAction(command, args) {
@@ -404,10 +418,10 @@ function parseCompositorActionArgs(action) {
if (!ACTION_ARGS[base]) if (!ACTION_ARGS[base])
return { base: action, args: {} }; return { base: action, args: {} };
var argConfig = ACTION_ARGS[base];
var argParts = parts.slice(1); var argParts = parts.slice(1);
if (base === "move-column-to-workspace") { switch (base) {
case "move-column-to-workspace":
for (var i = 0; i < argParts.length; i++) { for (var i = 0; i < argParts.length; i++) {
if (argParts[i] === "focus=true" || argParts[i] === "focus=false") { if (argParts[i] === "focus=true" || argParts[i] === "focus=false") {
args.focus = argParts[i] === "focus=true"; args.focus = argParts[i] === "focus=true";
@@ -415,15 +429,25 @@ function parseCompositorActionArgs(action) {
args.index = argParts[i]; args.index = argParts[i];
} }
} }
} else if (base.startsWith("screenshot")) { break;
args.opts = {}; case "move-column-to-workspace-down":
for (var j = 0; j < argParts.length; j += 2) { case "move-column-to-workspace-up":
if (j + 1 < argParts.length) for (var k = 0; k < argParts.length; k++) {
args.opts[argParts[j]] = argParts[j + 1]; if (argParts[k] === "focus=true" || argParts[k] === "focus=false")
args.focus = argParts[k] === "focus=true";
}
break;
default:
if (base.startsWith("screenshot")) {
for (var j = 0; j < argParts.length; j++) {
var kv = argParts[j].split("=");
if (kv.length === 2)
args[kv[0]] = kv[1] === "true";
} }
} else if (argParts.length > 0) { } else if (argParts.length > 0) {
args.value = argParts.join(" "); args.value = argParts.join(" ");
} }
}
return { base: base, args: args }; return { base: base, args: args };
} }
@@ -437,25 +461,30 @@ function buildCompositorAction(base, args) {
if (!args || Object.keys(args).length === 0) if (!args || Object.keys(args).length === 0)
return base; return base;
if (base === "move-column-to-workspace") { switch (base) {
case "move-column-to-workspace":
if (args.index) if (args.index)
parts.push(args.index); parts.push(args.index);
if (args.focus === true) if (args.focus === false)
parts.push("focus=true");
else if (args.focus === false)
parts.push("focus=false"); parts.push("focus=false");
} else if (base.startsWith("screenshot") && args.opts) { break;
for (var key in args.opts) { case "move-column-to-workspace-down":
if (args.opts[key] !== undefined && args.opts[key] !== "") { case "move-column-to-workspace-up":
parts.push(key); if (args.focus === false)
parts.push(args.opts[key]); parts.push("focus=false");
} break;
} default:
if (base.startsWith("screenshot")) {
if (args["show-pointer"] === true)
parts.push("show-pointer=true");
if (args["write-to-disk"] === true)
parts.push("write-to-disk=true");
} else if (args.value) { } else if (args.value) {
parts.push(args.value); parts.push(args.value);
} else if (args.index) { } else if (args.index) {
parts.push(args.index); parts.push(args.index);
} }
}
return parts.join(" "); return parts.join(" ");
} }

View File

@@ -23,6 +23,7 @@ Loader {
property bool isRightBarEdge: false property bool isRightBarEdge: false
property bool isTopBarEdge: false property bool isTopBarEdge: false
property bool isBottomBarEdge: false property bool isBottomBarEdge: false
property string _registeredScreenName: ""
asynchronous: false asynchronous: false
@@ -198,13 +199,16 @@ Loader {
if (!hasPopout) if (!hasPopout)
return; return;
BarWidgetService.registerWidget(widgetId, parentScreen.name, item); _registeredScreenName = parentScreen.name;
BarWidgetService.registerWidget(widgetId, _registeredScreenName, item);
} }
function unregisterWidget() { function unregisterWidget() {
if (!widgetId || !parentScreen?.name) if (!widgetId || !_registeredScreenName)
return; return;
BarWidgetService.unregisterWidget(widgetId, parentScreen.name);
BarWidgetService.unregisterWidget(widgetId, _registeredScreenName);
_registeredScreenName = "";
} }
function getWidgetComponent(widgetId, components) { function getWidgetComponent(widgetId, components) {

View File

@@ -597,7 +597,7 @@ Item {
onSaveBind: (originalKey, newData) => { onSaveBind: (originalKey, newData) => {
KeybindsService.saveBind(originalKey, newData); KeybindsService.saveBind(originalKey, newData);
keybindsTab._editingKey = newData.key; keybindsTab._editingKey = newData.key;
keybindsTab.expandedKey = modelData.action; keybindsTab.expandedKey = newData.action;
} }
onRemoveBind: key => { onRemoveBind: key => {
const remainingKey = bindItem.keys.find(k => k.key !== key)?.key ?? ""; const remainingKey = bindItem.keys.find(k => k.key !== key)?.key ?? "";

View File

@@ -650,9 +650,10 @@ Item {
} }
onWheel: wheel => { onWheel: wheel => {
if (!root.recording) if (!root.recording) {
wheel.accepted = false;
return; return;
}
wheel.accepted = true; wheel.accepted = true;
const mods = []; const mods = [];
@@ -959,12 +960,12 @@ Item {
Layout.preferredWidth: 120 Layout.preferredWidth: 120
compactMode: true compactMode: true
currentValue: { currentValue: {
const action = root.editAction; const base = root.editAction.split(" ")[0];
const cats = KeybindsService.getCompositorCategories(); const cats = KeybindsService.getCompositorCategories();
for (const cat of cats) { for (const cat of cats) {
const actions = KeybindsService.getCompositorActions(cat); const actions = KeybindsService.getCompositorActions(cat);
for (const act of actions) { for (const act of actions) {
if (act.id === action) if (act.id === base)
return cat; return cat;
} }
} }
@@ -1024,12 +1025,13 @@ Item {
} }
RowLayout { RowLayout {
id: optionsRow
Layout.fillWidth: true Layout.fillWidth: true
spacing: Theme.spacingM spacing: Theme.spacingM
visible: root._actionType === "compositor" && !root.useCustomCompositor && Actions.getActionArgConfig(root.editAction) visible: root._actionType === "compositor" && !root.useCustomCompositor && Actions.getActionArgConfig(root.editAction)
property var argConfig: Actions.getActionArgConfig(root.editAction) readonly property var argConfig: Actions.getActionArgConfig(root.editAction)
property var parsedArgs: Actions.parseCompositorActionArgs(root.editAction) readonly property var parsedArgs: Actions.parseCompositorActionArgs(root.editAction)
StyledText { StyledText {
text: I18n.tr("Options") text: I18n.tr("Options")
@@ -1048,56 +1050,75 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 40 Layout.preferredHeight: 40
visible: { visible: {
const cfg = parent.parent.argConfig; const cfg = optionsRow.argConfig;
if (!cfg || !cfg.config || !cfg.config.args) if (!cfg?.config?.args)
return false; return false;
const firstArg = cfg.config.args[0]; const firstArg = cfg.config.args[0];
return firstArg && (firstArg.type === "text" || firstArg.type === "number"); return firstArg && (firstArg.type === "text" || firstArg.type === "number");
} }
placeholderText: { placeholderText: optionsRow.argConfig?.config?.args?.[0]?.placeholder || ""
const cfg = parent.parent.argConfig;
if (!cfg || !cfg.config || !cfg.config.args) Connections {
return ""; target: optionsRow
return cfg.config.args[0]?.placeholder || ""; function onParsedArgsChanged() {
const newText = optionsRow.parsedArgs?.args?.value || optionsRow.parsedArgs?.args?.index || "";
if (argValueField.text !== newText)
argValueField.text = newText;
} }
text: parent.parent.parsedArgs?.args?.value || parent.parent.parsedArgs?.args?.index || "" }
onTextChanged: {
const cfg = parent.parent.argConfig; Component.onCompleted: {
text = optionsRow.parsedArgs?.args?.value || optionsRow.parsedArgs?.args?.index || "";
}
onEditingFinished: {
const cfg = optionsRow.argConfig;
if (!cfg) if (!cfg)
return; return;
const base = parent.parent.parsedArgs?.base || root.editAction.split(" ")[0]; const parsed = optionsRow.parsedArgs;
const args = cfg.config.args[0]?.type === "number" ? { const args = {};
index: text if (cfg.config.args[0]?.type === "number")
} : { args.index = text;
value: text else
}; args.value = text;
if (parsed?.args?.focus === false)
args.focus = false;
root.updateEdit({ root.updateEdit({
action: Actions.buildCompositorAction(base, args) action: Actions.buildCompositorAction(parsed?.base || cfg.base, args)
}); });
} }
} }
RowLayout { RowLayout {
visible: { visible: {
const cfg = parent.parent.argConfig; const cfg = optionsRow.argConfig;
return cfg && cfg.base === "move-column-to-workspace"; if (!cfg)
return false;
switch (cfg.base) {
case "move-column-to-workspace":
case "move-column-to-workspace-down":
case "move-column-to-workspace-up":
return true;
}
return false;
} }
spacing: Theme.spacingXS spacing: Theme.spacingXS
DankToggle { DankToggle {
id: focusToggle id: focusToggle
checked: parent.parent.parent.parsedArgs?.args?.focus === true checked: optionsRow.parsedArgs?.args?.focus !== false
onCheckedChanged: { onToggled: newChecked => {
const cfg = parent.parent.parent.argConfig; const cfg = optionsRow.argConfig;
if (!cfg) if (!cfg)
return; return;
const parsed = parent.parent.parent.parsedArgs; const parsed = optionsRow.parsedArgs;
const args = { const args = {};
index: parsed?.args?.index || "", if (cfg.base === "move-column-to-workspace")
focus: checked args.index = parsed?.args?.index || "";
}; if (!newChecked)
args.focus = false;
root.updateEdit({ root.updateEdit({
action: Actions.buildCompositorAction("move-column-to-workspace", args) action: Actions.buildCompositorAction(cfg.base, args)
}); });
} }
} }
@@ -1110,53 +1131,22 @@ Item {
} }
RowLayout { RowLayout {
visible: { visible: optionsRow.argConfig?.base?.startsWith("screenshot") ?? false
const cfg = parent.parent.argConfig;
return cfg && cfg.base && cfg.base.startsWith("screenshot");
}
spacing: Theme.spacingM spacing: Theme.spacingM
RowLayout {
spacing: Theme.spacingXS
DankToggle {
id: writeToDiskToggle
checked: parent.parent.parent.parent.parsedArgs?.args?.opts?.["write-to-disk"] === "true"
onCheckedChanged: {
const parsed = parent.parent.parent.parent.parsedArgs;
const base = parsed?.base || "screenshot";
const opts = parsed?.args?.opts || {};
opts["write-to-disk"] = checked ? "true" : "";
root.updateEdit({
action: Actions.buildCompositorAction(base, {
opts: opts
})
});
}
}
StyledText {
text: I18n.tr("Save")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
RowLayout { RowLayout {
spacing: Theme.spacingXS spacing: Theme.spacingXS
DankToggle { DankToggle {
id: showPointerToggle id: showPointerToggle
checked: parent.parent.parent.parent.parsedArgs?.args?.opts?.["show-pointer"] === "true" checked: optionsRow.parsedArgs?.args?.["show-pointer"] === true
onCheckedChanged: { onToggled: newChecked => {
const parsed = parent.parent.parent.parent.parsedArgs; const parsed = optionsRow.parsedArgs;
const base = parsed?.base || "screenshot"; const base = parsed?.base || "screenshot";
const opts = parsed?.args?.opts || {}; const args = Object.assign({}, parsed?.args || {});
opts["show-pointer"] = checked ? "true" : ""; args["show-pointer"] = newChecked;
root.updateEdit({ root.updateEdit({
action: Actions.buildCompositorAction(base, { action: Actions.buildCompositorAction(base, args)
opts: opts
})
}); });
} }
} }
@@ -1167,6 +1157,31 @@ Item {
color: Theme.surfaceVariantText color: Theme.surfaceVariantText
} }
} }
RowLayout {
visible: optionsRow.argConfig?.base !== "screenshot"
spacing: Theme.spacingXS
DankToggle {
id: writeToDiskToggle
checked: optionsRow.parsedArgs?.args?.["write-to-disk"] === true
onToggled: newChecked => {
const parsed = optionsRow.parsedArgs;
const base = parsed?.base || "screenshot-screen";
const args = Object.assign({}, parsed?.args || {});
args["write-to-disk"] = newChecked;
root.updateEdit({
action: Actions.buildCompositorAction(base, args)
});
}
}
StyledText {
text: I18n.tr("Save")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
}
}
} }
} }
} }