mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-14 16:22:46 -04:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8fad2826b1 | |||
| 57ee0fb2bd | |||
| 3ef10e73a5 | |||
| dc40492fc7 | |||
| e606a76a86 | |||
| 8838fd67b9 | |||
| c570e20308 | |||
| 0a00ef39e3 | |||
| 9a08b81214 | |||
| c617ae26a2 | |||
| f6a776a692 | |||
| 54b253099d | |||
| f662aca58c | |||
| 76e7755496 | |||
| e05ad81c13 | |||
| cffb16d7f7 | |||
| 18ca571944 |
@@ -0,0 +1,30 @@
|
|||||||
|
name: Check nix flake
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches: [master, main]
|
||||||
|
paths:
|
||||||
|
- "flake.*"
|
||||||
|
- "distro/nix/**"
|
||||||
|
jobs:
|
||||||
|
check-flake:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Create GitHub App token
|
||||||
|
id: app_token
|
||||||
|
uses: actions/create-github-app-token@v1
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.APP_ID }}
|
||||||
|
private-key: ${{ secrets.APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
token: ${{ steps.app_token.outputs.token }}
|
||||||
|
|
||||||
|
- name: Install Nix
|
||||||
|
uses: cachix/install-nix-action@v31
|
||||||
|
|
||||||
|
- name: Update vendorHash in flake.nix
|
||||||
|
run: nix flake check
|
||||||
+4
-33
@@ -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/
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ linters:
|
|||||||
# Signal handling
|
# Signal handling
|
||||||
- (*os.Process).Signal
|
- (*os.Process).Signal
|
||||||
- (*os.Process).Kill
|
- (*os.Process).Kill
|
||||||
|
- syscall.Kill
|
||||||
# DBus cleanup
|
# DBus cleanup
|
||||||
- (*github.com/godbus/dbus/v5.Conn).RemoveMatchSignal
|
- (*github.com/godbus/dbus/v5.Conn).RemoveMatchSignal
|
||||||
- (*github.com/godbus/dbus/v5.Conn).RemoveSignal
|
- (*github.com/godbus/dbus/v5.Conn).RemoveSignal
|
||||||
|
|||||||
@@ -454,7 +454,6 @@ func uninstallPluginCLI(idOrName string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCommonCommands returns the commands available in all builds
|
|
||||||
func getCommonCommands() []*cobra.Command {
|
func getCommonCommands() []*cobra.Command {
|
||||||
return []*cobra.Command{
|
return []*cobra.Command{
|
||||||
versionCmd,
|
versionCmd,
|
||||||
@@ -474,5 +473,6 @@ func getCommonCommands() []*cobra.Command {
|
|||||||
colorCmd,
|
colorCmd,
|
||||||
screenshotCmd,
|
screenshotCmd,
|
||||||
notifyActionCmd,
|
notifyActionCmd,
|
||||||
|
matugenCmd,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/matugen"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var matugenCmd = &cobra.Command{
|
||||||
|
Use: "matugen",
|
||||||
|
Short: "Generate Material Design themes",
|
||||||
|
Long: "Generate Material Design themes using matugen with dank16 color integration",
|
||||||
|
}
|
||||||
|
|
||||||
|
var matugenGenerateCmd = &cobra.Command{
|
||||||
|
Use: "generate",
|
||||||
|
Short: "Generate theme synchronously",
|
||||||
|
Run: runMatugenGenerate,
|
||||||
|
}
|
||||||
|
|
||||||
|
var matugenQueueCmd = &cobra.Command{
|
||||||
|
Use: "queue",
|
||||||
|
Short: "Queue theme generation (uses socket if available)",
|
||||||
|
Run: runMatugenQueue,
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
matugenCmd.AddCommand(matugenGenerateCmd)
|
||||||
|
matugenCmd.AddCommand(matugenQueueCmd)
|
||||||
|
|
||||||
|
for _, cmd := range []*cobra.Command{matugenGenerateCmd, matugenQueueCmd} {
|
||||||
|
cmd.Flags().String("state-dir", "", "State directory for cache files")
|
||||||
|
cmd.Flags().String("shell-dir", "", "DMS shell installation directory")
|
||||||
|
cmd.Flags().String("config-dir", "", "User config directory")
|
||||||
|
cmd.Flags().String("kind", "image", "Source type: image or hex")
|
||||||
|
cmd.Flags().String("value", "", "Wallpaper path or hex color")
|
||||||
|
cmd.Flags().String("mode", "dark", "Color mode: dark or light")
|
||||||
|
cmd.Flags().String("icon-theme", "System Default", "Icon theme name")
|
||||||
|
cmd.Flags().String("matugen-type", "scheme-tonal-spot", "Matugen scheme type")
|
||||||
|
cmd.Flags().Bool("run-user-templates", true, "Run user matugen templates")
|
||||||
|
cmd.Flags().String("stock-colors", "", "Stock theme colors JSON")
|
||||||
|
cmd.Flags().Bool("sync-mode-with-portal", false, "Sync color scheme with GNOME portal")
|
||||||
|
cmd.Flags().Bool("terminals-always-dark", false, "Force terminal themes to dark variant")
|
||||||
|
}
|
||||||
|
|
||||||
|
matugenQueueCmd.Flags().Bool("wait", true, "Wait for completion")
|
||||||
|
matugenQueueCmd.Flags().Duration("timeout", 30*time.Second, "Timeout for waiting")
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMatugenOptions(cmd *cobra.Command) matugen.Options {
|
||||||
|
stateDir, _ := cmd.Flags().GetString("state-dir")
|
||||||
|
shellDir, _ := cmd.Flags().GetString("shell-dir")
|
||||||
|
configDir, _ := cmd.Flags().GetString("config-dir")
|
||||||
|
kind, _ := cmd.Flags().GetString("kind")
|
||||||
|
value, _ := cmd.Flags().GetString("value")
|
||||||
|
mode, _ := cmd.Flags().GetString("mode")
|
||||||
|
iconTheme, _ := cmd.Flags().GetString("icon-theme")
|
||||||
|
matugenType, _ := cmd.Flags().GetString("matugen-type")
|
||||||
|
runUserTemplates, _ := cmd.Flags().GetBool("run-user-templates")
|
||||||
|
stockColors, _ := cmd.Flags().GetString("stock-colors")
|
||||||
|
syncModeWithPortal, _ := cmd.Flags().GetBool("sync-mode-with-portal")
|
||||||
|
terminalsAlwaysDark, _ := cmd.Flags().GetBool("terminals-always-dark")
|
||||||
|
|
||||||
|
return matugen.Options{
|
||||||
|
StateDir: stateDir,
|
||||||
|
ShellDir: shellDir,
|
||||||
|
ConfigDir: configDir,
|
||||||
|
Kind: kind,
|
||||||
|
Value: value,
|
||||||
|
Mode: mode,
|
||||||
|
IconTheme: iconTheme,
|
||||||
|
MatugenType: matugenType,
|
||||||
|
RunUserTemplates: runUserTemplates,
|
||||||
|
StockColors: stockColors,
|
||||||
|
SyncModeWithPortal: syncModeWithPortal,
|
||||||
|
TerminalsAlwaysDark: terminalsAlwaysDark,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMatugenGenerate(cmd *cobra.Command, args []string) {
|
||||||
|
opts := buildMatugenOptions(cmd)
|
||||||
|
if err := matugen.Run(opts); err != nil {
|
||||||
|
log.Fatalf("Theme generation failed: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMatugenQueue(cmd *cobra.Command, args []string) {
|
||||||
|
opts := buildMatugenOptions(cmd)
|
||||||
|
wait, _ := cmd.Flags().GetBool("wait")
|
||||||
|
timeout, _ := cmd.Flags().GetDuration("timeout")
|
||||||
|
|
||||||
|
socketPath := os.Getenv("DMS_SOCKET")
|
||||||
|
if socketPath == "" {
|
||||||
|
var err error
|
||||||
|
socketPath, err = server.FindSocket()
|
||||||
|
if err != nil {
|
||||||
|
log.Info("No socket available, running synchronously")
|
||||||
|
if err := matugen.Run(opts); err != nil {
|
||||||
|
log.Fatalf("Theme generation failed: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.Dial("unix", socketPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Info("Socket connection failed, running synchronously")
|
||||||
|
if err := matugen.Run(opts); err != nil {
|
||||||
|
log.Fatalf("Theme generation failed: %v", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
request := map[string]any{
|
||||||
|
"id": 1,
|
||||||
|
"method": "matugen.queue",
|
||||||
|
"params": map[string]any{
|
||||||
|
"stateDir": opts.StateDir,
|
||||||
|
"shellDir": opts.ShellDir,
|
||||||
|
"configDir": opts.ConfigDir,
|
||||||
|
"kind": opts.Kind,
|
||||||
|
"value": opts.Value,
|
||||||
|
"mode": opts.Mode,
|
||||||
|
"iconTheme": opts.IconTheme,
|
||||||
|
"matugenType": opts.MatugenType,
|
||||||
|
"runUserTemplates": opts.RunUserTemplates,
|
||||||
|
"stockColors": opts.StockColors,
|
||||||
|
"syncModeWithPortal": opts.SyncModeWithPortal,
|
||||||
|
"terminalsAlwaysDark": opts.TerminalsAlwaysDark,
|
||||||
|
"wait": wait,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewEncoder(conn).Encode(request); err != nil {
|
||||||
|
log.Fatalf("Failed to send request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !wait {
|
||||||
|
fmt.Println("Theme generation queued")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
resultCh := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
var response struct {
|
||||||
|
ID int `json:"id"`
|
||||||
|
Result any `json:"result"`
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
if err := json.NewDecoder(conn).Decode(&response); err != nil {
|
||||||
|
resultCh <- fmt.Errorf("failed to read response: %w", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if response.Error != "" {
|
||||||
|
resultCh <- fmt.Errorf("server error: %s", response.Error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resultCh <- nil
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-resultCh:
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Theme generation failed: %v", err)
|
||||||
|
}
|
||||||
|
fmt.Println("Theme generation completed")
|
||||||
|
case <-ctx.Done():
|
||||||
|
log.Fatalf("Timeout waiting for theme generation")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -35,7 +35,7 @@ Modes:
|
|||||||
full - Capture the focused output
|
full - Capture the focused output
|
||||||
all - Capture all outputs combined
|
all - Capture all outputs combined
|
||||||
output - Capture a specific output by name
|
output - Capture a specific output by name
|
||||||
window - Capture the focused window (Hyprland only)
|
window - Capture the focused window (Hyprland/DWL)
|
||||||
last - Capture the last selected region
|
last - Capture the last selected region
|
||||||
|
|
||||||
Output format (--format):
|
Output format (--format):
|
||||||
@@ -91,9 +91,8 @@ If no previous region exists, falls back to interactive selection.`,
|
|||||||
var ssWindowCmd = &cobra.Command{
|
var ssWindowCmd = &cobra.Command{
|
||||||
Use: "window",
|
Use: "window",
|
||||||
Short: "Capture the focused window",
|
Short: "Capture the focused window",
|
||||||
Long: `Capture the currently focused window.
|
Long: `Capture the currently focused window. Supported on Hyprland and DWL.`,
|
||||||
Currently only supported on Hyprland.`,
|
Run: runScreenshotWindow,
|
||||||
Run: runScreenshotWindow,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ssListCmd = &cobra.Command{
|
var ssListCmd = &cobra.Command{
|
||||||
|
|||||||
@@ -504,7 +504,7 @@ func parseTargetsFromIPCShowOutput(output string) ipcTargets {
|
|||||||
return targets
|
return targets
|
||||||
}
|
}
|
||||||
|
|
||||||
func getShellIPCCompletions(args []string, toComplete string) []string {
|
func getShellIPCCompletions(args []string, _ string) []string {
|
||||||
cmdArgs := []string{"-p", configPath, "ipc", "show"}
|
cmdArgs := []string{"-p", configPath, "ipc", "show"}
|
||||||
cmd := exec.Command("qs", cmdArgs...)
|
cmd := exec.Command("qs", cmdArgs...)
|
||||||
var targets ipcTargets
|
var targets ipcTargets
|
||||||
|
|||||||
@@ -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,16 +487,14 @@ 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, "windowrulev2 = noborder, class:^(com\\.mitchellh\\.ghostty)$")
|
assert.Contains(t, HyprlandConfig, "windowrule {")
|
||||||
|
assert.Contains(t, HyprlandConfig, "match:class = ^(com\\.mitchellh\\.ghostty)$")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGhosttyConfigStructure(t *testing.T) {
|
func TestGhosttyConfigStructure(t *testing.T) {
|
||||||
|
|||||||
@@ -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}}
|
||||||
|
|
||||||
# ==================
|
# ==================
|
||||||
@@ -100,36 +90,132 @@ misc {
|
|||||||
# ==================
|
# ==================
|
||||||
# WINDOW RULES
|
# WINDOW RULES
|
||||||
# ==================
|
# ==================
|
||||||
windowrulev2 = tile, class:^(org\.wezfurlong\.wezterm)$
|
windowrule {
|
||||||
|
name = windowrule-1
|
||||||
|
tile = on
|
||||||
|
match:class = ^(org\.wezfurlong\.wezterm)$
|
||||||
|
border_size = 0
|
||||||
|
}
|
||||||
|
|
||||||
windowrulev2 = rounding 12, class:^(org\.gnome\.)
|
|
||||||
windowrulev2 = noborder, class:^(org\.gnome\.)
|
|
||||||
|
|
||||||
windowrulev2 = tile, class:^(gnome-control-center)$
|
windowrule {
|
||||||
windowrulev2 = tile, class:^(pavucontrol)$
|
name = windowrule-2
|
||||||
windowrulev2 = tile, class:^(nm-connection-editor)$
|
rounding = 12
|
||||||
|
match:class = ^(org\.gnome\.)
|
||||||
|
border_size = 0
|
||||||
|
}
|
||||||
|
|
||||||
windowrulev2 = float, class:^(gnome-calculator)$
|
|
||||||
windowrulev2 = float, class:^(galculator)$
|
|
||||||
windowrulev2 = float, class:^(blueman-manager)$
|
|
||||||
windowrulev2 = float, class:^(org\.gnome\.Nautilus)$
|
|
||||||
windowrulev2 = float, class:^(steam)$
|
|
||||||
windowrulev2 = float, class:^(xdg-desktop-portal)$
|
|
||||||
|
|
||||||
windowrulev2 = noborder, class:^(org\.wezfurlong\.wezterm)$
|
|
||||||
windowrulev2 = noborder, class:^(Alacritty)$
|
|
||||||
windowrulev2 = noborder, class:^(zen)$
|
|
||||||
windowrulev2 = noborder, class:^(com\.mitchellh\.ghostty)$
|
|
||||||
windowrulev2 = noborder, class:^(kitty)$
|
|
||||||
|
|
||||||
windowrulev2 = float, class:^(firefox)$, title:^(Picture-in-Picture)$
|
windowrule {
|
||||||
windowrulev2 = float, class:^(zoom)$
|
name = windowrule-3
|
||||||
|
tile = on
|
||||||
|
match:class = ^(gnome-control-center)$
|
||||||
|
}
|
||||||
|
|
||||||
# DMS windows floating by default
|
windowrule {
|
||||||
windowrulev2 = float, class:^(org.quickshell)$
|
name = windowrule-4
|
||||||
windowrulev2 = opacity 0.9 0.9, floating:0, focus:0
|
tile = on
|
||||||
|
match:class = ^(pavucontrol)$
|
||||||
|
}
|
||||||
|
|
||||||
layerrule = noanim, ^(quickshell)$
|
windowrule {
|
||||||
|
name = windowrule-5
|
||||||
|
tile = on
|
||||||
|
match:class = ^(nm-connection-editor)$
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-6
|
||||||
|
float = on
|
||||||
|
match:class = ^(gnome-calculator)$
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-7
|
||||||
|
float = on
|
||||||
|
match:class = ^(galculator)$
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-8
|
||||||
|
float = on
|
||||||
|
match:class = ^(blueman-manager)$
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-9
|
||||||
|
float = on
|
||||||
|
match:class = ^(org\.gnome\.Nautilus)$
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-10
|
||||||
|
float = on
|
||||||
|
match:class = ^(steam)$
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-11
|
||||||
|
float = on
|
||||||
|
match:class = ^(xdg-desktop-portal)$
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-12
|
||||||
|
border_size = 0
|
||||||
|
match:class = ^(Alacritty)$
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-13
|
||||||
|
border_size = 0
|
||||||
|
match:class = ^(zen)$
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-14
|
||||||
|
border_size = 0
|
||||||
|
match:class = ^(com\.mitchellh\.ghostty)$
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-15
|
||||||
|
border_size = 0
|
||||||
|
match:class = ^(kitty)$
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-16
|
||||||
|
float = on
|
||||||
|
match:class = ^(firefox)$
|
||||||
|
match:title = ^(Picture-in-Picture)$
|
||||||
|
}
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-17
|
||||||
|
float = on
|
||||||
|
match:class = ^(zoom)$
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
windowrule {
|
||||||
|
name = windowrule-18
|
||||||
|
opacity = 0.9 0.9
|
||||||
|
match:float = 0
|
||||||
|
match:focus = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
layerrule {
|
||||||
|
name = layerrule-1
|
||||||
|
no_anim = on
|
||||||
|
match:namespace = ^(quickshell)$
|
||||||
|
}
|
||||||
|
|
||||||
# ==================
|
# ==================
|
||||||
# KEYBINDINGS
|
# KEYBINDINGS
|
||||||
@@ -137,7 +223,7 @@ layerrule = noanim, ^(quickshell)$
|
|||||||
$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
|
||||||
@@ -281,12 +367,9 @@ binde = $mod SHIFT, minus, resizeactive, 0 -10%
|
|||||||
binde = $mod SHIFT, equal, resizeactive, 0 10%
|
binde = $mod SHIFT, equal, resizeactive, 0 10%
|
||||||
|
|
||||||
# === Screenshots ===
|
# === Screenshots ===
|
||||||
bind = , XF86Launch1, exec, grimblast copy area
|
bind = , Print, exec, dms screenshot
|
||||||
bind = CTRL, XF86Launch1, exec, grimblast copy screen
|
bind = CTRL, Print, exec, dms screenshot full
|
||||||
bind = ALT, XF86Launch1, exec, grimblast copy active
|
bind = ALT, Print, exec, dms screenshot window
|
||||||
bind = , Print, exec, grimblast copy area
|
|
||||||
bind = CTRL, Print, exec, grimblast copy screen
|
|
||||||
bind = ALT, Print, exec, grimblast copy active
|
|
||||||
|
|
||||||
# === System Controls ===
|
# === System Controls ===
|
||||||
bind = $mod SHIFT, P, dpms, off
|
bind = $mod SHIFT, P, dpms, off
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -228,10 +228,7 @@ func (b *BaseDistribution) detectHyprlandTools() []deps.Dependency {
|
|||||||
name string
|
name string
|
||||||
description string
|
description string
|
||||||
}{
|
}{
|
||||||
{"grim", "Screenshot utility for Wayland"},
|
|
||||||
{"slurp", "Region selection utility for Wayland"},
|
|
||||||
{"hyprctl", "Hyprland control utility"},
|
{"hyprctl", "Hyprland control utility"},
|
||||||
{"grimblast", "Screenshot script for Hyprland"},
|
|
||||||
{"jq", "JSON processor"},
|
{"jq", "JSON processor"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,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...")
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -62,10 +62,6 @@ func (m *ManualPackageInstaller) InstallManualPackages(ctx context.Context, pack
|
|||||||
if err := m.installDgop(ctx, sudoPassword, progressChan); err != nil {
|
if err := m.installDgop(ctx, sudoPassword, progressChan); err != nil {
|
||||||
return fmt.Errorf("failed to install dgop: %w", err)
|
return fmt.Errorf("failed to install dgop: %w", err)
|
||||||
}
|
}
|
||||||
case "grimblast":
|
|
||||||
if err := m.installGrimblast(ctx, sudoPassword, progressChan); err != nil {
|
|
||||||
return fmt.Errorf("failed to install grimblast: %w", err)
|
|
||||||
}
|
|
||||||
case "niri":
|
case "niri":
|
||||||
if err := m.installNiri(ctx, sudoPassword, progressChan); err != nil {
|
if err := m.installNiri(ctx, sudoPassword, progressChan); err != nil {
|
||||||
return fmt.Errorf("failed to install niri: %w", err)
|
return fmt.Errorf("failed to install niri: %w", err)
|
||||||
@@ -166,62 +162,6 @@ func (m *ManualPackageInstaller) installDgop(ctx context.Context, sudoPassword s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *ManualPackageInstaller) installGrimblast(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
|
||||||
m.log("Installing grimblast script for Hyprland...")
|
|
||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
|
||||||
Phase: PhaseSystemPackages,
|
|
||||||
Progress: 0.1,
|
|
||||||
Step: "Downloading grimblast script...",
|
|
||||||
IsComplete: false,
|
|
||||||
CommandInfo: "curl grimblast script",
|
|
||||||
}
|
|
||||||
|
|
||||||
grimblastURL := "https://raw.githubusercontent.com/hyprwm/contrib/refs/heads/main/grimblast/grimblast"
|
|
||||||
tmpPath := filepath.Join(os.TempDir(), "grimblast")
|
|
||||||
|
|
||||||
downloadCmd := exec.CommandContext(ctx, "curl", "-L", "-o", tmpPath, grimblastURL)
|
|
||||||
if err := downloadCmd.Run(); err != nil {
|
|
||||||
m.logError("failed to download grimblast", err)
|
|
||||||
return fmt.Errorf("failed to download grimblast: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
|
||||||
Phase: PhaseSystemPackages,
|
|
||||||
Progress: 0.5,
|
|
||||||
Step: "Making grimblast executable...",
|
|
||||||
IsComplete: false,
|
|
||||||
CommandInfo: "chmod +x grimblast",
|
|
||||||
}
|
|
||||||
|
|
||||||
chmodCmd := exec.CommandContext(ctx, "chmod", "+x", tmpPath)
|
|
||||||
if err := chmodCmd.Run(); err != nil {
|
|
||||||
m.logError("failed to make grimblast executable", err)
|
|
||||||
return fmt.Errorf("failed to make grimblast executable: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
progressChan <- InstallProgressMsg{
|
|
||||||
Phase: PhaseSystemPackages,
|
|
||||||
Progress: 0.8,
|
|
||||||
Step: "Installing grimblast to /usr/local/bin...",
|
|
||||||
IsComplete: false,
|
|
||||||
NeedsSudo: true,
|
|
||||||
CommandInfo: "sudo cp grimblast /usr/local/bin/",
|
|
||||||
}
|
|
||||||
|
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword,
|
|
||||||
fmt.Sprintf("cp %s /usr/local/bin/grimblast", tmpPath))
|
|
||||||
if err := installCmd.Run(); err != nil {
|
|
||||||
m.logError("failed to install grimblast", err)
|
|
||||||
return fmt.Errorf("failed to install grimblast: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
os.Remove(tmpPath)
|
|
||||||
|
|
||||||
m.log("grimblast installed successfully to /usr/local/bin")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *ManualPackageInstaller) installNiri(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (m *ManualPackageInstaller) installNiri(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
m.log("Installing niri from source...")
|
m.log("Installing niri from source...")
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ func (m Model) categorizeDependencies() map[string][]DependencyInfo {
|
|||||||
switch dep.Name {
|
switch dep.Name {
|
||||||
case "dms (DankMaterialShell)", "quickshell":
|
case "dms (DankMaterialShell)", "quickshell":
|
||||||
categories["Shell"] = append(categories["Shell"], dep)
|
categories["Shell"] = append(categories["Shell"], dep)
|
||||||
case "hyprland", "grim", "slurp", "hyprctl", "grimblast":
|
case "hyprland", "hyprctl":
|
||||||
categories["Hyprland Components"] = append(categories["Hyprland Components"], dep)
|
categories["Hyprland Components"] = append(categories["Hyprland Components"], dep)
|
||||||
case "niri":
|
case "niri":
|
||||||
categories["Niri Components"] = append(categories["Niri Components"], dep)
|
categories["Niri Components"] = append(categories["Niri Components"], dep)
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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" {
|
||||||
|
|||||||
@@ -0,0 +1,587 @@
|
|||||||
|
package matugen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
matugenVersionOnce sync.Once
|
||||||
|
matugenSupportsCOE bool
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
StateDir string
|
||||||
|
ShellDir string
|
||||||
|
ConfigDir string
|
||||||
|
Kind string
|
||||||
|
Value string
|
||||||
|
Mode string
|
||||||
|
IconTheme string
|
||||||
|
MatugenType string
|
||||||
|
RunUserTemplates bool
|
||||||
|
StockColors string
|
||||||
|
SyncModeWithPortal bool
|
||||||
|
TerminalsAlwaysDark bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ColorsOutput struct {
|
||||||
|
Colors struct {
|
||||||
|
Dark map[string]string `json:"dark"`
|
||||||
|
Light map[string]string `json:"light"`
|
||||||
|
} `json:"colors"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Options) ColorsOutput() string {
|
||||||
|
return filepath.Join(o.StateDir, "dms-colors.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Run(opts Options) error {
|
||||||
|
if opts.StateDir == "" {
|
||||||
|
return fmt.Errorf("state-dir is required")
|
||||||
|
}
|
||||||
|
if opts.ShellDir == "" {
|
||||||
|
return fmt.Errorf("shell-dir is required")
|
||||||
|
}
|
||||||
|
if opts.ConfigDir == "" {
|
||||||
|
return fmt.Errorf("config-dir is required")
|
||||||
|
}
|
||||||
|
if opts.Kind == "" {
|
||||||
|
return fmt.Errorf("kind is required")
|
||||||
|
}
|
||||||
|
if opts.Value == "" {
|
||||||
|
return fmt.Errorf("value is required")
|
||||||
|
}
|
||||||
|
if opts.Mode == "" {
|
||||||
|
opts.Mode = "dark"
|
||||||
|
}
|
||||||
|
if opts.MatugenType == "" {
|
||||||
|
opts.MatugenType = "scheme-tonal-spot"
|
||||||
|
}
|
||||||
|
if opts.IconTheme == "" {
|
||||||
|
opts.IconTheme = "System Default"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(opts.StateDir, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to create state dir: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Building theme: %s %s (%s)", opts.Kind, opts.Value, opts.Mode)
|
||||||
|
|
||||||
|
if err := buildOnce(&opts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if opts.SyncModeWithPortal {
|
||||||
|
syncColorScheme(opts.Mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Done")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildOnce(opts *Options) error {
|
||||||
|
cfgFile, err := os.CreateTemp("", "matugen-config-*.toml")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create temp config: %w", err)
|
||||||
|
}
|
||||||
|
defer os.Remove(cfgFile.Name())
|
||||||
|
defer cfgFile.Close()
|
||||||
|
|
||||||
|
tmpDir, err := os.MkdirTemp("", "matugen-templates-*")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create temp dir: %w", err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
if err := buildMergedConfig(opts, cfgFile, tmpDir); err != nil {
|
||||||
|
return fmt.Errorf("failed to build config: %w", err)
|
||||||
|
}
|
||||||
|
cfgFile.Close()
|
||||||
|
|
||||||
|
var primaryDark, primaryLight, surface string
|
||||||
|
var dank16JSON string
|
||||||
|
var importArgs []string
|
||||||
|
|
||||||
|
if opts.StockColors != "" {
|
||||||
|
log.Info("Using stock/custom theme colors with matugen base")
|
||||||
|
primaryDark = extractNestedColor(opts.StockColors, "primary", "dark")
|
||||||
|
primaryLight = extractNestedColor(opts.StockColors, "primary", "light")
|
||||||
|
surface = extractNestedColor(opts.StockColors, "surface", "dark")
|
||||||
|
|
||||||
|
if primaryDark == "" {
|
||||||
|
return fmt.Errorf("failed to extract primary dark from stock colors")
|
||||||
|
}
|
||||||
|
if primaryLight == "" {
|
||||||
|
primaryLight = primaryDark
|
||||||
|
}
|
||||||
|
|
||||||
|
dank16JSON = generateDank16Variants(primaryDark, primaryLight, surface, opts.Mode)
|
||||||
|
importData := fmt.Sprintf(`{"colors": %s, "dank16": %s}`, opts.StockColors, dank16JSON)
|
||||||
|
importArgs = []string{"--import-json-string", importData}
|
||||||
|
|
||||||
|
log.Info("Running matugen color hex with stock color overrides")
|
||||||
|
args := []string{"color", "hex", primaryDark, "-m", opts.Mode, "-t", opts.MatugenType, "-c", cfgFile.Name()}
|
||||||
|
args = append(args, importArgs...)
|
||||||
|
if err := runMatugen(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Infof("Using dynamic theme from %s: %s", opts.Kind, opts.Value)
|
||||||
|
|
||||||
|
matJSON, err := runMatugenDryRun(opts)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("matugen dry-run failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
primaryDark = extractMatugenColor(matJSON, "primary", "dark")
|
||||||
|
primaryLight = extractMatugenColor(matJSON, "primary", "light")
|
||||||
|
surface = extractMatugenColor(matJSON, "surface", "dark")
|
||||||
|
|
||||||
|
if primaryDark == "" {
|
||||||
|
return fmt.Errorf("failed to extract primary color")
|
||||||
|
}
|
||||||
|
if primaryLight == "" {
|
||||||
|
primaryLight = primaryDark
|
||||||
|
}
|
||||||
|
|
||||||
|
dank16JSON = generateDank16Variants(primaryDark, primaryLight, surface, opts.Mode)
|
||||||
|
importData := fmt.Sprintf(`{"dank16": %s}`, dank16JSON)
|
||||||
|
importArgs = []string{"--import-json-string", importData}
|
||||||
|
|
||||||
|
log.Infof("Running matugen %s with dank16 injection", opts.Kind)
|
||||||
|
var args []string
|
||||||
|
switch opts.Kind {
|
||||||
|
case "hex":
|
||||||
|
args = []string{"color", "hex", opts.Value}
|
||||||
|
default:
|
||||||
|
args = []string{opts.Kind, opts.Value}
|
||||||
|
}
|
||||||
|
args = append(args, "-m", opts.Mode, "-t", opts.MatugenType, "-c", cfgFile.Name())
|
||||||
|
args = append(args, importArgs...)
|
||||||
|
if err := runMatugen(args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshGTK(opts.ConfigDir, opts.Mode)
|
||||||
|
signalTerminals()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildMergedConfig(opts *Options, cfgFile *os.File, tmpDir string) error {
|
||||||
|
userConfigPath := filepath.Join(opts.ConfigDir, "matugen", "config.toml")
|
||||||
|
|
||||||
|
wroteConfig := false
|
||||||
|
if opts.RunUserTemplates {
|
||||||
|
if data, err := os.ReadFile(userConfigPath); err == nil {
|
||||||
|
configSection := extractTOMLSection(string(data), "[config]", "[templates]")
|
||||||
|
if configSection != "" {
|
||||||
|
cfgFile.WriteString(configSection)
|
||||||
|
cfgFile.WriteString("\n")
|
||||||
|
wroteConfig = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !wroteConfig {
|
||||||
|
cfgFile.WriteString("[config]\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseConfigPath := filepath.Join(opts.ShellDir, "matugen", "configs", "base.toml")
|
||||||
|
if data, err := os.ReadFile(baseConfigPath); err == nil {
|
||||||
|
content := string(data)
|
||||||
|
lines := strings.Split(content, "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.TrimSpace(line) == "[config]" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cfgFile.WriteString(substituteShellDir(line, opts.ShellDir) + "\n")
|
||||||
|
}
|
||||||
|
cfgFile.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(cfgFile, `[templates.dank]
|
||||||
|
input_path = '%s/matugen/templates/dank.json'
|
||||||
|
output_path = '%s'
|
||||||
|
|
||||||
|
`, opts.ShellDir, opts.ColorsOutput())
|
||||||
|
|
||||||
|
switch opts.Mode {
|
||||||
|
case "light":
|
||||||
|
appendConfig(opts, cfgFile, "skip", "gtk3-light.toml")
|
||||||
|
default:
|
||||||
|
appendConfig(opts, cfgFile, "skip", "gtk3-dark.toml")
|
||||||
|
}
|
||||||
|
|
||||||
|
appendConfig(opts, cfgFile, "niri", "niri.toml")
|
||||||
|
appendConfig(opts, cfgFile, "qt5ct", "qt5ct.toml")
|
||||||
|
appendConfig(opts, cfgFile, "qt6ct", "qt6ct.toml")
|
||||||
|
appendConfig(opts, cfgFile, "firefox", "firefox.toml")
|
||||||
|
appendConfig(opts, cfgFile, "pywalfox", "pywalfox.toml")
|
||||||
|
appendConfig(opts, cfgFile, "vesktop", "vesktop.toml")
|
||||||
|
|
||||||
|
appendTerminalConfig(opts, cfgFile, tmpDir, "ghostty", "ghostty.toml")
|
||||||
|
appendTerminalConfig(opts, cfgFile, tmpDir, "kitty", "kitty.toml")
|
||||||
|
appendTerminalConfig(opts, cfgFile, tmpDir, "foot", "foot.toml")
|
||||||
|
appendTerminalConfig(opts, cfgFile, tmpDir, "alacritty", "alacritty.toml")
|
||||||
|
appendTerminalConfig(opts, cfgFile, tmpDir, "wezterm", "wezterm.toml")
|
||||||
|
|
||||||
|
appendConfig(opts, cfgFile, "dgop", "dgop.toml")
|
||||||
|
|
||||||
|
homeDir, _ := os.UserHomeDir()
|
||||||
|
appendVSCodeConfig(cfgFile, "vscode", filepath.Join(homeDir, ".vscode/extensions/local.dynamic-base16-dankshell-0.0.1"), opts.ShellDir)
|
||||||
|
appendVSCodeConfig(cfgFile, "codium", filepath.Join(homeDir, ".vscode-oss/extensions/local.dynamic-base16-dankshell-0.0.1"), opts.ShellDir)
|
||||||
|
appendVSCodeConfig(cfgFile, "codeoss", filepath.Join(homeDir, ".config/Code - OSS/extensions/local.dynamic-base16-dankshell-0.0.1"), opts.ShellDir)
|
||||||
|
appendVSCodeConfig(cfgFile, "cursor", filepath.Join(homeDir, ".cursor/extensions/local.dynamic-base16-dankshell-0.0.1"), opts.ShellDir)
|
||||||
|
appendVSCodeConfig(cfgFile, "windsurf", filepath.Join(homeDir, ".windsurf/extensions/local.dynamic-base16-dankshell-0.0.1"), opts.ShellDir)
|
||||||
|
|
||||||
|
if opts.RunUserTemplates {
|
||||||
|
if data, err := os.ReadFile(userConfigPath); err == nil {
|
||||||
|
templatesSection := extractTOMLSection(string(data), "[templates]", "")
|
||||||
|
if templatesSection != "" {
|
||||||
|
cfgFile.WriteString(templatesSection)
|
||||||
|
cfgFile.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
userPluginConfigDir := filepath.Join(opts.ConfigDir, "matugen", "dms", "configs")
|
||||||
|
if entries, err := os.ReadDir(userPluginConfigDir); err == nil {
|
||||||
|
for _, entry := range entries {
|
||||||
|
if !strings.HasSuffix(entry.Name(), ".toml") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if data, err := os.ReadFile(filepath.Join(userPluginConfigDir, entry.Name())); err == nil {
|
||||||
|
cfgFile.WriteString(string(data))
|
||||||
|
cfgFile.WriteString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendConfig(opts *Options, cfgFile *os.File, checkCmd, fileName string) {
|
||||||
|
configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName)
|
||||||
|
if _, err := os.Stat(configPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if checkCmd != "skip" && !commandExists(checkCmd) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cfgFile.WriteString(substituteShellDir(string(data), opts.ShellDir))
|
||||||
|
cfgFile.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendTerminalConfig(opts *Options, cfgFile *os.File, tmpDir, checkCmd, fileName string) {
|
||||||
|
configPath := filepath.Join(opts.ShellDir, "matugen", "configs", fileName)
|
||||||
|
if _, err := os.Stat(configPath); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if checkCmd != "skip" && !commandExists(checkCmd) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, err := os.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content := string(data)
|
||||||
|
|
||||||
|
if !opts.TerminalsAlwaysDark {
|
||||||
|
cfgFile.WriteString(substituteShellDir(content, opts.ShellDir))
|
||||||
|
cfgFile.WriteString("\n")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.Split(content, "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if !strings.Contains(line, "input_path") || !strings.Contains(line, "SHELL_DIR/matugen/templates/") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
start := strings.Index(line, "'SHELL_DIR/matugen/templates/")
|
||||||
|
if start == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
end := strings.Index(line[start+1:], "'")
|
||||||
|
if end == -1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
templateName := line[start+len("'SHELL_DIR/matugen/templates/") : start+1+end]
|
||||||
|
origPath := filepath.Join(opts.ShellDir, "matugen", "templates", templateName)
|
||||||
|
|
||||||
|
origData, err := os.ReadFile(origPath)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
modified := strings.ReplaceAll(string(origData), ".default.", ".dark.")
|
||||||
|
tmpPath := filepath.Join(tmpDir, templateName)
|
||||||
|
if err := os.WriteFile(tmpPath, []byte(modified), 0644); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
content = strings.ReplaceAll(content,
|
||||||
|
fmt.Sprintf("'SHELL_DIR/matugen/templates/%s'", templateName),
|
||||||
|
fmt.Sprintf("'%s'", tmpPath))
|
||||||
|
}
|
||||||
|
|
||||||
|
cfgFile.WriteString(substituteShellDir(content, opts.ShellDir))
|
||||||
|
cfgFile.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendVSCodeConfig(cfgFile *os.File, name, extDir, shellDir string) {
|
||||||
|
if _, err := os.Stat(extDir); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
templateDir := filepath.Join(shellDir, "matugen", "templates")
|
||||||
|
fmt.Fprintf(cfgFile, `[templates.dms%sdefault]
|
||||||
|
input_path = '%s/vscode-color-theme-default.json'
|
||||||
|
output_path = '%s/themes/dankshell-default.json'
|
||||||
|
|
||||||
|
[templates.dms%sdark]
|
||||||
|
input_path = '%s/vscode-color-theme-dark.json'
|
||||||
|
output_path = '%s/themes/dankshell-dark.json'
|
||||||
|
|
||||||
|
[templates.dms%slight]
|
||||||
|
input_path = '%s/vscode-color-theme-light.json'
|
||||||
|
output_path = '%s/themes/dankshell-light.json'
|
||||||
|
|
||||||
|
`, name, templateDir, extDir,
|
||||||
|
name, templateDir, extDir,
|
||||||
|
name, templateDir, extDir)
|
||||||
|
log.Infof("Added %s theme config (extension found at %s)", name, extDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func substituteShellDir(content, shellDir string) string {
|
||||||
|
return strings.ReplaceAll(content, "'SHELL_DIR/", "'"+shellDir+"/")
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractTOMLSection(content, startMarker, endMarker string) string {
|
||||||
|
startIdx := strings.Index(content, startMarker)
|
||||||
|
if startIdx == -1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if endMarker == "" {
|
||||||
|
return content[startIdx:]
|
||||||
|
}
|
||||||
|
|
||||||
|
endIdx := strings.Index(content[startIdx:], endMarker)
|
||||||
|
if endIdx == -1 {
|
||||||
|
return content[startIdx:]
|
||||||
|
}
|
||||||
|
return content[startIdx : startIdx+endIdx]
|
||||||
|
}
|
||||||
|
|
||||||
|
func commandExists(name string) bool {
|
||||||
|
_, err := exec.LookPath(name)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkMatugenVersion() {
|
||||||
|
matugenVersionOnce.Do(func() {
|
||||||
|
cmd := exec.Command("matugen", "--version")
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
versionStr := strings.TrimSpace(string(output))
|
||||||
|
versionStr = strings.TrimPrefix(versionStr, "matugen ")
|
||||||
|
|
||||||
|
parts := strings.Split(versionStr, ".")
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
major, err := strconv.Atoi(parts[0])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
minor, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
matugenSupportsCOE = major > 3 || (major == 3 && minor >= 1)
|
||||||
|
if matugenSupportsCOE {
|
||||||
|
log.Infof("Matugen %s supports --continue-on-error", versionStr)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMatugen(args []string) error {
|
||||||
|
checkMatugenVersion()
|
||||||
|
|
||||||
|
if matugenSupportsCOE {
|
||||||
|
args = append([]string{"--continue-on-error"}, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.Command("matugen", args...)
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMatugenDryRun(opts *Options) (string, error) {
|
||||||
|
var args []string
|
||||||
|
switch opts.Kind {
|
||||||
|
case "hex":
|
||||||
|
args = []string{"color", "hex", opts.Value}
|
||||||
|
default:
|
||||||
|
args = []string{opts.Kind, opts.Value}
|
||||||
|
}
|
||||||
|
args = append(args, "-m", "dark", "-t", opts.MatugenType, "--json", "hex", "--dry-run")
|
||||||
|
|
||||||
|
cmd := exec.Command("matugen", args...)
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.ReplaceAll(string(output), "\n", ""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractMatugenColor(jsonStr, colorName, variant string) string {
|
||||||
|
var data map[string]any
|
||||||
|
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
colors, ok := data["colors"].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
colorData, ok := colors[colorName].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
variantData, ok := colorData[variant].(string)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return variantData
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractNestedColor(jsonStr, colorName, variant string) string {
|
||||||
|
var data map[string]any
|
||||||
|
if err := json.Unmarshal([]byte(jsonStr), &data); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
colorData, ok := data[colorName].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
variantData, ok := colorData[variant].(map[string]any)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
color, ok := variantData["color"].(string)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateDank16Variants(primaryDark, primaryLight, surface, mode string) string {
|
||||||
|
variantOpts := dank16.VariantOptions{
|
||||||
|
PrimaryDark: primaryDark,
|
||||||
|
PrimaryLight: primaryLight,
|
||||||
|
Background: surface,
|
||||||
|
UseDPS: true,
|
||||||
|
IsLightMode: mode == "light",
|
||||||
|
}
|
||||||
|
variantColors := dank16.GenerateVariantPalette(variantOpts)
|
||||||
|
return dank16.GenerateVariantJSON(variantColors)
|
||||||
|
}
|
||||||
|
|
||||||
|
func refreshGTK(configDir, mode string) {
|
||||||
|
gtkCSS := filepath.Join(configDir, "gtk-3.0", "gtk.css")
|
||||||
|
|
||||||
|
info, err := os.Lstat(gtkCSS)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldRun := false
|
||||||
|
if info.Mode()&os.ModeSymlink != 0 {
|
||||||
|
target, err := os.Readlink(gtkCSS)
|
||||||
|
if err == nil && strings.Contains(target, "dank-colors.css") {
|
||||||
|
shouldRun = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
data, err := os.ReadFile(gtkCSS)
|
||||||
|
if err == nil && strings.Contains(string(data), "dank-colors.css") {
|
||||||
|
shouldRun = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !shouldRun {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exec.Command("gsettings", "set", "org.gnome.desktop.interface", "gtk-theme", "").Run()
|
||||||
|
exec.Command("gsettings", "set", "org.gnome.desktop.interface", "gtk-theme", "adw-gtk3-"+mode).Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
func signalTerminals() {
|
||||||
|
signalByName("kitty", syscall.SIGUSR1)
|
||||||
|
signalByName("ghostty", syscall.SIGUSR2)
|
||||||
|
signalByName(".kitty-wrapped", syscall.SIGUSR1)
|
||||||
|
signalByName(".ghostty-wrappe", syscall.SIGUSR2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func signalByName(name string, sig syscall.Signal) {
|
||||||
|
entries, err := os.ReadDir("/proc")
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, entry := range entries {
|
||||||
|
pid, err := strconv.Atoi(entry.Name())
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
comm, err := os.ReadFile(filepath.Join("/proc", entry.Name(), "comm"))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(comm)) == name {
|
||||||
|
syscall.Kill(pid, sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func syncColorScheme(mode string) {
|
||||||
|
scheme := "prefer-dark"
|
||||||
|
if mode == "light" {
|
||||||
|
scheme = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := exec.Command("gsettings", "set", "org.gnome.desktop.interface", "color-scheme", scheme).Run(); err != nil {
|
||||||
|
exec.Command("dconf", "write", "/org/gnome/desktop/interface/color-scheme", "'"+scheme+"'").Run()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package matugen
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
Success bool
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
|
||||||
|
type QueuedJob struct {
|
||||||
|
Options Options
|
||||||
|
Done chan Result
|
||||||
|
Ctx context.Context
|
||||||
|
Cancel context.CancelFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queue struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
current *QueuedJob
|
||||||
|
pending *QueuedJob
|
||||||
|
jobDone chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var globalQueue *Queue
|
||||||
|
var queueOnce sync.Once
|
||||||
|
|
||||||
|
func GetQueue() *Queue {
|
||||||
|
queueOnce.Do(func() {
|
||||||
|
globalQueue = &Queue{
|
||||||
|
jobDone: make(chan struct{}, 1),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return globalQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) Submit(opts Options) <-chan Result {
|
||||||
|
result := make(chan Result, 1)
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
job := &QueuedJob{
|
||||||
|
Options: opts,
|
||||||
|
Done: result,
|
||||||
|
Ctx: ctx,
|
||||||
|
Cancel: cancel,
|
||||||
|
}
|
||||||
|
|
||||||
|
q.mu.Lock()
|
||||||
|
|
||||||
|
if q.pending != nil {
|
||||||
|
log.Info("Cancelling pending theme request")
|
||||||
|
q.pending.Cancel()
|
||||||
|
q.pending.Done <- Result{Success: false, Error: context.Canceled}
|
||||||
|
close(q.pending.Done)
|
||||||
|
}
|
||||||
|
|
||||||
|
if q.current != nil {
|
||||||
|
q.pending = job
|
||||||
|
q.mu.Unlock()
|
||||||
|
log.Info("Theme request queued (worker running)")
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
q.current = job
|
||||||
|
q.mu.Unlock()
|
||||||
|
|
||||||
|
go q.runWorker()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) runWorker() {
|
||||||
|
for {
|
||||||
|
q.mu.Lock()
|
||||||
|
job := q.current
|
||||||
|
if job == nil {
|
||||||
|
q.mu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
q.mu.Unlock()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-job.Ctx.Done():
|
||||||
|
q.finishJob(Result{Success: false, Error: context.Canceled})
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Processing theme: %s %s (%s)", job.Options.Kind, job.Options.Value, job.Options.Mode)
|
||||||
|
err := Run(job.Options)
|
||||||
|
|
||||||
|
var result Result
|
||||||
|
if err != nil {
|
||||||
|
result = Result{Success: false, Error: err}
|
||||||
|
} else {
|
||||||
|
result = Result{Success: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
q.finishJob(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) finishJob(result Result) {
|
||||||
|
q.mu.Lock()
|
||||||
|
defer q.mu.Unlock()
|
||||||
|
|
||||||
|
if q.current != nil {
|
||||||
|
select {
|
||||||
|
case q.current.Done <- result:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
close(q.current.Done)
|
||||||
|
}
|
||||||
|
|
||||||
|
q.current = q.pending
|
||||||
|
q.pending = nil
|
||||||
|
|
||||||
|
if q.current == nil {
|
||||||
|
select {
|
||||||
|
case q.jobDone <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) IsRunning() bool {
|
||||||
|
q.mu.Lock()
|
||||||
|
defer q.mu.Unlock()
|
||||||
|
return q.current != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queue) HasPending() bool {
|
||||||
|
q.mu.Lock()
|
||||||
|
defer q.mu.Unlock()
|
||||||
|
return q.pending != nil
|
||||||
|
}
|
||||||
@@ -5,6 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/dwl_ipc"
|
||||||
|
wlhelpers "github.com/AvengeMedia/DankMaterialShell/core/internal/wayland/client"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Compositor int
|
type Compositor int
|
||||||
@@ -44,10 +48,42 @@ func DetectCompositor() Compositor {
|
|||||||
return detectedCompositor
|
return detectedCompositor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if detectDWLProtocol() {
|
||||||
|
detectedCompositor = CompositorDWL
|
||||||
|
return detectedCompositor
|
||||||
|
}
|
||||||
|
|
||||||
detectedCompositor = CompositorUnknown
|
detectedCompositor = CompositorUnknown
|
||||||
return detectedCompositor
|
return detectedCompositor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func detectDWLProtocol() bool {
|
||||||
|
display, err := client.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ctx := display.Context()
|
||||||
|
defer ctx.Close()
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
found := false
|
||||||
|
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
|
||||||
|
if e.Interface == dwl_ipc.ZdwlIpcManagerV2InterfaceName {
|
||||||
|
found = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
func SetCompositorDWL() {
|
func SetCompositorDWL() {
|
||||||
detectedCompositor = CompositorDWL
|
detectedCompositor = CompositorDWL
|
||||||
}
|
}
|
||||||
@@ -57,14 +93,18 @@ type WindowGeometry struct {
|
|||||||
Y int32
|
Y int32
|
||||||
Width int32
|
Width int32
|
||||||
Height int32
|
Height int32
|
||||||
|
Output string
|
||||||
|
Scale float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetActiveWindow() (*WindowGeometry, error) {
|
func GetActiveWindow() (*WindowGeometry, error) {
|
||||||
switch DetectCompositor() {
|
switch DetectCompositor() {
|
||||||
case CompositorHyprland:
|
case CompositorHyprland:
|
||||||
return getHyprlandActiveWindow()
|
return getHyprlandActiveWindow()
|
||||||
|
case CompositorDWL:
|
||||||
|
return getDWLActiveWindow()
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("window capture requires Hyprland")
|
return nil, fmt.Errorf("window capture requires Hyprland or DWL")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,7 +260,112 @@ func SetDWLActiveOutput(name string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getDWLFocusedMonitor() string {
|
func getDWLFocusedMonitor() string {
|
||||||
return dwlActiveOutput
|
if dwlActiveOutput != "" {
|
||||||
|
return dwlActiveOutput
|
||||||
|
}
|
||||||
|
return queryDWLActiveOutput()
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryDWLActiveOutput() string {
|
||||||
|
display, err := client.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
ctx := display.Context()
|
||||||
|
defer ctx.Close()
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var dwlManager *dwl_ipc.ZdwlIpcManagerV2
|
||||||
|
outputs := make(map[uint32]*client.Output)
|
||||||
|
|
||||||
|
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
|
||||||
|
switch e.Interface {
|
||||||
|
case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
|
||||||
|
mgr := dwl_ipc.NewZdwlIpcManagerV2(ctx)
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil {
|
||||||
|
dwlManager = mgr
|
||||||
|
}
|
||||||
|
case client.OutputInterfaceName:
|
||||||
|
out := client.NewOutput(ctx)
|
||||||
|
version := e.Version
|
||||||
|
if version > 4 {
|
||||||
|
version = 4
|
||||||
|
}
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, version, out); err == nil {
|
||||||
|
outputs[e.Name] = out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if dwlManager == nil || len(outputs) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
outputNames := make(map[uint32]string)
|
||||||
|
for name, out := range outputs {
|
||||||
|
n := name
|
||||||
|
out.SetNameHandler(func(e client.OutputNameEvent) {
|
||||||
|
outputNames[n] = e.Name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type outputState struct {
|
||||||
|
name string
|
||||||
|
active bool
|
||||||
|
gotFrame bool
|
||||||
|
}
|
||||||
|
states := make(map[uint32]*outputState)
|
||||||
|
|
||||||
|
for name, out := range outputs {
|
||||||
|
dwlOut, err := dwlManager.GetOutput(out)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state := &outputState{name: outputNames[name]}
|
||||||
|
states[name] = state
|
||||||
|
|
||||||
|
dwlOut.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
|
||||||
|
state.active = e.Active != 0
|
||||||
|
})
|
||||||
|
dwlOut.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
|
||||||
|
state.gotFrame = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
allFramesReceived := func() bool {
|
||||||
|
for _, s := range states {
|
||||||
|
if !s.gotFrame {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for !allFramesReceived() {
|
||||||
|
if err := ctx.Dispatch(); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, state := range states {
|
||||||
|
if state.active {
|
||||||
|
return state.name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFocusedMonitor() string {
|
func GetFocusedMonitor() string {
|
||||||
@@ -236,3 +381,143 @@ func GetFocusedMonitor() string {
|
|||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDWLActiveWindow() (*WindowGeometry, error) {
|
||||||
|
display, err := client.Connect("")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("connect: %w", err)
|
||||||
|
}
|
||||||
|
ctx := display.Context()
|
||||||
|
defer ctx.Close()
|
||||||
|
|
||||||
|
registry, err := display.GetRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("get registry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dwlManager *dwl_ipc.ZdwlIpcManagerV2
|
||||||
|
outputs := make(map[uint32]*client.Output)
|
||||||
|
|
||||||
|
registry.SetGlobalHandler(func(e client.RegistryGlobalEvent) {
|
||||||
|
switch e.Interface {
|
||||||
|
case dwl_ipc.ZdwlIpcManagerV2InterfaceName:
|
||||||
|
mgr := dwl_ipc.NewZdwlIpcManagerV2(ctx)
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, e.Version, mgr); err == nil {
|
||||||
|
dwlManager = mgr
|
||||||
|
}
|
||||||
|
case client.OutputInterfaceName:
|
||||||
|
out := client.NewOutput(ctx)
|
||||||
|
version := e.Version
|
||||||
|
if version > 4 {
|
||||||
|
version = 4
|
||||||
|
}
|
||||||
|
if err := registry.Bind(e.Name, e.Interface, version, out); err == nil {
|
||||||
|
outputs[e.Name] = out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
|
return nil, fmt.Errorf("roundtrip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dwlManager == nil {
|
||||||
|
return nil, fmt.Errorf("dwl_ipc_manager not available")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(outputs) == 0 {
|
||||||
|
return nil, fmt.Errorf("no outputs found")
|
||||||
|
}
|
||||||
|
|
||||||
|
outputNames := make(map[uint32]string)
|
||||||
|
for name, out := range outputs {
|
||||||
|
n := name
|
||||||
|
out.SetNameHandler(func(e client.OutputNameEvent) {
|
||||||
|
outputNames[n] = e.Name
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := wlhelpers.Roundtrip(display, ctx); err != nil {
|
||||||
|
return nil, fmt.Errorf("roundtrip: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type dwlOutputState struct {
|
||||||
|
output *dwl_ipc.ZdwlIpcOutputV2
|
||||||
|
name string
|
||||||
|
active bool
|
||||||
|
x, y int32
|
||||||
|
w, h int32
|
||||||
|
scalefactor uint32
|
||||||
|
gotFrame bool
|
||||||
|
}
|
||||||
|
|
||||||
|
dwlOutputs := make(map[uint32]*dwlOutputState)
|
||||||
|
for name, out := range outputs {
|
||||||
|
dwlOut, err := dwlManager.GetOutput(out)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
state := &dwlOutputState{output: dwlOut, name: outputNames[name]}
|
||||||
|
dwlOutputs[name] = state
|
||||||
|
|
||||||
|
dwlOut.SetActiveHandler(func(e dwl_ipc.ZdwlIpcOutputV2ActiveEvent) {
|
||||||
|
state.active = e.Active != 0
|
||||||
|
})
|
||||||
|
dwlOut.SetXHandler(func(e dwl_ipc.ZdwlIpcOutputV2XEvent) {
|
||||||
|
state.x = e.X
|
||||||
|
})
|
||||||
|
dwlOut.SetYHandler(func(e dwl_ipc.ZdwlIpcOutputV2YEvent) {
|
||||||
|
state.y = e.Y
|
||||||
|
})
|
||||||
|
dwlOut.SetWidthHandler(func(e dwl_ipc.ZdwlIpcOutputV2WidthEvent) {
|
||||||
|
state.w = e.Width
|
||||||
|
})
|
||||||
|
dwlOut.SetHeightHandler(func(e dwl_ipc.ZdwlIpcOutputV2HeightEvent) {
|
||||||
|
state.h = e.Height
|
||||||
|
})
|
||||||
|
dwlOut.SetScalefactorHandler(func(e dwl_ipc.ZdwlIpcOutputV2ScalefactorEvent) {
|
||||||
|
state.scalefactor = e.Scalefactor
|
||||||
|
})
|
||||||
|
dwlOut.SetFrameHandler(func(e dwl_ipc.ZdwlIpcOutputV2FrameEvent) {
|
||||||
|
state.gotFrame = true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
allFramesReceived := func() bool {
|
||||||
|
for _, s := range dwlOutputs {
|
||||||
|
if !s.gotFrame {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
for !allFramesReceived() {
|
||||||
|
if err := ctx.Dispatch(); err != nil {
|
||||||
|
return nil, fmt.Errorf("dispatch: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, state := range dwlOutputs {
|
||||||
|
if !state.active {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if state.w <= 0 || state.h <= 0 {
|
||||||
|
return nil, fmt.Errorf("no active window")
|
||||||
|
}
|
||||||
|
scale := float64(state.scalefactor) / 100.0
|
||||||
|
if scale <= 0 {
|
||||||
|
scale = 1.0
|
||||||
|
}
|
||||||
|
return &WindowGeometry{
|
||||||
|
X: state.x,
|
||||||
|
Y: state.y,
|
||||||
|
Width: state.w,
|
||||||
|
Height: state.h,
|
||||||
|
Output: state.name,
|
||||||
|
Scale: scale,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("no active output found")
|
||||||
|
}
|
||||||
|
|||||||
@@ -135,16 +135,138 @@ func (s *Screenshoter) captureWindow() (*CaptureResult, error) {
|
|||||||
Height: geom.Height,
|
Height: geom.Height,
|
||||||
}
|
}
|
||||||
|
|
||||||
output := s.findOutputForRegion(region)
|
var output *WaylandOutput
|
||||||
|
if geom.Output != "" {
|
||||||
|
output = s.findOutputByName(geom.Output)
|
||||||
|
}
|
||||||
|
if output == nil {
|
||||||
|
output = s.findOutputForRegion(region)
|
||||||
|
}
|
||||||
if output == nil {
|
if output == nil {
|
||||||
return nil, fmt.Errorf("could not find output for window")
|
return nil, fmt.Errorf("could not find output for window")
|
||||||
}
|
}
|
||||||
|
|
||||||
if DetectCompositor() == CompositorHyprland {
|
switch DetectCompositor() {
|
||||||
|
case CompositorHyprland:
|
||||||
return s.captureAndCrop(output, region)
|
return s.captureAndCrop(output, region)
|
||||||
|
case CompositorDWL:
|
||||||
|
return s.captureDWLWindow(output, region, geom.Scale)
|
||||||
|
default:
|
||||||
|
return s.captureRegionOnOutput(output, region)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Screenshoter) captureDWLWindow(output *WaylandOutput, region Region, dwlScale float64) (*CaptureResult, error) {
|
||||||
|
result, err := s.captureWholeOutput(output)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.captureRegionOnOutput(output, region)
|
scale := dwlScale
|
||||||
|
if scale <= 0 {
|
||||||
|
scale = float64(result.Buffer.Width) / float64(output.width)
|
||||||
|
}
|
||||||
|
if scale <= 0 {
|
||||||
|
scale = 1.0
|
||||||
|
}
|
||||||
|
|
||||||
|
localX := int(float64(region.X) * scale)
|
||||||
|
localY := int(float64(region.Y) * scale)
|
||||||
|
if localX >= result.Buffer.Width {
|
||||||
|
localX = localX % result.Buffer.Width
|
||||||
|
}
|
||||||
|
if localY >= result.Buffer.Height {
|
||||||
|
localY = localY % result.Buffer.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
w := int(float64(region.Width) * scale)
|
||||||
|
h := int(float64(region.Height) * scale)
|
||||||
|
|
||||||
|
if localY+h > result.Buffer.Height && h <= result.Buffer.Height {
|
||||||
|
localY = result.Buffer.Height - h
|
||||||
|
if localY < 0 {
|
||||||
|
localY = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if localX+w > result.Buffer.Width && w <= result.Buffer.Width {
|
||||||
|
localX = result.Buffer.Width - w
|
||||||
|
if localX < 0 {
|
||||||
|
localX = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if localX < 0 {
|
||||||
|
w += localX
|
||||||
|
localX = 0
|
||||||
|
}
|
||||||
|
if localY < 0 {
|
||||||
|
h += localY
|
||||||
|
localY = 0
|
||||||
|
}
|
||||||
|
if localX+w > result.Buffer.Width {
|
||||||
|
w = result.Buffer.Width - localX
|
||||||
|
}
|
||||||
|
if localY+h > result.Buffer.Height {
|
||||||
|
h = result.Buffer.Height - localY
|
||||||
|
}
|
||||||
|
|
||||||
|
if w <= 0 || h <= 0 {
|
||||||
|
result.Buffer.Close()
|
||||||
|
return nil, fmt.Errorf("window not visible on output")
|
||||||
|
}
|
||||||
|
|
||||||
|
cropped, err := CreateShmBuffer(w, h, w*4)
|
||||||
|
if err != nil {
|
||||||
|
result.Buffer.Close()
|
||||||
|
return nil, fmt.Errorf("create crop buffer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
srcData := result.Buffer.Data()
|
||||||
|
dstData := cropped.Data()
|
||||||
|
|
||||||
|
for y := 0; y < h; y++ {
|
||||||
|
srcY := localY + y
|
||||||
|
if result.YInverted {
|
||||||
|
srcY = result.Buffer.Height - 1 - (localY + y)
|
||||||
|
}
|
||||||
|
if srcY < 0 || srcY >= result.Buffer.Height {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dstY := y
|
||||||
|
if result.YInverted {
|
||||||
|
dstY = h - 1 - y
|
||||||
|
}
|
||||||
|
|
||||||
|
for x := 0; x < w; x++ {
|
||||||
|
srcX := localX + x
|
||||||
|
if srcX < 0 || srcX >= result.Buffer.Width {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
si := srcY*result.Buffer.Stride + srcX*4
|
||||||
|
di := dstY*cropped.Stride + x*4
|
||||||
|
|
||||||
|
if si+3 >= len(srcData) || di+3 >= len(dstData) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
dstData[di+0] = srcData[si+0]
|
||||||
|
dstData[di+1] = srcData[si+1]
|
||||||
|
dstData[di+2] = srcData[si+2]
|
||||||
|
dstData[di+3] = srcData[si+3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Buffer.Close()
|
||||||
|
cropped.Format = PixelFormat(result.Format)
|
||||||
|
|
||||||
|
return &CaptureResult{
|
||||||
|
Buffer: cropped,
|
||||||
|
Region: region,
|
||||||
|
YInverted: false,
|
||||||
|
Format: result.Format,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) captureFullScreen() (*CaptureResult, error) {
|
func (s *Screenshoter) captureFullScreen() (*CaptureResult, error) {
|
||||||
@@ -457,6 +579,31 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio
|
|||||||
w := int32(float64(region.Width) * scale)
|
w := int32(float64(region.Width) * scale)
|
||||||
h := int32(float64(region.Height) * scale)
|
h := int32(float64(region.Height) * scale)
|
||||||
|
|
||||||
|
if DetectCompositor() == CompositorDWL {
|
||||||
|
scaledOutW := int32(float64(output.width) * scale)
|
||||||
|
scaledOutH := int32(float64(output.height) * scale)
|
||||||
|
if localX >= scaledOutW {
|
||||||
|
localX = localX % scaledOutW
|
||||||
|
}
|
||||||
|
if localY >= scaledOutH {
|
||||||
|
localY = localY % scaledOutH
|
||||||
|
}
|
||||||
|
if localX+w > scaledOutW {
|
||||||
|
w = scaledOutW - localX
|
||||||
|
}
|
||||||
|
if localY+h > scaledOutH {
|
||||||
|
h = scaledOutH - localY
|
||||||
|
}
|
||||||
|
if localX < 0 {
|
||||||
|
w += localX
|
||||||
|
localX = 0
|
||||||
|
}
|
||||||
|
if localY < 0 {
|
||||||
|
h += localY
|
||||||
|
localY = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cursor := int32(0)
|
cursor := int32(0)
|
||||||
if s.config.IncludeCursor {
|
if s.config.IncludeCursor {
|
||||||
cursor = 1
|
cursor = 1
|
||||||
@@ -557,6 +704,17 @@ func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Screenshoter) findOutputByName(name string) *WaylandOutput {
|
||||||
|
s.outputsMu.Lock()
|
||||||
|
defer s.outputsMu.Unlock()
|
||||||
|
for _, o := range s.outputs {
|
||||||
|
if o.name == name {
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Screenshoter) findOutputForRegion(region Region) *WaylandOutput {
|
func (s *Screenshoter) findOutputForRegion(region Region) *WaylandOutput {
|
||||||
s.outputsMu.Lock()
|
s.outputsMu.Lock()
|
||||||
defer s.outputsMu.Unlock()
|
defer s.outputsMu.Unlock()
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/matugen"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/server/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MatugenQueueResult struct {
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMatugenQueue(conn net.Conn, req models.Request) {
|
||||||
|
getString := func(key string) string {
|
||||||
|
if v, ok := req.Params[key].(string); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
getBool := func(key string, def bool) bool {
|
||||||
|
if v, ok := req.Params[key].(bool); ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := matugen.Options{
|
||||||
|
StateDir: getString("stateDir"),
|
||||||
|
ShellDir: getString("shellDir"),
|
||||||
|
ConfigDir: getString("configDir"),
|
||||||
|
Kind: getString("kind"),
|
||||||
|
Value: getString("value"),
|
||||||
|
Mode: getString("mode"),
|
||||||
|
IconTheme: getString("iconTheme"),
|
||||||
|
MatugenType: getString("matugenType"),
|
||||||
|
RunUserTemplates: getBool("runUserTemplates", true),
|
||||||
|
StockColors: getString("stockColors"),
|
||||||
|
SyncModeWithPortal: getBool("syncModeWithPortal", false),
|
||||||
|
TerminalsAlwaysDark: getBool("terminalsAlwaysDark", false),
|
||||||
|
}
|
||||||
|
|
||||||
|
wait := getBool("wait", true)
|
||||||
|
|
||||||
|
queue := matugen.GetQueue()
|
||||||
|
resultCh := queue.Submit(opts)
|
||||||
|
|
||||||
|
if !wait {
|
||||||
|
models.Respond(conn, req.ID, MatugenQueueResult{
|
||||||
|
Success: true,
|
||||||
|
Message: "queued",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case result := <-resultCh:
|
||||||
|
if result.Error != nil {
|
||||||
|
if result.Error == context.Canceled {
|
||||||
|
models.Respond(conn, req.ID, MatugenQueueResult{
|
||||||
|
Success: false,
|
||||||
|
Message: "cancelled",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.RespondError(conn, req.ID, result.Error.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
models.Respond(conn, req.ID, MatugenQueueResult{
|
||||||
|
Success: true,
|
||||||
|
Message: "completed",
|
||||||
|
})
|
||||||
|
case <-ctx.Done():
|
||||||
|
models.RespondError(conn, req.ID, "timeout waiting for theme generation")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleMatugenStatus(conn net.Conn, req models.Request) {
|
||||||
|
queue := matugen.GetQueue()
|
||||||
|
models.Respond(conn, req.ID, map[string]bool{
|
||||||
|
"running": queue.IsRunning(),
|
||||||
|
"pending": queue.HasPending(),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -215,6 +215,10 @@ func RouteRequest(conn net.Conn, req models.Request) {
|
|||||||
models.Respond(conn, req.ID, info)
|
models.Respond(conn, req.ID, info)
|
||||||
case "subscribe":
|
case "subscribe":
|
||||||
handleSubscribe(conn, req)
|
handleSubscribe(conn, req)
|
||||||
|
case "matugen.queue":
|
||||||
|
handleMatugenQueue(conn, req)
|
||||||
|
case "matugen.status":
|
||||||
|
handleMatugenStatus(conn, req)
|
||||||
default:
|
default:
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
+2
-2
@@ -4,13 +4,13 @@
|
|||||||
lib,
|
lib,
|
||||||
dmsPkgs,
|
dmsPkgs,
|
||||||
...
|
...
|
||||||
}: let
|
} @ args: let
|
||||||
cfg = config.programs.dankMaterialShell;
|
cfg = config.programs.dankMaterialShell;
|
||||||
jsonFormat = pkgs.formats.json {};
|
jsonFormat = pkgs.formats.json {};
|
||||||
common = import ./common.nix {inherit config pkgs lib dmsPkgs;};
|
common = import ./common.nix {inherit config pkgs lib dmsPkgs;};
|
||||||
in {
|
in {
|
||||||
imports = [
|
imports = [
|
||||||
./options.nix
|
(import ./options.nix args)
|
||||||
(lib.mkRemovedOptionModule ["programs" "dankMaterialShell" "enableNightMode"] "Night mode is now always available.")
|
(lib.mkRemovedOptionModule ["programs" "dankMaterialShell" "enableNightMode"] "Night mode is now always available.")
|
||||||
(lib.mkRenamedOptionModule ["programs" "dankMaterialShell" "enableSystemd"] ["programs" "dankMaterialShell" "systemd" "enable"])
|
(lib.mkRenamedOptionModule ["programs" "dankMaterialShell" "enableSystemd"] ["programs" "dankMaterialShell" "systemd" "enable"])
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,12 +4,12 @@
|
|||||||
lib,
|
lib,
|
||||||
dmsPkgs,
|
dmsPkgs,
|
||||||
...
|
...
|
||||||
}: let
|
} @ args: let
|
||||||
cfg = config.programs.dankMaterialShell;
|
cfg = config.programs.dankMaterialShell;
|
||||||
common = import ./common.nix {inherit config pkgs lib dmsPkgs;};
|
common = import ./common.nix {inherit config pkgs lib dmsPkgs;};
|
||||||
in {
|
in {
|
||||||
imports = [
|
imports = [
|
||||||
./options.nix
|
(import ./options.nix args)
|
||||||
];
|
];
|
||||||
|
|
||||||
config = lib.mkIf cfg.enable
|
config = lib.mkIf cfg.enable
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
pkgs,
|
|
||||||
lib,
|
lib,
|
||||||
|
dmsPkgs,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
inherit (lib) types;
|
inherit (lib) types;
|
||||||
@@ -62,7 +62,9 @@ in {
|
|||||||
description = "Add needed dependencies to have system sound support";
|
description = "Add needed dependencies to have system sound support";
|
||||||
};
|
};
|
||||||
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).";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+29
-7
@@ -7,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1762435535,
|
"lastModified": 1762835999,
|
||||||
"narHash": "sha256-QhzRn7pYN35IFpKjjxJAj3GPJECuC+VLhoGem3ezycc=",
|
"narHash": "sha256-UykYGrGFOFTmDpKTLNxj1wvd1gbDG4TkqLNSbV0TYwk=",
|
||||||
"owner": "AvengeMedia",
|
"owner": "AvengeMedia",
|
||||||
"repo": "dgop",
|
"repo": "dgop",
|
||||||
"rev": "6cf638dde818f9f8a2e26d0243179c43cb3458d7",
|
"rev": "799301991cd5dcea9b64245f9d500dcc76615653",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -22,11 +22,11 @@
|
|||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1762363567,
|
"lastModified": 1764950072,
|
||||||
"narHash": "sha256-YRqMDEtSMbitIMj+JLpheSz0pwEr0Rmy5mC7myl17xs=",
|
"narHash": "sha256-BmPWzogsG2GsXZtlT+MTcAWeDK5hkbGRZTeZNW42fwA=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "ae814fd3904b621d8ab97418f1d0f2eb0d3716f4",
|
"rev": "f61125a668a320878494449750330ca58b78c557",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -36,10 +36,32 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"quickshell": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1764663772,
|
||||||
|
"narHash": "sha256-sHqLmm0wAt3PC4vczJeBozI1/f4rv9yp3IjkClHDXDs=",
|
||||||
|
"ref": "refs/heads/master",
|
||||||
|
"rev": "26531fc46ef17e9365b03770edd3fb9206fcb460",
|
||||||
|
"revCount": 713,
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.outfoxxed.me/quickshell/quickshell"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"rev": "26531fc46ef17e9365b03770edd3fb9206fcb460",
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.outfoxxed.me/quickshell/quickshell"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"dgop": "dgop",
|
"dgop": "dgop",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs",
|
||||||
|
"quickshell": "quickshell"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -7,12 +7,17 @@
|
|||||||
url = "github:AvengeMedia/dgop";
|
url = "github:AvengeMedia/dgop";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
quickshell = {
|
||||||
|
url = "git+https://git.outfoxxed.me/quickshell/quickshell?rev=26531fc46ef17e9365b03770edd3fb9206fcb460";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs = {
|
outputs = {
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
dgop,
|
dgop,
|
||||||
|
quickshell,
|
||||||
...
|
...
|
||||||
}: let
|
}: let
|
||||||
forEachSystem = fn:
|
forEachSystem = fn:
|
||||||
@@ -22,6 +27,7 @@
|
|||||||
buildDmsPkgs = pkgs: {
|
buildDmsPkgs = pkgs: {
|
||||||
dms-shell = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
dms-shell = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||||
dgop = dgop.packages.${pkgs.stdenv.hostPlatform.system}.dgop;
|
dgop = dgop.packages.${pkgs.stdenv.hostPlatform.system}.dgop;
|
||||||
|
quickshell = quickshell.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||||
};
|
};
|
||||||
mkModuleWithDmsPkgs = path: args @ {pkgs, ...}: {
|
mkModuleWithDmsPkgs = path: args @ {pkgs, ...}: {
|
||||||
imports = [
|
imports = [
|
||||||
@@ -46,67 +52,69 @@
|
|||||||
+ "_"
|
+ "_"
|
||||||
+ (self.shortRev or "dirty");
|
+ (self.shortRev or "dirty");
|
||||||
in {
|
in {
|
||||||
dms-shell = pkgs.buildGoModule (let
|
dms-shell = pkgs.buildGoModule (
|
||||||
rootSrc = ./.;
|
let
|
||||||
in {
|
rootSrc = ./.;
|
||||||
inherit version;
|
in {
|
||||||
pname = "dms-shell";
|
inherit version;
|
||||||
src = ./core;
|
pname = "dms-shell";
|
||||||
vendorHash = "sha256-2PCqiW4frxME8IlmwWH5ktznhd/G1bah5Ae4dp0HPTQ=";
|
src = ./core;
|
||||||
|
vendorHash = "sha256-2PCqiW4frxME8IlmwWH5ktznhd/G1bah5Ae4dp0HPTQ=";
|
||||||
|
|
||||||
subPackages = ["cmd/dms"];
|
subPackages = ["cmd/dms"];
|
||||||
|
|
||||||
ldflags = [
|
ldflags = [
|
||||||
"-s"
|
"-s"
|
||||||
"-w"
|
"-w"
|
||||||
"-X main.Version=${version}"
|
"-X main.Version=${version}"
|
||||||
];
|
];
|
||||||
|
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = with pkgs; [
|
||||||
pkgs.installShellFiles
|
installShellFiles
|
||||||
pkgs.makeWrapper
|
makeWrapper
|
||||||
];
|
];
|
||||||
|
|
||||||
postInstall = ''
|
postInstall = ''
|
||||||
mkdir -p $out/share/quickshell/dms
|
mkdir -p $out/share/quickshell/dms
|
||||||
cp -r ${rootSrc}/quickshell/. $out/share/quickshell/dms/
|
cp -r ${rootSrc}/quickshell/. $out/share/quickshell/dms/
|
||||||
|
|
||||||
chmod u+w $out/share/quickshell/dms/VERSION
|
chmod u+w $out/share/quickshell/dms/VERSION
|
||||||
echo "${version}" > $out/share/quickshell/dms/VERSION
|
echo "${version}" > $out/share/quickshell/dms/VERSION
|
||||||
|
|
||||||
# Install desktop file and icon
|
# Install desktop file and icon
|
||||||
install -D ${rootSrc}/assets/dms-open.desktop \
|
install -D ${rootSrc}/assets/dms-open.desktop \
|
||||||
$out/share/applications/dms-open.desktop
|
$out/share/applications/dms-open.desktop
|
||||||
install -D ${rootSrc}/core/assets/danklogo.svg \
|
install -D ${rootSrc}/core/assets/danklogo.svg \
|
||||||
$out/share/hicolor/scalable/apps/danklogo.svg
|
$out/share/hicolor/scalable/apps/danklogo.svg
|
||||||
|
|
||||||
wrapProgram $out/bin/dms --add-flags "-c $out/share/quickshell/dms"
|
wrapProgram $out/bin/dms --add-flags "-c $out/share/quickshell/dms"
|
||||||
|
|
||||||
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
|
||||||
$out/lib/systemd/user/dms.service
|
$out/lib/systemd/user/dms.service
|
||||||
|
|
||||||
substituteInPlace $out/lib/systemd/user/dms.service \
|
substituteInPlace $out/lib/systemd/user/dms.service \
|
||||||
--replace-fail /usr/bin/dms $out/bin/dms \
|
--replace-fail /usr/bin/dms $out/bin/dms \
|
||||||
--replace-fail /usr/bin/pkill ${pkgs.procps}/bin/pkill
|
--replace-fail /usr/bin/pkill ${pkgs.procps}/bin/pkill
|
||||||
|
|
||||||
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
|
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
|
||||||
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
|
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
|
||||||
|
|
||||||
installShellCompletion --cmd dms \
|
installShellCompletion --cmd dms \
|
||||||
--bash <($out/bin/dms completion bash) \
|
--bash <($out/bin/dms completion bash) \
|
||||||
--fish <($out/bin/dms completion fish) \
|
--fish <($out/bin/dms completion fish) \
|
||||||
--zsh <($out/bin/dms completion zsh)
|
--zsh <($out/bin/dms completion zsh)
|
||||||
'';
|
'';
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
description = "Desktop shell for wayland compositors built with Quickshell & GO";
|
description = "Desktop shell for wayland compositors built with Quickshell & GO";
|
||||||
homepage = "https://danklinux.com";
|
homepage = "https://danklinux.com";
|
||||||
changelog = "https://github.com/AvengeMedia/DankMaterialShell/releases/tag/v${version}";
|
changelog = "https://github.com/AvengeMedia/DankMaterialShell/releases/tag/v${version}";
|
||||||
license = pkgs.lib.licenses.mit;
|
license = pkgs.lib.licenses.mit;
|
||||||
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;
|
||||||
}
|
}
|
||||||
@@ -119,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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,14 +429,24 @@ 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) {
|
||||||
|
args.value = argParts.join(" ");
|
||||||
}
|
}
|
||||||
} else if (argParts.length > 0) {
|
|
||||||
args.value = argParts.join(" ");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { base: base, args: args };
|
return { base: base, args: args };
|
||||||
@@ -437,24 +461,29 @@ 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) {
|
||||||
|
parts.push(args.value);
|
||||||
|
} else if (args.index) {
|
||||||
|
parts.push(args.index);
|
||||||
}
|
}
|
||||||
} else if (args.value) {
|
|
||||||
parts.push(args.value);
|
|
||||||
} else if (args.index) {
|
|
||||||
parts.push(args.index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts.join(" ");
|
return parts.join(" ");
|
||||||
|
|||||||
@@ -295,7 +295,7 @@ Singleton {
|
|||||||
|
|
||||||
property bool lockScreenShowPowerActions: true
|
property bool lockScreenShowPowerActions: true
|
||||||
property bool enableFprint: false
|
property bool enableFprint: false
|
||||||
property int maxFprintTries: 3
|
property int maxFprintTries: 15
|
||||||
property bool fprintdAvailable: false
|
property bool fprintdAvailable: false
|
||||||
property string lockScreenActiveMonitor: "all"
|
property string lockScreenActiveMonitor: "all"
|
||||||
property string lockScreenInactiveColor: "#000000"
|
property string lockScreenInactiveColor: "#000000"
|
||||||
|
|||||||
+27
-10
@@ -820,18 +820,35 @@ Singleton {
|
|||||||
"runUserTemplates": (typeof SettingsData !== "undefined") ? SettingsData.runUserMatugenTemplates : true
|
"runUserTemplates": (typeof SettingsData !== "undefined") ? SettingsData.runUserMatugenTemplates : true
|
||||||
};
|
};
|
||||||
|
|
||||||
if (stockColors) {
|
|
||||||
desired.stockColors = JSON.stringify(stockColors);
|
|
||||||
}
|
|
||||||
|
|
||||||
const json = JSON.stringify(desired);
|
|
||||||
const desiredPath = stateDir + "/matugen.desired.json";
|
|
||||||
const syncModeWithPortal = (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal) ? "true" : "false";
|
|
||||||
const terminalsAlwaysDark = (typeof SettingsData !== "undefined" && SettingsData.terminalsAlwaysDark) ? "true" : "false";
|
|
||||||
|
|
||||||
console.log("Theme: Starting matugen worker");
|
console.log("Theme: Starting matugen worker");
|
||||||
workerRunning = true;
|
workerRunning = true;
|
||||||
systemThemeGenerator.command = ["sh", "-c", `mkdir -p '${stateDir}' && cat > '${desiredPath}' << 'EOF'\n${json}\nEOF\nexec '${shellDir}/scripts/matugen-worker.sh' '${stateDir}' '${shellDir}' '${configDir}' '${syncModeWithPortal}' '${terminalsAlwaysDark}' --run`];
|
|
||||||
|
const args = [
|
||||||
|
"dms", "matugen", "queue",
|
||||||
|
"--state-dir", stateDir,
|
||||||
|
"--shell-dir", shellDir,
|
||||||
|
"--config-dir", configDir,
|
||||||
|
"--kind", desired.kind,
|
||||||
|
"--value", desired.value,
|
||||||
|
"--mode", desired.mode,
|
||||||
|
"--icon-theme", desired.iconTheme,
|
||||||
|
"--matugen-type", desired.matugenType,
|
||||||
|
];
|
||||||
|
|
||||||
|
if (!desired.runUserTemplates) {
|
||||||
|
args.push("--run-user-templates=false");
|
||||||
|
}
|
||||||
|
if (stockColors) {
|
||||||
|
args.push("--stock-colors", JSON.stringify(stockColors));
|
||||||
|
}
|
||||||
|
if (typeof SettingsData !== "undefined" && SettingsData.syncModeWithPortal) {
|
||||||
|
args.push("--sync-mode-with-portal");
|
||||||
|
}
|
||||||
|
if (typeof SettingsData !== "undefined" && SettingsData.terminalsAlwaysDark) {
|
||||||
|
args.push("--terminals-always-dark");
|
||||||
|
}
|
||||||
|
|
||||||
|
systemThemeGenerator.command = args;
|
||||||
systemThemeGenerator.running = true;
|
systemThemeGenerator.running = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ var SPEC = {
|
|||||||
|
|
||||||
lockScreenShowPowerActions: { def: true },
|
lockScreenShowPowerActions: { def: true },
|
||||||
enableFprint: { def: false },
|
enableFprint: { def: false },
|
||||||
maxFprintTries: { def: 3 },
|
maxFprintTries: { def: 15 },
|
||||||
fprintdAvailable: { def: false, persist: false },
|
fprintdAvailable: { def: false, persist: false },
|
||||||
lockScreenActiveMonitor: { def: "all" },
|
lockScreenActiveMonitor: { def: "all" },
|
||||||
lockScreenInactiveColor: { def: "#000000" },
|
lockScreenInactiveColor: { def: "#000000" },
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ Row {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DankSlider {
|
DankSlider {
|
||||||
|
id: volumeSlider
|
||||||
|
|
||||||
readonly property real actualVolumePercent: defaultSink ? Math.round(defaultSink.audio.volume * 100) : 0
|
readonly property real actualVolumePercent: defaultSink ? Math.round(defaultSink.audio.volume * 100) : 0
|
||||||
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
@@ -63,7 +65,6 @@ Row {
|
|||||||
enabled: defaultSink !== null
|
enabled: defaultSink !== null
|
||||||
minimum: 0
|
minimum: 0
|
||||||
maximum: 100
|
maximum: 100
|
||||||
value: defaultSink ? Math.min(100, Math.round(defaultSink.audio.volume * 100)) : 0
|
|
||||||
showValue: true
|
showValue: true
|
||||||
unit: "%"
|
unit: "%"
|
||||||
valueOverride: actualVolumePercent
|
valueOverride: actualVolumePercent
|
||||||
@@ -81,4 +82,11 @@ Row {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binding {
|
||||||
|
target: volumeSlider
|
||||||
|
property: "value"
|
||||||
|
value: defaultSink ? Math.min(100, Math.round(defaultSink.audio.volume * 100)) : 0
|
||||||
|
when: !volumeSlider.isDragging
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
name: "content_paste"
|
name: "content_paste"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: Theme.widgetIconColor
|
color: Theme.widgetIconColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: root.getNetworkIconName()
|
name: root.getNetworkIconName()
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: root.getNetworkIconColor()
|
color: root.getNetworkIconColor()
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||||
@@ -181,7 +181,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "vpn_lock"
|
name: "vpn_lock"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: NetworkService.vpnConnected ? Theme.primary : Theme.outlineButton
|
color: NetworkService.vpnConnected ? Theme.primary : Theme.outlineButton
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
||||||
@@ -189,7 +189,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "bluetooth"
|
name: "bluetooth"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
|
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||||
@@ -205,7 +205,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: audioIconV
|
id: audioIconV
|
||||||
name: root.getVolumeIconName()
|
name: root.getVolumeIconName()
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: Theme.widgetIconColor
|
color: Theme.widgetIconColor
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -230,7 +230,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: micIconV
|
id: micIconV
|
||||||
name: root.getMicIconName()
|
name: root.getMicIconName()
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: root.getMicIconColor()
|
color: root.getMicIconColor()
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: brightnessIconV
|
id: brightnessIconV
|
||||||
name: root.getBrightnessIconName()
|
name: root.getBrightnessIconName()
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: Theme.widgetIconColor
|
color: Theme.widgetIconColor
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -272,7 +272,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: root.getBatteryIconColor()
|
color: root.getBatteryIconColor()
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
||||||
@@ -280,7 +280,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "print"
|
name: "print"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
||||||
@@ -288,7 +288,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "settings"
|
name: "settings"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
visible: root.hasNoVisibleIcons()
|
visible: root.hasNoVisibleIcons()
|
||||||
@@ -304,7 +304,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: networkIcon
|
id: networkIcon
|
||||||
name: root.getNetworkIconName()
|
name: root.getNetworkIconName()
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: root.getNetworkIconColor()
|
color: root.getNetworkIconColor()
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
visible: root.showNetworkIcon && NetworkService.networkAvailable
|
||||||
@@ -313,7 +313,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: vpnIcon
|
id: vpnIcon
|
||||||
name: "vpn_lock"
|
name: "vpn_lock"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: NetworkService.vpnConnected ? Theme.primary : Theme.outlineButton
|
color: NetworkService.vpnConnected ? Theme.primary : Theme.outlineButton
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
|
||||||
@@ -322,7 +322,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: bluetoothIcon
|
id: bluetoothIcon
|
||||||
name: "bluetooth"
|
name: "bluetooth"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
|
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
|
||||||
@@ -338,7 +338,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: audioIcon
|
id: audioIcon
|
||||||
name: root.getVolumeIconName()
|
name: root.getVolumeIconName()
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: Theme.widgetIconColor
|
color: Theme.widgetIconColor
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -364,7 +364,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: micIcon
|
id: micIcon
|
||||||
name: root.getMicIconName()
|
name: root.getMicIconName()
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: root.getMicIconColor()
|
color: root.getMicIconColor()
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -390,7 +390,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: brightnessIcon
|
id: brightnessIcon
|
||||||
name: root.getBrightnessIconName()
|
name: root.getBrightnessIconName()
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: Theme.widgetIconColor
|
color: Theme.widgetIconColor
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
}
|
}
|
||||||
@@ -409,7 +409,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: batteryIcon
|
id: batteryIcon
|
||||||
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: root.getBatteryIconColor()
|
color: root.getBatteryIconColor()
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
visible: root.showBatteryIcon && BatteryService.batteryAvailable
|
||||||
@@ -418,7 +418,7 @@ BasePill {
|
|||||||
DankIcon {
|
DankIcon {
|
||||||
id: printerIcon
|
id: printerIcon
|
||||||
name: "print"
|
name: "print"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: Theme.primary
|
color: Theme.primary
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
|
||||||
@@ -426,7 +426,7 @@ BasePill {
|
|||||||
|
|
||||||
DankIcon {
|
DankIcon {
|
||||||
name: "settings"
|
name: "settings"
|
||||||
size: Theme.barIconSize(root.barThickness)
|
size: Theme.barIconSize(root.barThickness, -4)
|
||||||
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
color: root.isActive ? Theme.primary : Theme.widgetIconColor
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
visible: root.hasNoVisibleIcons()
|
visible: root.hasNoVisibleIcons()
|
||||||
|
|||||||
@@ -59,12 +59,6 @@ Item {
|
|||||||
"description": I18n.tr("Quick note-taking slideout panel"),
|
"description": I18n.tr("Quick note-taking slideout panel"),
|
||||||
"icon": "sticky_note_2"
|
"icon": "sticky_note_2"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "systemTray",
|
|
||||||
"name": I18n.tr("System Tray"),
|
|
||||||
"description": I18n.tr("System tray icons"),
|
|
||||||
"icon": "notifications"
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -822,7 +816,7 @@ Item {
|
|||||||
const prefs = displaysTab.getScreenPreferences(parent.componentId);
|
const prefs = displaysTab.getScreenPreferences(parent.componentId);
|
||||||
const isAll = prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
|
const isAll = prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
|
||||||
const cid = parent.componentId;
|
const cid = parent.componentId;
|
||||||
const isRelevantComponent = ["dankBar", "dock", "notifications", "osd", "toast", "notepad", "systemTray"].includes(cid) || cid.startsWith("bar:");
|
const isRelevantComponent = ["dankBar", "dock", "notifications", "osd", "toast", "notepad"].includes(cid) || cid.startsWith("bar:");
|
||||||
return !isAll && isRelevantComponent;
|
return !isAll && isRelevantComponent;
|
||||||
}
|
}
|
||||||
onToggled: checked => {
|
onToggled: checked => {
|
||||||
@@ -906,4 +900,4 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ?? "";
|
||||||
|
|||||||
@@ -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: {
|
Component.onCompleted: {
|
||||||
const cfg = parent.parent.argConfig;
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,319 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -uo pipefail
|
|
||||||
|
|
||||||
log() { echo "[matugen-worker] $*" >&2; }
|
|
||||||
err() { echo "[matugen-worker] ERROR: $*" >&2; }
|
|
||||||
|
|
||||||
[[ $# -lt 6 ]] && { echo "Usage: $0 STATE_DIR SHELL_DIR CONFIG_DIR SYNC_MODE_WITH_PORTAL TERMINALS_ALWAYS_DARK --run" >&2; exit 1; }
|
|
||||||
|
|
||||||
STATE_DIR="$1"
|
|
||||||
SHELL_DIR="$2"
|
|
||||||
CONFIG_DIR="$3"
|
|
||||||
SYNC_MODE_WITH_PORTAL="$4"
|
|
||||||
TERMINALS_ALWAYS_DARK="$5"
|
|
||||||
shift 5
|
|
||||||
[[ "${1:-}" != "--run" ]] && { echo "Usage: $0 ... --run" >&2; exit 1; }
|
|
||||||
|
|
||||||
[[ ! -d "$STATE_DIR" ]] && { err "STATE_DIR '$STATE_DIR' does not exist"; exit 1; }
|
|
||||||
[[ ! -d "$SHELL_DIR" ]] && { err "SHELL_DIR '$SHELL_DIR' does not exist"; exit 1; }
|
|
||||||
[[ ! -d "$CONFIG_DIR" ]] && { err "CONFIG_DIR '$CONFIG_DIR' does not exist"; exit 1; }
|
|
||||||
|
|
||||||
DESIRED_JSON="$STATE_DIR/matugen.desired.json"
|
|
||||||
BUILT_KEY="$STATE_DIR/matugen.key"
|
|
||||||
LOCK="$STATE_DIR/matugen-worker.lock"
|
|
||||||
COLORS_OUTPUT="$STATE_DIR/dms-colors.json"
|
|
||||||
|
|
||||||
exec 9>"$LOCK"
|
|
||||||
flock 9
|
|
||||||
rm -f "$BUILT_KEY"
|
|
||||||
|
|
||||||
read_json_field() {
|
|
||||||
local json="$1" field="$2"
|
|
||||||
echo "$json" | sed -n "s/.*\"$field\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p" | head -1
|
|
||||||
}
|
|
||||||
|
|
||||||
read_json_escaped_field() {
|
|
||||||
local json="$1" field="$2"
|
|
||||||
local after="${json#*\"$field\":\"}"
|
|
||||||
[[ "$after" == "$json" ]] && return
|
|
||||||
local result=""
|
|
||||||
while [[ -n "$after" ]]; do
|
|
||||||
local char="${after:0:1}"
|
|
||||||
after="${after:1}"
|
|
||||||
[[ "$char" == '"' ]] && break
|
|
||||||
[[ "$char" == '\' ]] && { result+="${after:0:1}"; after="${after:1}"; continue; }
|
|
||||||
result+="$char"
|
|
||||||
done
|
|
||||||
echo "$result"
|
|
||||||
}
|
|
||||||
|
|
||||||
read_json_bool() {
|
|
||||||
local json="$1" field="$2"
|
|
||||||
echo "$json" | sed -n "s/.*\"$field\"[[:space:]]*:[[:space:]]*\([^,}]*\).*/\1/p" | head -1 | tr -d ' '
|
|
||||||
}
|
|
||||||
|
|
||||||
compute_key() {
|
|
||||||
local json="$1"
|
|
||||||
local kind=$(read_json_field "$json" "kind")
|
|
||||||
local value=$(read_json_field "$json" "value")
|
|
||||||
local mode=$(read_json_field "$json" "mode")
|
|
||||||
local icon=$(read_json_field "$json" "iconTheme")
|
|
||||||
local mtype=$(read_json_field "$json" "matugenType")
|
|
||||||
local run_user=$(read_json_bool "$json" "runUserTemplates")
|
|
||||||
local stock_colors=$(read_json_escaped_field "$json" "stockColors")
|
|
||||||
echo "${kind}|${value}|${mode}|${icon:-default}|${mtype:-scheme-tonal-spot}|${run_user:-true}|${stock_colors:-}|${TERMINALS_ALWAYS_DARK:-false}" | sha256sum | cut -d' ' -f1
|
|
||||||
}
|
|
||||||
|
|
||||||
append_config() {
|
|
||||||
local check_cmd="$1" file_name="$2" cfg_file="$3"
|
|
||||||
local target="$SHELL_DIR/matugen/configs/$file_name"
|
|
||||||
[[ ! -f "$target" ]] && return
|
|
||||||
[[ "$check_cmd" != "skip" ]] && ! command -v "$check_cmd" >/dev/null 2>&1 && return
|
|
||||||
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$target" >> "$cfg_file"
|
|
||||||
echo "" >> "$cfg_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
append_terminal_config() {
|
|
||||||
local check_cmd="$1" file_name="$2" cfg_file="$3" tmp_dir="$4"
|
|
||||||
local config_file="$SHELL_DIR/matugen/configs/$file_name"
|
|
||||||
[[ ! -f "$config_file" ]] && return
|
|
||||||
[[ "$check_cmd" != "skip" ]] && ! command -v "$check_cmd" >/dev/null 2>&1 && return
|
|
||||||
|
|
||||||
if [[ "$TERMINALS_ALWAYS_DARK" == "true" ]]; then
|
|
||||||
local config_content
|
|
||||||
config_content=$(cat "$config_file")
|
|
||||||
local templates
|
|
||||||
templates=$(echo "$config_content" | grep "input_path.*SHELL_DIR/matugen/templates/" | sed "s/.*'SHELL_DIR\/matugen\/templates\/\([^']*\)'.*/\1/")
|
|
||||||
for tpl in $templates; do
|
|
||||||
local orig="$SHELL_DIR/matugen/templates/$tpl"
|
|
||||||
[[ ! -f "$orig" ]] && continue
|
|
||||||
local tmp_template="$tmp_dir/$tpl"
|
|
||||||
sed 's/\.default\./\.dark\./g' "$orig" > "$tmp_template"
|
|
||||||
config_content=$(echo "$config_content" | sed "s|'SHELL_DIR/matugen/templates/$tpl'|'$tmp_template'|g")
|
|
||||||
done
|
|
||||||
echo "$config_content" | sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" >> "$cfg_file"
|
|
||||||
echo "" >> "$cfg_file"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" "$config_file" >> "$cfg_file"
|
|
||||||
echo "" >> "$cfg_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
append_vscode_config() {
|
|
||||||
local name="$1" ext_dir="$2" cfg_file="$3"
|
|
||||||
[[ ! -d "$ext_dir" ]] && return
|
|
||||||
local template_dir="$SHELL_DIR/matugen/templates"
|
|
||||||
cat >> "$cfg_file" << EOF
|
|
||||||
[templates.dms${name}default]
|
|
||||||
input_path = '$template_dir/vscode-color-theme-default.json'
|
|
||||||
output_path = '$ext_dir/themes/dankshell-default.json'
|
|
||||||
|
|
||||||
[templates.dms${name}dark]
|
|
||||||
input_path = '$template_dir/vscode-color-theme-dark.json'
|
|
||||||
output_path = '$ext_dir/themes/dankshell-dark.json'
|
|
||||||
|
|
||||||
[templates.dms${name}light]
|
|
||||||
input_path = '$template_dir/vscode-color-theme-light.json'
|
|
||||||
output_path = '$ext_dir/themes/dankshell-light.json'
|
|
||||||
|
|
||||||
EOF
|
|
||||||
log "Added $name theme config (extension found at $ext_dir)"
|
|
||||||
}
|
|
||||||
|
|
||||||
build_merged_config() {
|
|
||||||
local mode="$1" run_user="$2" cfg_file="$3" tmp_dir="$4"
|
|
||||||
|
|
||||||
if [[ "$run_user" == "true" && -f "$CONFIG_DIR/matugen/config.toml" ]]; then
|
|
||||||
awk '/^\[config\]/{p=1} /^\[templates\]/{p=0} p' "$CONFIG_DIR/matugen/config.toml" >> "$cfg_file"
|
|
||||||
else
|
|
||||||
echo "[config]" >> "$cfg_file"
|
|
||||||
fi
|
|
||||||
echo "" >> "$cfg_file"
|
|
||||||
|
|
||||||
grep -v '^\[config\]' "$SHELL_DIR/matugen/configs/base.toml" | sed "s|'SHELL_DIR/|'$SHELL_DIR/|g" >> "$cfg_file"
|
|
||||||
echo "" >> "$cfg_file"
|
|
||||||
|
|
||||||
cat >> "$cfg_file" << EOF
|
|
||||||
[templates.dank]
|
|
||||||
input_path = '$SHELL_DIR/matugen/templates/dank.json'
|
|
||||||
output_path = '$COLORS_OUTPUT'
|
|
||||||
|
|
||||||
EOF
|
|
||||||
|
|
||||||
[[ "$mode" == "light" ]] && append_config "skip" "gtk3-light.toml" "$cfg_file" || append_config "skip" "gtk3-dark.toml" "$cfg_file"
|
|
||||||
|
|
||||||
append_config "niri" "niri.toml" "$cfg_file"
|
|
||||||
append_config "qt5ct" "qt5ct.toml" "$cfg_file"
|
|
||||||
append_config "qt6ct" "qt6ct.toml" "$cfg_file"
|
|
||||||
append_config "firefox" "firefox.toml" "$cfg_file"
|
|
||||||
append_config "pywalfox" "pywalfox.toml" "$cfg_file"
|
|
||||||
append_config "vesktop" "vesktop.toml" "$cfg_file"
|
|
||||||
append_terminal_config "ghostty" "ghostty.toml" "$cfg_file" "$tmp_dir"
|
|
||||||
append_terminal_config "kitty" "kitty.toml" "$cfg_file" "$tmp_dir"
|
|
||||||
append_terminal_config "foot" "foot.toml" "$cfg_file" "$tmp_dir"
|
|
||||||
append_terminal_config "alacritty" "alacritty.toml" "$cfg_file" "$tmp_dir"
|
|
||||||
append_terminal_config "wezterm" "wezterm.toml" "$cfg_file" "$tmp_dir"
|
|
||||||
append_config "dgop" "dgop.toml" "$cfg_file"
|
|
||||||
|
|
||||||
append_vscode_config "vscode" "$HOME/.vscode/extensions/local.dynamic-base16-dankshell-0.0.1" "$cfg_file"
|
|
||||||
append_vscode_config "codium" "$HOME/.vscode-oss/extensions/local.dynamic-base16-dankshell-0.0.1" "$cfg_file"
|
|
||||||
append_vscode_config "codeoss" "$HOME/.config/Code - OSS/extensions/local.dynamic-base16-dankshell-0.0.1" "$cfg_file"
|
|
||||||
append_vscode_config "cursor" "$HOME/.cursor/extensions/local.dynamic-base16-dankshell-0.0.1" "$cfg_file"
|
|
||||||
append_vscode_config "windsurf" "$HOME/.windsurf/extensions/local.dynamic-base16-dankshell-0.0.1" "$cfg_file"
|
|
||||||
|
|
||||||
if [[ "$run_user" == "true" && -f "$CONFIG_DIR/matugen/config.toml" ]]; then
|
|
||||||
awk '/^\[templates\]/{p=1} p' "$CONFIG_DIR/matugen/config.toml" >> "$cfg_file"
|
|
||||||
echo "" >> "$cfg_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -d "$CONFIG_DIR/matugen/dms/configs" ]]; then
|
|
||||||
for config in "$CONFIG_DIR/matugen/dms/configs"/*.toml; do
|
|
||||||
[[ -f "$config" ]] || continue
|
|
||||||
cat "$config" >> "$cfg_file"
|
|
||||||
echo "" >> "$cfg_file"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
generate_dank16_variants() {
|
|
||||||
local primary_dark="$1" primary_light="$2" surface="$3" mode="$4"
|
|
||||||
local args=(--variants --primary-dark "$primary_dark" --primary-light "$primary_light")
|
|
||||||
[[ "$mode" == "light" ]] && args+=(--light)
|
|
||||||
[[ -n "$surface" ]] && args+=(--background "$surface")
|
|
||||||
dms dank16 "${args[@]}" 2>/dev/null || echo '{}'
|
|
||||||
}
|
|
||||||
|
|
||||||
set_system_color_scheme() {
|
|
||||||
[[ "$SYNC_MODE_WITH_PORTAL" != "true" ]] && return
|
|
||||||
local mode="$1"
|
|
||||||
local scheme="prefer-dark"
|
|
||||||
[[ "$mode" == "light" ]] && scheme="default"
|
|
||||||
gsettings set org.gnome.desktop.interface color-scheme "$scheme" 2>/dev/null || \
|
|
||||||
dconf write /org/gnome/desktop/interface/color-scheme "'$scheme'" 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
sync_color_scheme_on_exit() {
|
|
||||||
[[ "$SYNC_MODE_WITH_PORTAL" != "true" ]] && return
|
|
||||||
[[ ! -f "$DESIRED_JSON" ]] && return
|
|
||||||
local json mode
|
|
||||||
json=$(cat "$DESIRED_JSON" 2>/dev/null) || return
|
|
||||||
mode=$(read_json_field "$json" "mode")
|
|
||||||
[[ -n "$mode" ]] && set_system_color_scheme "$mode"
|
|
||||||
}
|
|
||||||
|
|
||||||
trap sync_color_scheme_on_exit EXIT
|
|
||||||
|
|
||||||
refresh_gtk() {
|
|
||||||
local mode="$1"
|
|
||||||
local gtk_css="$CONFIG_DIR/gtk-3.0/gtk.css"
|
|
||||||
[[ ! -e "$gtk_css" ]] && return
|
|
||||||
local should_run=false
|
|
||||||
if [[ -L "$gtk_css" ]]; then
|
|
||||||
[[ "$(readlink "$gtk_css")" == *"dank-colors.css"* ]] && should_run=true
|
|
||||||
elif grep -q "dank-colors.css" "$gtk_css" 2>/dev/null; then
|
|
||||||
should_run=true
|
|
||||||
fi
|
|
||||||
[[ "$should_run" != "true" ]] && return
|
|
||||||
gsettings set org.gnome.desktop.interface gtk-theme "" 2>/dev/null || true
|
|
||||||
gsettings set org.gnome.desktop.interface gtk-theme "adw-gtk3-${mode}" 2>/dev/null || true
|
|
||||||
}
|
|
||||||
|
|
||||||
signal_terminals() {
|
|
||||||
pgrep -x kitty >/dev/null 2>&1 && pkill -USR1 kitty
|
|
||||||
pgrep -x ghostty >/dev/null 2>&1 && pkill -USR2 ghostty
|
|
||||||
pgrep -x .kitty-wrapped >/dev/null 2>&1 && pkill -USR2 .kitty-wrapped
|
|
||||||
pgrep -x .ghostty-wrappe >/dev/null 2>&1 && pkill -USR2 .ghostty-wrappe
|
|
||||||
}
|
|
||||||
|
|
||||||
build_once() {
|
|
||||||
local json="$1"
|
|
||||||
local kind=$(read_json_field "$json" "kind")
|
|
||||||
local value=$(read_json_field "$json" "value")
|
|
||||||
local mode=$(read_json_field "$json" "mode")
|
|
||||||
local mtype=$(read_json_field "$json" "matugenType")
|
|
||||||
local run_user=$(read_json_bool "$json" "runUserTemplates")
|
|
||||||
local stock_colors=$(read_json_escaped_field "$json" "stockColors")
|
|
||||||
|
|
||||||
[[ -z "$mtype" ]] && mtype="scheme-tonal-spot"
|
|
||||||
[[ -z "$run_user" ]] && run_user="true"
|
|
||||||
|
|
||||||
local TMP_CFG=$(mktemp)
|
|
||||||
local TMP_DIR=$(mktemp -d)
|
|
||||||
trap "rm -f '$TMP_CFG'; rm -rf '$TMP_DIR'" RETURN
|
|
||||||
|
|
||||||
build_merged_config "$mode" "$run_user" "$TMP_CFG" "$TMP_DIR"
|
|
||||||
|
|
||||||
local primary_dark primary_light surface dank16 import_args=()
|
|
||||||
|
|
||||||
if [[ -n "$stock_colors" ]]; then
|
|
||||||
log "Using stock/custom theme colors with matugen base"
|
|
||||||
primary_dark=$(echo "$stock_colors" | sed -n 's/.*"primary"[^{]*{[^}]*"dark"[^{]*{[^}]*"color"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
|
|
||||||
primary_light=$(echo "$stock_colors" | sed -n 's/.*"primary"[^{]*{[^}]*"light"[^{]*{[^}]*"color"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
|
|
||||||
surface=$(echo "$stock_colors" | sed -n 's/.*"surface"[^{]*{[^}]*"dark"[^{]*{[^}]*"color"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' | head -1)
|
|
||||||
|
|
||||||
[[ -z "$primary_dark" ]] && { err "Failed to extract primary dark from stock colors"; return 1; }
|
|
||||||
[[ -z "$primary_light" ]] && primary_light="$primary_dark"
|
|
||||||
|
|
||||||
dank16=$(generate_dank16_variants "$primary_dark" "$primary_light" "$surface" "$mode")
|
|
||||||
|
|
||||||
import_args+=(--import-json-string "{\"colors\": $stock_colors, \"dank16\": $dank16}")
|
|
||||||
|
|
||||||
log "Running matugen color hex with stock color overrides"
|
|
||||||
if ! matugen color hex "$primary_dark" -m "$mode" -t "${mtype:-scheme-tonal-spot}" -c "$TMP_CFG" "${import_args[@]}"; then
|
|
||||||
err "matugen failed"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
log "Using dynamic theme from $kind: $value"
|
|
||||||
|
|
||||||
local matugen_cmd=("matugen")
|
|
||||||
[[ "$kind" == "hex" ]] && matugen_cmd+=("color" "hex") || matugen_cmd+=("$kind")
|
|
||||||
matugen_cmd+=("$value")
|
|
||||||
|
|
||||||
local mat_json
|
|
||||||
mat_json=$("${matugen_cmd[@]}" -m dark -t "$mtype" --json hex --dry-run 2>/dev/null | tr -d '\n')
|
|
||||||
[[ -z "$mat_json" ]] && { err "matugen dry-run failed"; return 1; }
|
|
||||||
|
|
||||||
primary_dark=$(echo "$mat_json" | sed -n 's/.*"primary"[[:space:]]*:[[:space:]]*{[^}]*"dark"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
|
|
||||||
primary_light=$(echo "$mat_json" | sed -n 's/.*"primary"[[:space:]]*:[[:space:]]*{[^}]*"light"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
|
|
||||||
surface=$(echo "$mat_json" | sed -n 's/.*"surface"[[:space:]]*:[[:space:]]*{[^}]*"dark"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
|
|
||||||
|
|
||||||
[[ -z "$primary_dark" ]] && { err "Failed to extract primary color"; return 1; }
|
|
||||||
[[ -z "$primary_light" ]] && primary_light="$primary_dark"
|
|
||||||
|
|
||||||
dank16=$(generate_dank16_variants "$primary_dark" "$primary_light" "$surface" "$mode")
|
|
||||||
|
|
||||||
import_args+=(--import-json-string "{\"dank16\": $dank16}")
|
|
||||||
|
|
||||||
log "Running matugen $kind with dank16 injection"
|
|
||||||
if ! "${matugen_cmd[@]}" -m "$mode" -t "$mtype" -c "$TMP_CFG" "${import_args[@]}"; then
|
|
||||||
err "matugen failed"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
refresh_gtk "$mode"
|
|
||||||
signal_terminals
|
|
||||||
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
[[ ! -f "$DESIRED_JSON" ]] && { log "No desired state file"; exit 0; }
|
|
||||||
|
|
||||||
DESIRED=$(cat "$DESIRED_JSON")
|
|
||||||
WANT_KEY=$(compute_key "$DESIRED")
|
|
||||||
HAVE_KEY=""
|
|
||||||
[[ -f "$BUILT_KEY" ]] && HAVE_KEY=$(cat "$BUILT_KEY" 2>/dev/null || true)
|
|
||||||
|
|
||||||
[[ "$WANT_KEY" == "$HAVE_KEY" ]] && { log "Already up to date"; exit 0; }
|
|
||||||
|
|
||||||
log "Building theme (key: ${WANT_KEY:0:12}...)"
|
|
||||||
if build_once "$DESIRED"; then
|
|
||||||
echo "$WANT_KEY" > "$BUILT_KEY"
|
|
||||||
log "Done"
|
|
||||||
exit 0
|
|
||||||
else
|
|
||||||
err "Build failed"
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
Reference in New Issue
Block a user