1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-05-13 07:42:46 -04:00

Compare commits

...

26 Commits

Author SHA1 Message Date
Marcus Ramberg 8fad2826b1 ci: add flake check 2025-12-08 16:09:16 +01:00
bbedward 57ee0fb2bd bump: failed fprint tries 2025-12-08 10:02:53 -05:00
osscar 3ef10e73a5 nix: remove leading dot in nativeBuildInputs (#948)
Co-authored-by: osscar <osscar.unheard025@passmail.net>
2025-12-08 15:52:32 +01:00
bbedward dc40492fc7 cc: fix audio slider binding 2025-12-08 09:45:25 -05:00
bbedward e606a76a86 screenshot: add screenshot-window support for DWL/MangoWC 2025-12-08 09:39:42 -05:00
Lucas 8838fd67b9 nix: add dev-shell (#944)
* nix: add dev-shell

* docs: add Nix dev shell in contributing docs
2025-12-08 12:22:07 +01:00
Lucas c570e20308 nix: use quickshell from source by default in greeter (#941) 2025-12-08 07:37:29 +01:00
bbedward 0a00ef39e3 ipc: fix bar widget IPCs when screens change 2025-12-07 23:15:24 -05:00
bbedward 9a08b81214 dankinstall: swap to systemd by default, use 90-dms.conf for vars 2025-12-07 22:51:22 -05:00
bbedward c617ae26a2 niri: fix some keybind tab issues
- Fix args for screenshot
- move-column stuff is focus=true by default
- Parsing fixes
part of #914
2025-12-07 22:41:01 -05:00
Lucas f6a776a692 nix: use by default quickshell from source (#939) 2025-12-07 21:11:22 -05:00
bbedward 54b253099d dankinstall: update hyprland syntax
fixes #913
2025-12-07 21:03:24 -05:00
bbedward f662aca58c dankinstall: replace grim+slurp+grimblast with dms 2025-12-07 20:59:46 -05:00
bbedward 76e7755496 consistent icon sizing 2025-12-07 20:21:07 -05:00
bbedward e05ad81c13 displays: remove system tray per-display opt
- superceded by omegabar
2025-12-07 20:13:40 -05:00
bbedward cffb16d7f7 matugen: make signalByName helper not use exec 2025-12-07 20:10:31 -05:00
bbedward 18ca571944 matugen: scrap shell script for proper backend implementation with queue
system
2025-12-07 20:00:43 -05:00
bbedward 3ae1973e21 screenshot/colorpicker: fix scaling, update go-wayland to fix object
destruction, fix hyprland window detection
2025-12-07 13:44:35 -05:00
bbedward 308c8c3ea7 lock screen: fix inconsistency with network status, add VPN
maybe fix #926
2025-12-07 12:33:29 -05:00
bbedward f49b5dd037 media player: replace color quantizer with album art 2025-12-07 12:23:00 -05:00
bbedward f245ba82ad gamma: fix non-automation toggling
fixes #924
2025-12-07 12:02:50 -05:00
arfan 60d22d6973 feat: add workspace index display when app icon enabled (#936) 2025-12-07 11:48:48 -05:00
Farokh d6f48a82d9 Update VSCode color theme templates for improved contrast and readability (#931)
* matugen/vscode-theme: update VSCode templates for contrast and readability

* vscode-theme: rework dark theme, refine light, restore default fallback

* dank16: add variants option, make default vscode consistent, fix termial
always dark

---------

Co-authored-by: bbedward <bbedward@gmail.com>
2025-12-07 11:47:25 -05:00
Marcus Ramberg c0d73dae67 fix: handle ipc arguments (#930) 2025-12-07 11:01:31 -05:00
Marcus Ramberg 49eb60589d fix: also restart ghostty/kitty on nix (#934) 2025-12-07 10:28:26 -05:00
Marcus Ramberg 89993b7421 core: remove unused function after refactors (#935) 2025-12-07 10:27:44 -05:00
81 changed files with 3843 additions and 2477 deletions
+30
View File
@@ -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
View File
@@ -102,39 +102,6 @@ go.work.sum
# .idea/
# .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/
# Extracted source trees in Ubuntu package directories
@@ -142,3 +109,7 @@ distro/ubuntu/*/dms-git-repo/
distro/ubuntu/*/DankMaterialShell-*/
distro/ubuntu/danklinux/*/dsearch-*/
distro/ubuntu/danklinux/*/dgop-*/
# direnv
.envrc
.direnv/
+15
View File
@@ -12,6 +12,21 @@ Enable pre-commit hooks to catch CI failures before pushing:
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
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.
+1
View File
@@ -21,6 +21,7 @@ linters:
# Signal handling
- (*os.Process).Signal
- (*os.Process).Kill
- syscall.Kill
# DBus cleanup
- (*github.com/godbus/dbus/v5.Conn).RemoveMatchSignal
- (*github.com/godbus/dbus/v5.Conn).RemoveSignal
+1 -1
View File
@@ -454,7 +454,6 @@ func uninstallPluginCLI(idOrName string) error {
return nil
}
// getCommonCommands returns the commands available in all builds
func getCommonCommands() []*cobra.Command {
return []*cobra.Command{
versionCmd,
@@ -474,5 +473,6 @@ func getCommonCommands() []*cobra.Command {
colorCmd,
screenshotCmd,
notifyActionCmd,
matugenCmd,
}
}
+47 -8
View File
@@ -10,15 +10,15 @@ import (
)
var dank16Cmd = &cobra.Command{
Use: "dank16 <hex_color>",
Use: "dank16 [hex_color]",
Short: "Generate Base16 color palettes",
Long: "Generate Base16 color palettes from a color with support for various output formats",
Args: cobra.ExactArgs(1),
Args: cobra.MaximumNArgs(1),
Run: runDank16,
}
func init() {
dank16Cmd.Flags().Bool("light", false, "Generate light theme variant")
dank16Cmd.Flags().Bool("light", false, "Generate light theme variant (sets default to light)")
dank16Cmd.Flags().Bool("json", false, "Output in JSON format")
dank16Cmd.Flags().Bool("kitty", false, "Output in Kitty terminal format")
dank16Cmd.Flags().Bool("foot", false, "Output in Foot terminal format")
@@ -27,17 +27,15 @@ func init() {
dank16Cmd.Flags().Bool("wezterm", false, "Output in Wezterm terminal format")
dank16Cmd.Flags().String("background", "", "Custom background color")
dank16Cmd.Flags().String("contrast", "dps", "Contrast algorithm: dps (Delta Phi Star, default) or wcag")
dank16Cmd.Flags().Bool("variants", false, "Output all variants (dark/light/default) in JSON")
dank16Cmd.Flags().String("primary-dark", "", "Primary color for dark mode (use with --variants)")
dank16Cmd.Flags().String("primary-light", "", "Primary color for light mode (use with --variants)")
_ = dank16Cmd.RegisterFlagCompletionFunc("contrast", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"dps", "wcag"}, cobra.ShellCompDirectiveNoFileComp
})
}
func runDank16(cmd *cobra.Command, args []string) {
primaryColor := args[0]
if !strings.HasPrefix(primaryColor, "#") {
primaryColor = "#" + primaryColor
}
isLight, _ := cmd.Flags().GetBool("light")
isJson, _ := cmd.Flags().GetBool("json")
isKitty, _ := cmd.Flags().GetBool("kitty")
@@ -47,16 +45,57 @@ func runDank16(cmd *cobra.Command, args []string) {
isWezterm, _ := cmd.Flags().GetBool("wezterm")
background, _ := cmd.Flags().GetString("background")
contrastAlgo, _ := cmd.Flags().GetString("contrast")
useVariants, _ := cmd.Flags().GetBool("variants")
primaryDark, _ := cmd.Flags().GetString("primary-dark")
primaryLight, _ := cmd.Flags().GetString("primary-light")
if background != "" && !strings.HasPrefix(background, "#") {
background = "#" + background
}
if primaryDark != "" && !strings.HasPrefix(primaryDark, "#") {
primaryDark = "#" + primaryDark
}
if primaryLight != "" && !strings.HasPrefix(primaryLight, "#") {
primaryLight = "#" + primaryLight
}
contrastAlgo = strings.ToLower(contrastAlgo)
if contrastAlgo != "dps" && contrastAlgo != "wcag" {
log.Fatalf("Invalid contrast algorithm: %s (must be 'dps' or 'wcag')", contrastAlgo)
}
if useVariants {
if primaryDark == "" || primaryLight == "" {
if len(args) == 0 {
log.Fatalf("--variants requires either a positional color argument or both --primary-dark and --primary-light")
}
primaryColor := args[0]
if !strings.HasPrefix(primaryColor, "#") {
primaryColor = "#" + primaryColor
}
primaryDark = primaryColor
primaryLight = primaryColor
}
variantOpts := dank16.VariantOptions{
PrimaryDark: primaryDark,
PrimaryLight: primaryLight,
Background: background,
UseDPS: contrastAlgo == "dps",
IsLightMode: isLight,
}
variantColors := dank16.GenerateVariantPalette(variantOpts)
fmt.Print(dank16.GenerateVariantJSON(variantColors))
return
}
if len(args) == 0 {
log.Fatalf("A color argument is required (or use --variants with --primary-dark and --primary-light)")
}
primaryColor := args[0]
if !strings.HasPrefix(primaryColor, "#") {
primaryColor = "#" + primaryColor
}
opts := dank16.PaletteOptions{
IsLight: isLight,
Background: background,
+182
View File
@@ -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")
}
}
+3 -4
View File
@@ -35,7 +35,7 @@ Modes:
full - Capture the focused output
all - Capture all outputs combined
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
Output format (--format):
@@ -91,9 +91,8 @@ If no previous region exists, falls back to interactive selection.`,
var ssWindowCmd = &cobra.Command{
Use: "window",
Short: "Capture the focused window",
Long: `Capture the currently focused window.
Currently only supported on Hyprland.`,
Run: runScreenshotWindow,
Long: `Capture the currently focused window. Supported on Hyprland and DWL.`,
Run: runScreenshotWindow,
}
var ssListCmd = &cobra.Command{
+35 -7
View File
@@ -16,7 +16,7 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/internal/server"
)
type ipcTargets map[string][]string
type ipcTargets map[string]map[string][]string
var isSessionManaged bool
@@ -476,28 +476,40 @@ func runShellDaemon(session bool) {
}
func parseTargetsFromIPCShowOutput(output string) ipcTargets {
targets := map[string][]string{}
targets := make(ipcTargets)
var currentTarget string
for _, line := range strings.Split(output, "\n") {
if strings.HasPrefix(line, "target ") {
currentTarget = strings.TrimSpace(strings.TrimPrefix(line, "target "))
targets[currentTarget] = make(map[string][]string)
}
if strings.HasPrefix(line, " function") && currentTarget != "" {
argsList := []string{}
currentFunc := strings.TrimPrefix(line, " function ")
currentFunc = strings.SplitN(currentFunc, "(", 2)[0]
targets[currentTarget] = append(targets[currentTarget], currentFunc)
funcDef := strings.SplitN(currentFunc, "(", 2)
argList := strings.SplitN(funcDef[1], ")", 2)[0]
args := strings.Split(argList, ",")
if len(args) > 0 && strings.TrimSpace(args[0]) != "" {
argsList = append(argsList, funcDef[0])
for _, arg := range args {
argName := strings.SplitN(strings.TrimSpace(arg), ":", 2)[0]
argsList = append(argsList, argName)
}
targets[currentTarget][funcDef[0]] = argsList
} else {
targets[currentTarget][funcDef[0]] = make([]string, 0)
}
}
}
return targets
}
func getShellIPCCompletions(args []string, toComplete string) []string {
func getShellIPCCompletions(args []string, _ string) []string {
cmdArgs := []string{"-p", configPath, "ipc", "show"}
cmd := exec.Command("qs", cmdArgs...)
var targets ipcTargets
if output, err := cmd.Output(); err == nil {
log.Debugf("IPC show output: %s", string(output))
targets = parseTargetsFromIPCShowOutput(string(output))
} else {
log.Debugf("Error getting IPC show output for completions: %v", err)
@@ -516,8 +528,24 @@ func getShellIPCCompletions(args []string, toComplete string) []string {
}
return targetNames
}
if len(args) == 1 {
if targetFuncs, ok := targets[args[0]]; ok {
funcNames := make([]string, 0)
for k := range targetFuncs {
funcNames = append(funcNames, k)
}
return funcNames
}
return nil
}
if len(args) <= len(targets[args[0]]) {
funcArgs := targets[args[0]][args[1]]
if len(funcArgs) >= len(args) {
return []string{fmt.Sprintf("[%s]", funcArgs[len(args)-1])}
}
}
return targets[args[0]]
return nil
}
func runShellIPCCommand(args []string) {
+30 -23
View File
@@ -2,7 +2,6 @@ package colorpicker
import (
"fmt"
"math"
"sync"
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
@@ -116,6 +115,11 @@ func (p *Picker) Run() (*Color, error) {
return nil, fmt.Errorf("roundtrip: %w", err)
}
// Extra roundtrip to ensure pointer/keyboard from seat capabilities are registered
if err := p.roundtrip(); err != nil {
return nil, fmt.Errorf("roundtrip after seat: %w", err)
}
if err := p.createSurfaces(); err != nil {
return nil, fmt.Errorf("create surfaces: %w", err)
}
@@ -405,15 +409,10 @@ func (p *Picker) createLayerSurface(output *Output) (*LayerSurface, error) {
func (p *Picker) computeSurfaceScale(ls *LayerSurface) int32 {
out := ls.output
if out == nil || out.fractionalScale <= 0 {
if out == nil || out.scale <= 0 {
return 1
}
scale := int32(math.Ceil(out.fractionalScale))
if scale <= 0 {
scale = 1
}
return scale
return out.scale
}
func (p *Picker) ensureShortcutsInhibitor(ls *LayerSurface) {
@@ -485,6 +484,13 @@ func (p *Picker) captureForSurface(ls *LayerSurface) {
frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) {
ls.state.OnScreencopyReady()
logicalW, _ := ls.state.LogicalSize()
screenBuf := ls.state.ScreenBuffer()
if logicalW > 0 && screenBuf != nil {
ls.output.fractionalScale = float64(screenBuf.Width) / float64(logicalW)
}
scale := p.computeSurfaceScale(ls)
ls.state.SetScale(scale)
frame.Destroy()
@@ -545,18 +551,17 @@ func (p *Picker) redrawSurface(ls *LayerSurface) {
logicalH = int(ls.output.height)
}
scale := ls.state.Scale()
if scale <= 0 {
scale = 1
}
if ls.viewport != nil {
srcW := float64(renderBuf.Width) / float64(scale)
srcH := float64(renderBuf.Height) / float64(scale)
_ = ls.viewport.SetSource(0, 0, srcW, srcH)
_ = ls.wlSurface.SetBufferScale(1)
_ = ls.viewport.SetSource(0, 0, float64(renderBuf.Width), float64(renderBuf.Height))
_ = ls.viewport.SetDestination(int32(logicalW), int32(logicalH))
} else {
bufferScale := ls.output.scale
if bufferScale <= 0 {
bufferScale = 1
}
_ = ls.wlSurface.SetBufferScale(bufferScale)
}
_ = ls.wlSurface.SetBufferScale(scale)
_ = ls.wlSurface.Attach(wlBuffer, 0, 0)
_ = ls.wlSurface.Damage(0, 0, int32(logicalW), int32(logicalH))
_ = ls.wlSurface.Commit()
@@ -581,17 +586,19 @@ func (p *Picker) setupInput() {
p.seat.SetCapabilitiesHandler(func(e client.SeatCapabilitiesEvent) {
if e.Capabilities&uint32(client.SeatCapabilityPointer) != 0 && p.pointer == nil {
pointer, err := p.seat.GetPointer()
if err == nil {
p.pointer = pointer
p.setupPointerHandlers()
if err != nil {
return
}
p.pointer = pointer
p.setupPointerHandlers()
}
if e.Capabilities&uint32(client.SeatCapabilityKeyboard) != 0 && p.keyboard == nil {
keyboard, err := p.seat.GetKeyboard()
if err == nil {
p.keyboard = keyboard
p.setupKeyboardHandlers()
if err != nil {
return
}
p.keyboard = keyboard
p.setupKeyboardHandlers()
}
})
}
+20 -7
View File
@@ -269,12 +269,17 @@ func (s *SurfaceState) Redraw() *ShmBuffer {
px = clamp(px, 0, dst.Width-1)
py = clamp(py, 0, dst.Height-1)
picked := GetPixelColorWithFormat(s.screenBuf, px, py, s.screenFormat)
sampleY := py
if s.yInverted {
sampleY = s.screenBuf.Height - 1 - py
}
drawMagnifier(
picked := GetPixelColorWithFormat(s.screenBuf, px, sampleY, s.screenFormat)
drawMagnifierWithInversion(
dst.Data(), dst.Stride, dst.Width, dst.Height,
s.screenBuf.Data(), s.screenBuf.Stride, s.screenBuf.Width, s.screenBuf.Height,
px, py, picked,
px, py, picked, s.yInverted,
)
drawColorPreview(dst.Data(), dst.Stride, dst.Width, dst.Height, px, py, picked, s.displayFormat, s.lowercase)
@@ -379,11 +384,12 @@ func blendColors(bg, fg Color, alpha float64) Color {
}
}
func drawMagnifier(
func drawMagnifierWithInversion(
dst []byte, dstStride, dstW, dstH int,
src []byte, srcStride, srcW, srcH int,
cx, cy int,
borderColor Color,
yInverted bool,
) {
if dstW <= 0 || dstH <= 0 || srcW <= 0 || srcH <= 0 {
return
@@ -439,10 +445,11 @@ func drawMagnifier(
finalColor = blendColors(bgColor, borderColor, alpha)
case dist > innerRadius:
if dist > outerRadiusF-aaWidth {
switch {
case dist > outerRadiusF-aaWidth:
alpha := clampF((outerRadiusF-dist)/aaWidth, 0, 1)
finalColor = blendColors(borderColor, borderColor, alpha)
} else if dist < innerRadius+aaWidth {
case dist < innerRadius+aaWidth:
alpha := clampF((dist-innerRadius)/aaWidth, 0, 1)
fx := float64(dx) / zoom
fy := float64(dy) / zoom
@@ -450,6 +457,9 @@ func drawMagnifier(
sy := cy + int(math.Round(fy))
sx = clamp(sx, 0, srcW-1)
sy = clamp(sy, 0, srcH-1)
if yInverted {
sy = srcH - 1 - sy
}
srcOff := sy*srcStride + sx*4
if srcOff+4 <= len(src) {
magColor := Color{B: src[srcOff+0], G: src[srcOff+1], R: src[srcOff+2], A: 255}
@@ -457,7 +467,7 @@ func drawMagnifier(
} else {
finalColor = borderColor
}
} else {
default:
finalColor = borderColor
}
@@ -468,6 +478,9 @@ func drawMagnifier(
sy := cy + int(math.Round(fy))
sx = clamp(sx, 0, srcW-1)
sy = clamp(sy, 0, srcH-1)
if yInverted {
sy = srcH - 1 - sy
}
srcOff := sy*srcStride + sx*4
if srcOff+4 <= len(src) {
finalColor = Color{B: src[srcOff+0], G: src[srcOff+1], R: src[srcOff+2], A: 255}
+5 -7
View File
@@ -435,7 +435,7 @@ func TestHyprlandConfigDeployment(t *testing.T) {
content, err := os.ReadFile(result.Path)
require.NoError(t, err)
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 = ")
})
@@ -471,7 +471,7 @@ general {
require.NoError(t, err)
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), "bind = $mod, T, exec, kitty")
assert.Contains(t, string(newContent), "bind = $mod, T, exec, $TERMINAL")
assert.NotContains(t, string(newContent), "monitor = eDP-2")
})
}
@@ -487,16 +487,14 @@ func TestNiriConfigStructure(t *testing.T) {
func TestHyprlandConfigStructure(t *testing.T) {
assert.Contains(t, HyprlandConfig, "# MONITOR CONFIG")
assert.Contains(t, HyprlandConfig, "# ENVIRONMENT VARS")
assert.Contains(t, HyprlandConfig, "# STARTUP APPS")
assert.Contains(t, HyprlandConfig, "# INPUT CONFIG")
assert.Contains(t, HyprlandConfig, "# KEYBINDINGS")
assert.Contains(t, HyprlandConfig, "{{POLKIT_AGENT_PATH}}")
assert.Contains(t, HyprlandConfig, "{{TERMINAL_COMMAND}}")
assert.Contains(t, HyprlandConfig, "exec-once = dms run")
assert.Contains(t, HyprlandConfig, "bind = $mod, T, exec,")
assert.Contains(t, HyprlandConfig, "bind = $mod, T, exec, $TERMINAL")
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) {
+123 -40
View File
@@ -7,20 +7,10 @@
# monitor = eDP-2, 2560x1600@239.998993, 2560x0, 1, vrr, 1
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
# ==================
exec-once = bash -c "wl-paste --watch cliphist store &"
exec-once = dms run
exec-once = {{POLKIT_AGENT_PATH}}
# ==================
@@ -100,36 +90,132 @@ misc {
# ==================
# 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)$
windowrulev2 = tile, class:^(pavucontrol)$
windowrulev2 = tile, class:^(nm-connection-editor)$
windowrule {
name = windowrule-2
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)$
windowrulev2 = float, class:^(zoom)$
windowrule {
name = windowrule-3
tile = on
match:class = ^(gnome-control-center)$
}
# DMS windows floating by default
windowrulev2 = float, class:^(org.quickshell)$
windowrulev2 = opacity 0.9 0.9, floating:0, focus:0
windowrule {
name = windowrule-4
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
@@ -137,7 +223,7 @@ layerrule = noanim, ^(quickshell)$
$mod = SUPER
# === Application Launchers ===
bind = $mod, T, exec, {{TERMINAL_COMMAND}}
bind = $mod, T, exec, $TERMINAL
bind = $mod, space, exec, dms ipc call spotlight toggle
bind = $mod, V, exec, dms ipc call clipboard toggle
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%
# === Screenshots ===
bind = , XF86Launch1, exec, grimblast copy area
bind = CTRL, XF86Launch1, exec, grimblast copy screen
bind = ALT, XF86Launch1, exec, grimblast copy active
bind = , Print, exec, grimblast copy area
bind = CTRL, Print, exec, grimblast copy screen
bind = ALT, Print, exec, grimblast copy active
bind = , Print, exec, dms screenshot
bind = CTRL, Print, exec, dms screenshot full
bind = ALT, Print, exec, dms screenshot window
# === System Controls ===
bind = $mod SHIFT, P, dpms, off
-6
View File
@@ -116,15 +116,9 @@ overview {
// See the binds section below for more spawn examples.
// This line starts waybar, a commonly used bar for Wayland compositors.
spawn-at-startup "bash" "-c" "wl-paste --watch cliphist store &"
spawn-at-startup "dms" "run"
spawn-at-startup "{{POLKIT_AGENT_PATH}}"
environment {
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 {
skip-at-startup
+81
View File
@@ -23,6 +23,17 @@ type ColorInfo struct {
B int `json:"b"`
}
type VariantColorValue struct {
Hex string `json:"hex"`
HexStripped string `json:"hex_stripped"`
}
type VariantColorInfo struct {
Dark VariantColorValue `json:"dark"`
Light VariantColorValue `json:"light"`
Default VariantColorValue `json:"default"`
}
type Palette struct {
Color0 ColorInfo `json:"color0"`
Color1 ColorInfo `json:"color1"`
@@ -42,6 +53,25 @@ type Palette struct {
Color15 ColorInfo `json:"color15"`
}
type VariantPalette struct {
Color0 VariantColorInfo `json:"color0"`
Color1 VariantColorInfo `json:"color1"`
Color2 VariantColorInfo `json:"color2"`
Color3 VariantColorInfo `json:"color3"`
Color4 VariantColorInfo `json:"color4"`
Color5 VariantColorInfo `json:"color5"`
Color6 VariantColorInfo `json:"color6"`
Color7 VariantColorInfo `json:"color7"`
Color8 VariantColorInfo `json:"color8"`
Color9 VariantColorInfo `json:"color9"`
Color10 VariantColorInfo `json:"color10"`
Color11 VariantColorInfo `json:"color11"`
Color12 VariantColorInfo `json:"color12"`
Color13 VariantColorInfo `json:"color13"`
Color14 VariantColorInfo `json:"color14"`
Color15 VariantColorInfo `json:"color15"`
}
func NewColorInfo(hex string) ColorInfo {
rgb := HexToRGB(hex)
stripped := hex
@@ -492,3 +522,54 @@ func GeneratePalette(primaryColor string, opts PaletteOptions) Palette {
return palette
}
type VariantOptions struct {
PrimaryDark string
PrimaryLight string
Background string
UseDPS bool
IsLightMode bool
}
func mergeColorInfo(dark, light ColorInfo, isLightMode bool) VariantColorInfo {
darkVal := VariantColorValue{Hex: dark.Hex, HexStripped: dark.HexStripped}
lightVal := VariantColorValue{Hex: light.Hex, HexStripped: light.HexStripped}
defaultVal := darkVal
if isLightMode {
defaultVal = lightVal
}
return VariantColorInfo{
Dark: darkVal,
Light: lightVal,
Default: defaultVal,
}
}
func GenerateVariantPalette(opts VariantOptions) VariantPalette {
darkOpts := PaletteOptions{IsLight: false, Background: opts.Background, UseDPS: opts.UseDPS}
lightOpts := PaletteOptions{IsLight: true, Background: opts.Background, UseDPS: opts.UseDPS}
dark := GeneratePalette(opts.PrimaryDark, darkOpts)
light := GeneratePalette(opts.PrimaryLight, lightOpts)
return VariantPalette{
Color0: mergeColorInfo(dark.Color0, light.Color0, opts.IsLightMode),
Color1: mergeColorInfo(dark.Color1, light.Color1, opts.IsLightMode),
Color2: mergeColorInfo(dark.Color2, light.Color2, opts.IsLightMode),
Color3: mergeColorInfo(dark.Color3, light.Color3, opts.IsLightMode),
Color4: mergeColorInfo(dark.Color4, light.Color4, opts.IsLightMode),
Color5: mergeColorInfo(dark.Color5, light.Color5, opts.IsLightMode),
Color6: mergeColorInfo(dark.Color6, light.Color6, opts.IsLightMode),
Color7: mergeColorInfo(dark.Color7, light.Color7, opts.IsLightMode),
Color8: mergeColorInfo(dark.Color8, light.Color8, opts.IsLightMode),
Color9: mergeColorInfo(dark.Color9, light.Color9, opts.IsLightMode),
Color10: mergeColorInfo(dark.Color10, light.Color10, opts.IsLightMode),
Color11: mergeColorInfo(dark.Color11, light.Color11, opts.IsLightMode),
Color12: mergeColorInfo(dark.Color12, light.Color12, opts.IsLightMode),
Color13: mergeColorInfo(dark.Color13, light.Color13, opts.IsLightMode),
Color14: mergeColorInfo(dark.Color14, light.Color14, opts.IsLightMode),
Color15: mergeColorInfo(dark.Color15, light.Color15, opts.IsLightMode),
}
}
+5
View File
@@ -11,6 +11,11 @@ func GenerateJSON(p Palette) string {
return string(marshalled)
}
func GenerateVariantJSON(p VariantPalette) string {
marshalled, _ := json.Marshal(p)
return string(marshalled)
}
func GenerateKittyTheme(p Palette) string {
var result strings.Builder
fmt.Fprintf(&result, "color0 %s\n", p.Color0.Hex)
+9
View File
@@ -357,6 +357,15 @@ func (a *ArchDistribution) InstallPackages(ctx context.Context, dependencies []d
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
progressChan <- InstallProgressMsg{
Phase: PhaseComplete,
+67 -20
View File
@@ -17,8 +17,10 @@ import (
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
)
const forceQuickshellGit = false
const forceDMSGit = false
const (
forceQuickshellGit = false
forceDMSGit = false
)
// BaseDistribution provides common functionality for all distributions
type BaseDistribution struct {
@@ -219,20 +221,6 @@ func (b *BaseDistribution) detectClipboardTools() []deps.Dependency {
return dependencies
}
func (b *BaseDistribution) detectHyprpicker() deps.Dependency {
status := deps.StatusMissing
if b.commandExists("hyprpicker") {
status = deps.StatusInstalled
}
return deps.Dependency{
Name: "hyprpicker",
Status: status,
Description: "Color picker for Wayland",
Required: true,
}
}
func (b *BaseDistribution) detectHyprlandTools() []deps.Dependency {
var dependencies []deps.Dependency
@@ -240,10 +228,7 @@ func (b *BaseDistribution) detectHyprlandTools() []deps.Dependency {
name string
description string
}{
{"grim", "Screenshot utility for Wayland"},
{"slurp", "Region selection utility for Wayland"},
{"hyprctl", "Hyprland control utility"},
{"grimblast", "Screenshot script for Hyprland"},
{"jq", "JSON processor"},
}
@@ -564,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
func (b *BaseDistribution) installDMSBinary(ctx context.Context, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
b.log("Installing/updating DMS binary...")
@@ -602,7 +649,7 @@ func (b *BaseDistribution) installDMSBinary(ctx context.Context, sudoPassword st
return fmt.Errorf("failed to get user home directory: %w", err)
}
tmpDir := filepath.Join(homeDir, ".cache", "dankinstall", "manual-builds")
if err := os.MkdirAll(tmpDir, 0755); err != nil {
if err := os.MkdirAll(tmpDir, 0o755); err != nil {
return fmt.Errorf("failed to create temp directory: %w", err)
}
defer os.RemoveAll(tmpDir)
+9
View File
@@ -333,6 +333,15 @@ func (d *DebianDistribution) InstallPackages(ctx context.Context, dependencies [
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{
Phase: PhaseComplete,
Progress: 1.0,
+9
View File
@@ -357,6 +357,15 @@ func (f *FedoraDistribution) InstallPackages(ctx context.Context, dependencies [
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
progressChan <- InstallProgressMsg{
Phase: PhaseComplete,
+9
View File
@@ -451,6 +451,15 @@ func (g *GentooDistribution) InstallPackages(ctx context.Context, dependencies [
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{
Phase: PhaseComplete,
Progress: 1.0,
-60
View File
@@ -62,10 +62,6 @@ func (m *ManualPackageInstaller) InstallManualPackages(ctx context.Context, pack
if err := m.installDgop(ctx, sudoPassword, progressChan); err != nil {
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":
if err := m.installNiri(ctx, sudoPassword, progressChan); err != nil {
return fmt.Errorf("failed to install niri: %w", err)
@@ -166,62 +162,6 @@ func (m *ManualPackageInstaller) installDgop(ctx context.Context, sudoPassword s
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 {
m.log("Installing niri from source...")
+9
View File
@@ -372,6 +372,15 @@ func (o *OpenSUSEDistribution) InstallPackages(ctx context.Context, dependencies
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
progressChan <- InstallProgressMsg{
Phase: PhaseComplete,
+9
View File
@@ -352,6 +352,15 @@ func (u *UbuntuDistribution) InstallPackages(ctx context.Context, dependencies [
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
progressChan <- InstallProgressMsg{
Phase: PhaseComplete,
+1 -1
View File
@@ -514,7 +514,7 @@ func (m Model) categorizeDependencies() map[string][]DependencyInfo {
switch dep.Name {
case "dms (DankMaterialShell)", "quickshell":
categories["Shell"] = append(categories["Shell"], dep)
case "hyprland", "grim", "slurp", "hyprctl", "grimblast":
case "hyprland", "hyprctl":
categories["Hyprland Components"] = append(categories["Hyprland Components"], dep)
case "niri":
categories["Niri Components"] = append(categories["Niri Components"], dep)
+21 -3
View File
@@ -371,6 +371,18 @@ func (n *NiriProvider) buildActionNode(action string) *document.Node {
node.SetName(parts[0])
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, "")
}
return node
@@ -379,7 +391,7 @@ func (n *NiriProvider) buildActionNode(action string) *document.Node {
func (n *NiriProvider) parseActionParts(action string) []string {
var parts []string
var current strings.Builder
var inQuote, escaped bool
var inQuote, escaped, wasQuoted bool
for _, r := range action {
switch {
@@ -389,17 +401,19 @@ func (n *NiriProvider) parseActionParts(action string) []string {
case r == '\\':
escaped = true
case r == '"':
wasQuoted = true
inQuote = !inQuote
case r == ' ' && !inQuote:
if current.Len() > 0 {
if current.Len() > 0 || wasQuoted {
parts = append(parts, current.String())
current.Reset()
wasQuoted = false
}
default:
current.WriteRune(r)
}
}
if current.Len() > 0 {
if current.Len() > 0 || wasQuoted {
parts = append(parts, current.String())
}
return parts
@@ -508,6 +522,10 @@ func (n *NiriProvider) writeBindNode(sb *strings.Builder, bind *overrideBind, in
sb.WriteString(" ")
n.writeArg(sb, arg.ValueString(), forceQuote)
}
if child.Properties.Exist() {
sb.WriteString(" ")
sb.WriteString(strings.TrimLeft(child.Properties.String(), " "))
}
}
sb.WriteString("; }\n")
}
@@ -265,6 +265,11 @@ func (p *NiriParser) parseKeybindNode(node *document.Node, _ string) *NiriKeyBin
for _, arg := range actionNode.Arguments {
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
@@ -602,8 +602,24 @@ func TestNiriParseActionWithProperties(t *testing.T) {
for _, kb := range result.Section.Keybinds {
switch kb.Action {
case "move-column-to-workspace":
if len(kb.Args) != 1 {
t.Errorf("move-column-to-workspace should have 1 arg, got %d", len(kb.Args))
if len(kb.Args) != 2 {
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":
if kb.Key != "Tab" {
+587
View File
@@ -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()
}
}
+139
View File
@@ -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
}
+2 -2
View File
@@ -44,7 +44,7 @@ func NewZdwlIpcManagerV2(ctx *client.Context) *ZdwlIpcManagerV2 {
// Indicates that the client will not the dwl_ipc_manager object anymore.
// Objects created through this instance are not affected.
func (i *ZdwlIpcManagerV2) Release() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -188,7 +188,7 @@ func NewZdwlIpcOutputV2(ctx *client.Context) *ZdwlIpcOutputV2 {
//
// Indicates to that the client no longer needs this dwl_ipc_output.
func (i *ZdwlIpcOutputV2) Release() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -174,7 +174,7 @@ func (i *ExtWorkspaceManagerV1) Stop() error {
}
func (i *ExtWorkspaceManagerV1) Destroy() error {
i.Context().Unregister(i)
i.MarkZombie()
return nil
}
@@ -385,7 +385,7 @@ func (i *ExtWorkspaceGroupHandleV1) CreateWorkspace(workspace string) error {
// use the workspace group object any more or after the removed event to finalize
// the destruction of the object.
func (i *ExtWorkspaceGroupHandleV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 1
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -655,7 +655,7 @@ func NewExtWorkspaceHandleV1(ctx *client.Context) *ExtWorkspaceHandleV1 {
// use the workspace object any more or after the remove event to finalize
// the destruction of the object.
func (i *ExtWorkspaceHandleV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -54,7 +54,7 @@ func NewZwpKeyboardShortcutsInhibitManagerV1(ctx *client.Context) *ZwpKeyboardSh
//
// Destroy the keyboard shortcuts inhibitor manager.
func (i *ZwpKeyboardShortcutsInhibitManagerV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -218,7 +218,7 @@ func NewZwpKeyboardShortcutsInhibitorV1(ctx *client.Context) *ZwpKeyboardShortcu
//
// Remove the keyboard shortcuts inhibitor from the associated wl_surface.
func (i *ZwpKeyboardShortcutsInhibitorV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -85,7 +85,7 @@ func (i *ZwlrGammaControlManagerV1) GetGammaControl(output *client.Output) (*Zwl
// All objects created by the manager will still remain valid, until their
// appropriate destroy request has been called.
func (i *ZwlrGammaControlManagerV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 1
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -169,7 +169,7 @@ func (i *ZwlrGammaControlV1) SetGamma(fd int) error {
// Destroys the gamma control object. If the object is still valid, this
// restores the original gamma tables.
func (i *ZwlrGammaControlV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 1
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -129,7 +129,7 @@ func (i *ZwlrLayerShellV1) GetLayerSurface(surface *client.Surface, output *clie
// object any more. Objects that have been created through this instance
// are not affected.
func (i *ZwlrLayerShellV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 1
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -509,7 +509,7 @@ func (i *ZwlrLayerSurfaceV1) AckConfigure(serial uint32) error {
//
// This request destroys the layer surface.
func (i *ZwlrLayerSurfaceV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 7
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -172,7 +172,7 @@ func (i *ZwlrOutputManagerV1) Stop() error {
}
func (i *ZwlrOutputManagerV1) Destroy() error {
i.Context().Unregister(i)
i.MarkZombie()
return nil
}
@@ -334,7 +334,7 @@ func NewZwlrOutputHeadV1(ctx *client.Context) *ZwlrOutputHeadV1 {
// This request indicates that the client will no longer use this head
// object.
func (i *ZwlrOutputHeadV1) Release() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -879,7 +879,7 @@ func NewZwlrOutputModeV1(ctx *client.Context) *ZwlrOutputModeV1 {
// This request indicates that the client will no longer use this mode
// object.
func (i *ZwlrOutputModeV1) Release() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -1132,7 +1132,7 @@ func (i *ZwlrOutputConfigurationV1) Test() error {
// This request also destroys wlr_output_configuration_head objects created
// via this object.
func (i *ZwlrOutputConfigurationV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 4
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -1415,7 +1415,7 @@ func (i *ZwlrOutputConfigurationHeadV1) SetAdaptiveSync(state uint32) error {
}
func (i *ZwlrOutputConfigurationHeadV1) Destroy() error {
i.Context().Unregister(i)
i.MarkZombie()
return nil
}
@@ -79,7 +79,7 @@ func (i *ZwlrOutputPowerManagerV1) GetOutputPower(output *client.Output) (*ZwlrO
// All objects created by the manager will still remain valid, until their
// appropriate destroy request has been called.
func (i *ZwlrOutputPowerManagerV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 1
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -143,7 +143,7 @@ func (i *ZwlrOutputPowerV1) SetMode(mode uint32) error {
//
// Destroys the output power management mode control object.
func (i *ZwlrOutputPowerV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 1
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -120,7 +120,7 @@ func (i *ZwlrScreencopyManagerV1) CaptureOutputRegion(overlayCursor int32, outpu
// All objects created by the manager will still remain valid, until their
// appropriate destroy request has been called.
func (i *ZwlrScreencopyManagerV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 2
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -219,7 +219,7 @@ func (i *ZwlrScreencopyFrameV1) Copy(buffer *client.Buffer) error {
//
// Destroys the frame. This request can be sent at any time by the client.
func (i *ZwlrScreencopyFrameV1) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 1
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -66,7 +66,7 @@ func NewWpViewporter(ctx *client.Context) *WpViewporter {
// protocol object anymore. This does not affect any other objects,
// wp_viewport objects included.
func (i *WpViewporter) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -267,7 +267,7 @@ func NewWpViewport(ctx *client.Context) *WpViewport {
// The associated wl_surface's crop and scale state is removed.
// The change is applied on the next wl_surface.commit.
func (i *WpViewport) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
+463 -9
View File
@@ -5,6 +5,10 @@ import (
"fmt"
"os"
"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
@@ -12,13 +16,76 @@ type Compositor int
const (
CompositorUnknown Compositor = iota
CompositorHyprland
CompositorSway
CompositorNiri
CompositorDWL
)
var detectedCompositor Compositor = -1
func DetectCompositor() Compositor {
if os.Getenv("HYPRLAND_INSTANCE_SIGNATURE") != "" {
return CompositorHyprland
if detectedCompositor >= 0 {
return detectedCompositor
}
return CompositorUnknown
hyprlandSig := os.Getenv("HYPRLAND_INSTANCE_SIGNATURE")
niriSocket := os.Getenv("NIRI_SOCKET")
swaySocket := os.Getenv("SWAYSOCK")
switch {
case niriSocket != "":
if _, err := os.Stat(niriSocket); err == nil {
detectedCompositor = CompositorNiri
return detectedCompositor
}
case swaySocket != "":
if _, err := os.Stat(swaySocket); err == nil {
detectedCompositor = CompositorSway
return detectedCompositor
}
case hyprlandSig != "":
detectedCompositor = CompositorHyprland
return detectedCompositor
}
if detectDWLProtocol() {
detectedCompositor = CompositorDWL
return detectedCompositor
}
detectedCompositor = CompositorUnknown
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() {
detectedCompositor = CompositorDWL
}
type WindowGeometry struct {
@@ -26,16 +93,18 @@ type WindowGeometry struct {
Y int32
Width int32
Height int32
Output string
Scale float64
}
func GetActiveWindow() (*WindowGeometry, error) {
compositor := DetectCompositor()
switch compositor {
switch DetectCompositor() {
case CompositorHyprland:
return getHyprlandActiveWindow()
case CompositorDWL:
return getDWLActiveWindow()
default:
return nil, fmt.Errorf("window capture requires Hyprland (other compositors not yet supported)")
return nil, fmt.Errorf("window capture requires Hyprland or DWL")
}
}
@@ -45,8 +114,7 @@ type hyprlandWindow struct {
}
func getHyprlandActiveWindow() (*WindowGeometry, error) {
cmd := exec.Command("hyprctl", "-j", "activewindow")
output, err := cmd.Output()
output, err := exec.Command("hyprctl", "-j", "activewindow").Output()
if err != nil {
return nil, fmt.Errorf("hyprctl activewindow: %w", err)
}
@@ -67,3 +135,389 @@ func getHyprlandActiveWindow() (*WindowGeometry, error) {
Height: win.Size[1],
}, nil
}
type hyprlandMonitor struct {
Name string `json:"name"`
X int32 `json:"x"`
Y int32 `json:"y"`
Width int32 `json:"width"`
Height int32 `json:"height"`
Scale float64 `json:"scale"`
Focused bool `json:"focused"`
}
func GetHyprlandMonitorScale(name string) float64 {
output, err := exec.Command("hyprctl", "-j", "monitors").Output()
if err != nil {
return 0
}
var monitors []hyprlandMonitor
if err := json.Unmarshal(output, &monitors); err != nil {
return 0
}
for _, m := range monitors {
if m.Name == name {
return m.Scale
}
}
return 0
}
func getHyprlandFocusedMonitor() string {
output, err := exec.Command("hyprctl", "-j", "monitors").Output()
if err != nil {
return ""
}
var monitors []hyprlandMonitor
if err := json.Unmarshal(output, &monitors); err != nil {
return ""
}
for _, m := range monitors {
if m.Focused {
return m.Name
}
}
return ""
}
func GetHyprlandMonitorGeometry(name string) (x, y, w, h int32, ok bool) {
output, err := exec.Command("hyprctl", "-j", "monitors").Output()
if err != nil {
return 0, 0, 0, 0, false
}
var monitors []hyprlandMonitor
if err := json.Unmarshal(output, &monitors); err != nil {
return 0, 0, 0, 0, false
}
for _, m := range monitors {
if m.Name == name {
logicalW := int32(float64(m.Width) / m.Scale)
logicalH := int32(float64(m.Height) / m.Scale)
return m.X, m.Y, logicalW, logicalH, true
}
}
return 0, 0, 0, 0, false
}
type swayWorkspace struct {
Output string `json:"output"`
Focused bool `json:"focused"`
}
func getSwayFocusedMonitor() string {
output, err := exec.Command("swaymsg", "-t", "get_workspaces").Output()
if err != nil {
return ""
}
var workspaces []swayWorkspace
if err := json.Unmarshal(output, &workspaces); err != nil {
return ""
}
for _, ws := range workspaces {
if ws.Focused {
return ws.Output
}
}
return ""
}
type niriWorkspace struct {
Output string `json:"output"`
IsFocused bool `json:"is_focused"`
}
func getNiriFocusedMonitor() string {
output, err := exec.Command("niri", "msg", "-j", "workspaces").Output()
if err != nil {
return ""
}
var workspaces []niriWorkspace
if err := json.Unmarshal(output, &workspaces); err != nil {
return ""
}
for _, ws := range workspaces {
if ws.IsFocused {
return ws.Output
}
}
return ""
}
var dwlActiveOutput string
func SetDWLActiveOutput(name string) {
dwlActiveOutput = name
}
func getDWLFocusedMonitor() string {
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 {
switch DetectCompositor() {
case CompositorHyprland:
return getHyprlandFocusedMonitor()
case CompositorSway:
return getSwayFocusedMonitor()
case CompositorNiri:
return getNiriFocusedMonitor()
case CompositorDWL:
return getDWLFocusedMonitor()
}
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")
}
+17 -13
View File
@@ -251,9 +251,10 @@ func (r *RegionSelector) handleGlobal(e client.RegistryGlobalEvent) {
if err := r.registry.Bind(e.Name, e.Interface, version, output); err == nil {
r.outputsMu.Lock()
r.outputs[e.Name] = &WaylandOutput{
wlOutput: output,
globalName: e.Name,
scale: 1,
wlOutput: output,
globalName: e.Name,
scale: 1,
fractionalScale: 1.0,
}
r.outputsMu.Unlock()
r.setupOutputHandlers(e.Name, output)
@@ -320,6 +321,7 @@ func (r *RegionSelector) setupOutputHandlers(name uint32, output *client.Output)
r.outputsMu.Lock()
if o, ok := r.outputs[name]; ok {
o.scale = e.Factor
o.fractionalScale = float64(e.Factor)
}
r.outputsMu.Unlock()
})
@@ -607,6 +609,10 @@ func (r *RegionSelector) captureForSurface(os *OutputSurface) {
os.screenFormat = pc.format
os.yInverted = pc.yInverted
if os.logicalW > 0 && os.screenBuf != nil {
os.output.fractionalScale = float64(os.screenBuf.Width) / float64(os.logicalW)
}
r.initRenderBuffer(os)
r.applyPreSelection(os)
r.redrawSurface(os)
@@ -713,19 +719,17 @@ func (r *RegionSelector) redrawSurface(os *OutputSurface) {
// Draw overlay (dimming + selection) into this slot
r.drawOverlay(os, slot.shm)
// Attach and commit (viewport only needs to be set once, but it's cheap)
scale := os.output.scale
if scale <= 0 {
scale = 1
}
if os.viewport != nil {
srcW := float64(slot.shm.Width) / float64(scale)
srcH := float64(slot.shm.Height) / float64(scale)
_ = os.viewport.SetSource(0, 0, srcW, srcH)
_ = os.wlSurface.SetBufferScale(1)
_ = os.viewport.SetSource(0, 0, float64(slot.shm.Width), float64(slot.shm.Height))
_ = os.viewport.SetDestination(int32(os.logicalW), int32(os.logicalH))
} else {
bufferScale := os.output.scale
if bufferScale <= 0 {
bufferScale = 1
}
_ = os.wlSurface.SetBufferScale(bufferScale)
}
_ = os.wlSurface.SetBufferScale(scale)
_ = os.wlSurface.Attach(slot.wlBuf, 0, 0)
_ = os.wlSurface.Damage(0, 0, int32(os.logicalW), int32(os.logicalH))
+12 -5
View File
@@ -223,16 +223,23 @@ func (r *RegionSelector) finishSelection() {
dstData := cropped.Data()
for y := 0; y < h; y++ {
srcY := by1 + y
if srcY >= srcBuf.Height {
break
if os.yInverted {
srcY = srcBuf.Height - 1 - (by1 + y)
}
if srcY < 0 || srcY >= srcBuf.Height {
continue
}
dstY := y
if os.yInverted {
dstY = h - 1 - y
}
for x := 0; x < w; x++ {
srcX := bx1 + x
if srcX >= srcBuf.Width {
break
if srcX < 0 || srcX >= srcBuf.Width {
continue
}
si := srcY*srcBuf.Stride + srcX*4
di := y*cropped.Stride + x*4
di := dstY*cropped.Stride + x*4
if si+3 < len(srcData) && di+3 < len(dstData) {
dstData[di+0] = srcData[si+0]
dstData[di+1] = srcData[si+1]
+393 -67
View File
@@ -11,14 +11,15 @@ import (
)
type WaylandOutput struct {
wlOutput *client.Output
globalName uint32
name string
x, y int32
width int32
height int32
scale int32
transform int32
wlOutput *client.Output
globalName uint32
name string
x, y int32
width int32
height int32
scale int32
fractionalScale float64
transform int32
}
type CaptureResult struct {
@@ -134,12 +135,138 @@ func (s *Screenshoter) captureWindow() (*CaptureResult, error) {
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 {
return nil, fmt.Errorf("could not find output for window")
}
return s.captureRegionOnOutput(output, region)
switch DetectCompositor() {
case CompositorHyprland:
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
}
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) {
@@ -181,33 +308,8 @@ func (s *Screenshoter) captureOutput(name string) (*CaptureResult, error) {
func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
s.outputsMu.Lock()
outputs := make([]*WaylandOutput, 0, len(s.outputs))
var minX, minY, maxX, maxY int32
first := true
for _, o := range s.outputs {
outputs = append(outputs, o)
right := o.x + o.width
bottom := o.y + o.height
if first {
minX, minY = o.x, o.y
maxX, maxY = right, bottom
first = false
continue
}
if o.x < minX {
minX = o.x
}
if o.y < minY {
minY = o.y
}
if right > maxX {
maxX = right
}
if bottom > maxY {
maxY = bottom
}
}
s.outputsMu.Unlock()
@@ -219,18 +321,18 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
return s.captureWholeOutput(outputs[0])
}
totalW := maxX - minX
totalH := maxY - minY
compositeStride := int(totalW) * 4
composite, err := CreateShmBuffer(int(totalW), int(totalH), compositeStride)
if err != nil {
return nil, fmt.Errorf("create composite buffer: %w", err)
// Capture all outputs first to get actual buffer sizes
type capturedOutput struct {
output *WaylandOutput
result *CaptureResult
physX int
physY int
}
captured := make([]capturedOutput, 0, len(outputs))
composite.Clear()
var minX, minY, maxX, maxY int
first := true
var format uint32
for _, output := range outputs {
result, err := s.captureWholeOutput(output)
if err != nil {
@@ -238,16 +340,88 @@ func (s *Screenshoter) captureAllScreens() (*CaptureResult, error) {
continue
}
if format == 0 {
format = result.Format
outX, outY := output.x, output.y
scale := float64(output.scale)
if DetectCompositor() == CompositorHyprland {
if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok {
outX, outY = hx, hy
}
if s := GetHyprlandMonitorScale(output.name); s > 0 {
scale = s
}
}
s.blitBuffer(composite, result.Buffer, int(output.x-minX), int(output.y-minY), result.YInverted)
result.Buffer.Close()
if scale <= 0 {
scale = 1.0
}
physX := int(float64(outX) * scale)
physY := int(float64(outY) * scale)
captured = append(captured, capturedOutput{
output: output,
result: result,
physX: physX,
physY: physY,
})
right := physX + result.Buffer.Width
bottom := physY + result.Buffer.Height
if first {
minX, minY = physX, physY
maxX, maxY = right, bottom
first = false
continue
}
if physX < minX {
minX = physX
}
if physY < minY {
minY = physY
}
if right > maxX {
maxX = right
}
if bottom > maxY {
maxY = bottom
}
}
if len(captured) == 0 {
return nil, fmt.Errorf("failed to capture any outputs")
}
if len(captured) == 1 {
return captured[0].result, nil
}
totalW := maxX - minX
totalH := maxY - minY
compositeStride := totalW * 4
composite, err := CreateShmBuffer(totalW, totalH, compositeStride)
if err != nil {
for _, c := range captured {
c.result.Buffer.Close()
}
return nil, fmt.Errorf("create composite buffer: %w", err)
}
composite.Clear()
var format uint32
for _, c := range captured {
if format == 0 {
format = c.result.Format
}
s.blitBuffer(composite, c.result.Buffer, c.physX-minX, c.physY-minY, c.result.YInverted)
c.result.Buffer.Close()
}
return &CaptureResult{
Buffer: composite,
Region: Region{X: minX, Y: minY, Width: totalW, Height: totalH},
Region: Region{X: int32(minX), Y: int32(minY), Width: int32(totalW), Height: int32(totalH)},
Format: format,
}, nil
}
@@ -311,21 +485,131 @@ func (s *Screenshoter) captureWholeOutput(output *WaylandOutput) (*CaptureResult
})
}
func (s *Screenshoter) captureAndCrop(output *WaylandOutput, region Region) (*CaptureResult, error) {
result, err := s.captureWholeOutput(output)
if err != nil {
return nil, err
}
outX, outY := output.x, output.y
scale := float64(output.scale)
if hx, hy, _, _, ok := GetHyprlandMonitorGeometry(output.name); ok {
outX, outY = hx, hy
}
if s := GetHyprlandMonitorScale(output.name); s > 0 {
scale = s
}
if scale <= 0 {
scale = 1.0
}
localX := int(float64(region.X-outX) * scale)
localY := int(float64(region.Y-outY) * scale)
w := int(float64(region.Width) * scale)
h := int(float64(region.Height) * scale)
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) captureRegionOnOutput(output *WaylandOutput, region Region) (*CaptureResult, error) {
localX := region.X - output.x
localY := region.Y - output.y
scale := output.fractionalScale
if scale <= 0 && DetectCompositor() == CompositorHyprland {
scale = GetHyprlandMonitorScale(output.name)
}
if scale <= 0 {
scale = float64(output.scale)
}
if scale <= 0 {
scale = 1.0
}
localX := int32(float64(region.X-output.x) * scale)
localY := int32(float64(region.Y-output.y) * scale)
w := int32(float64(region.Width) * 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)
if s.config.IncludeCursor {
cursor = 1
}
frame, err := s.screencopy.CaptureOutputRegion(
cursor,
output.wlOutput,
localX, localY,
region.Width, region.Height,
)
frame, err := s.screencopy.CaptureOutputRegion(cursor, output.wlOutput, localX, localY, w, h)
if err != nil {
return nil, fmt.Errorf("capture region: %w", err)
}
@@ -335,6 +619,8 @@ func (s *Screenshoter) captureRegionOnOutput(output *WaylandOutput, region Regio
func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1, region Region) (*CaptureResult, error) {
var buf *ShmBuffer
var pool *client.ShmPool
var wlBuf *client.Buffer
var format PixelFormat
var yInverted bool
ready := false
@@ -360,15 +646,17 @@ func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1,
return
}
pool, err := s.shm.CreatePool(buf.Fd(), int32(buf.Size()))
var err error
pool, err = s.shm.CreatePool(buf.Fd(), int32(buf.Size()))
if err != nil {
log.Error("failed to create pool", "err", err)
return
}
wlBuf, err := pool.CreateBuffer(0, int32(buf.Width), int32(buf.Height), int32(buf.Stride), uint32(format))
wlBuf, err = pool.CreateBuffer(0, int32(buf.Width), int32(buf.Height), int32(buf.Stride), uint32(format))
if err != nil {
pool.Destroy()
pool = nil
log.Error("failed to create wl_buffer", "err", err)
return
}
@@ -376,8 +664,6 @@ func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1,
if err := frame.Copy(wlBuf); err != nil {
log.Error("failed to copy frame", "err", err)
}
pool.Destroy()
})
frame.SetReadyHandler(func(e wlr_screencopy.ZwlrScreencopyFrameV1ReadyEvent) {
@@ -396,6 +682,12 @@ func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1,
}
frame.Destroy()
if wlBuf != nil {
wlBuf.Destroy()
}
if pool != nil {
pool.Destroy()
}
if failed {
if buf != nil {
@@ -412,6 +704,17 @@ func (s *Screenshoter) processFrame(frame *wlr_screencopy.ZwlrScreencopyFrameV1,
}, 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 {
s.outputsMu.Lock()
defer s.outputsMu.Unlock()
@@ -420,14 +723,26 @@ func (s *Screenshoter) findOutputForRegion(region Region) *WaylandOutput {
cy := region.Y + region.Height/2
for _, o := range s.outputs {
if cx >= o.x && cx < o.x+o.width && cy >= o.y && cy < o.y+o.height {
x, y, w, h := o.x, o.y, o.width, o.height
if DetectCompositor() == CompositorHyprland {
if hx, hy, hw, hh, ok := GetHyprlandMonitorGeometry(o.name); ok {
x, y, w, h = hx, hy, hw, hh
}
}
if cx >= x && cx < x+w && cy >= y && cy < y+h {
return o
}
}
for _, o := range s.outputs {
if region.X >= o.x && region.X < o.x+o.width &&
region.Y >= o.y && region.Y < o.y+o.height {
x, y, w, h := o.x, o.y, o.width, o.height
if DetectCompositor() == CompositorHyprland {
if hx, hy, hw, hh, ok := GetHyprlandMonitorGeometry(o.name); ok {
x, y, w, h = hx, hy, hw, hh
}
}
if region.X >= x && region.X < x+w &&
region.Y >= y && region.Y < y+h {
return o
}
}
@@ -436,6 +751,15 @@ func (s *Screenshoter) findOutputForRegion(region Region) *WaylandOutput {
}
func (s *Screenshoter) findFocusedOutput() *WaylandOutput {
if mon := GetFocusedMonitor(); mon != "" {
s.outputsMu.Lock()
defer s.outputsMu.Unlock()
for _, o := range s.outputs {
if o.name == mon {
return o
}
}
}
s.outputsMu.Lock()
defer s.outputsMu.Unlock()
for _, o := range s.outputs {
@@ -501,9 +825,10 @@ func (s *Screenshoter) handleGlobal(e client.RegistryGlobalEvent) {
if err := s.registry.Bind(e.Name, e.Interface, version, output); err == nil {
s.outputsMu.Lock()
s.outputs[e.Name] = &WaylandOutput{
wlOutput: output,
globalName: e.Name,
scale: 1,
wlOutput: output,
globalName: e.Name,
scale: 1,
fractionalScale: 1.0,
}
s.outputsMu.Unlock()
s.setupOutputHandlers(e.Name, output)
@@ -546,6 +871,7 @@ func (s *Screenshoter) setupOutputHandlers(name uint32, output *client.Output) {
s.outputsMu.Lock()
if o, ok := s.outputs[name]; ok {
o.scale = e.Factor
o.fractionalScale = float64(e.Factor)
}
s.outputsMu.Unlock()
})
+91
View File
@@ -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(),
})
}
+4
View File
@@ -215,6 +215,10 @@ func RouteRequest(conn net.Conn, req models.Request) {
models.Respond(conn, req.ID, info)
case "subscribe":
handleSubscribe(conn, req)
case "matugen.queue":
handleMatugenQueue(conn, req)
case "matugen.status":
handleMatugenStatus(conn, req)
default:
models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method))
}
+28 -27
View File
@@ -113,7 +113,7 @@ func (i *Display) GetRegistry() (*Registry, error) {
}
func (i *Display) Destroy() error {
i.Context().Unregister(i)
i.MarkZombie()
return nil
}
@@ -224,15 +224,16 @@ func (i *Display) Dispatch(opcode uint32, fd int, data []byte) {
i.errorHandler(e)
case 1:
if i.deleteIdHandler == nil {
return
}
var e DisplayDeleteIdEvent
l := 0
e.Id = Uint32(data[l : l+4])
l += 4
i.deleteIdHandler(e)
i.Context().DeleteID(e.Id)
if i.deleteIdHandler != nil {
i.deleteIdHandler(e)
}
}
}
@@ -326,7 +327,7 @@ func (i *Registry) Bind(name uint32, iface string, version uint32, id Proxy) err
}
func (i *Registry) Destroy() error {
i.Context().Unregister(i)
i.MarkZombie()
return nil
}
@@ -433,7 +434,7 @@ func NewCallback(ctx *Context) *Callback {
}
func (i *Callback) Destroy() error {
i.Context().Unregister(i)
i.MarkZombie()
return nil
}
@@ -529,7 +530,7 @@ func (i *Compositor) CreateRegion() (*Region, error) {
}
func (i *Compositor) Destroy() error {
i.Context().Unregister(i)
i.MarkZombie()
return nil
}
@@ -619,7 +620,7 @@ func (i *ShmPool) CreateBuffer(offset, width, height, stride int32, format uint3
// buffers that have been created from this pool
// are gone.
func (i *ShmPool) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 1
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -735,7 +736,7 @@ func (i *Shm) CreatePool(fd int, size int32) (*ShmPool, error) {
//
// Objects created via this interface remain unaffected.
func (i *Shm) Release() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 1
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -1642,7 +1643,7 @@ func NewBuffer(ctx *Context) *Buffer {
//
// For possible side-effects to a surface, see wl_surface.attach.
func (i *Buffer) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -1803,7 +1804,7 @@ func (i *DataOffer) Receive(mimeType string, fd int) error {
//
// Destroy the data offer.
func (i *DataOffer) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 2
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -2120,7 +2121,7 @@ func (i *DataSource) Offer(mimeType string) error {
//
// Destroy the data source.
func (i *DataSource) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 1
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -2540,7 +2541,7 @@ func (i *DataDevice) SetSelection(source *DataSource, serial uint32) error {
//
// This request destroys the data device.
func (i *DataDevice) Release() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 2
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -2859,7 +2860,7 @@ func (i *DataDeviceManager) GetDataDevice(seat *Seat) (*DataDevice, error) {
}
func (i *DataDeviceManager) Destroy() error {
i.Context().Unregister(i)
i.MarkZombie()
return nil
}
@@ -3000,7 +3001,7 @@ func (i *Shell) GetShellSurface(surface *Surface) (*ShellSurface, error) {
}
func (i *Shell) Destroy() error {
i.Context().Unregister(i)
i.MarkZombie()
return nil
}
@@ -3421,7 +3422,7 @@ func (i *ShellSurface) SetClass(class string) error {
}
func (i *ShellSurface) Destroy() error {
i.Context().Unregister(i)
i.MarkZombie()
return nil
}
@@ -3798,7 +3799,7 @@ func NewSurface(ctx *Context) *Surface {
//
// Deletes the surface and invalidates its object ID.
func (i *Surface) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -4618,7 +4619,7 @@ func (i *Seat) GetTouch() (*Touch, error) {
// Using this request a client can tell the server that it is not going to
// use the seat object anymore.
func (i *Seat) Release() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 3
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -4920,7 +4921,7 @@ func (i *Pointer) SetCursor(serial uint32, surface *Surface, hotspotX, hotspotY
// This request destroys the pointer proxy object, so clients must not call
// wl_pointer_destroy() after using this request.
func (i *Pointer) Release() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 1
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -5685,7 +5686,7 @@ func NewKeyboard(ctx *Context) *Keyboard {
// Release : release the keyboard object
func (i *Keyboard) Release() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -6091,7 +6092,7 @@ func NewTouch(ctx *Context) *Touch {
// Release : release the touch object
func (i *Touch) Release() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -6406,7 +6407,7 @@ func NewOutput(ctx *Context) *Output {
// Using this request a client can tell the server that it is not going to
// use the output object anymore.
func (i *Output) Release() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -6923,7 +6924,7 @@ func NewRegion(ctx *Context) *Region {
//
// Destroy the region. This will invalidate the object ID.
func (i *Region) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -7057,7 +7058,7 @@ func NewSubcompositor(ctx *Context) *Subcompositor {
// protocol object anymore. This does not affect any other
// objects, wl_subsurface objects included.
func (i *Subcompositor) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -7280,7 +7281,7 @@ func NewSubsurface(ctx *Context) *Subsurface {
// wl_subcompositor.get_subsurface request. The wl_surface's association
// to the parent is deleted. The wl_surface is unmapped immediately.
func (i *Subsurface) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
@@ -7499,7 +7500,7 @@ func NewFixes(ctx *Context) *Fixes {
// Destroy : destroys this object
func (i *Fixes) Destroy() error {
defer i.Context().Unregister(i)
defer i.MarkZombie()
const opcode = 0
const _reqBufLen = 8
var _reqBuf [_reqBufLen]byte
+15 -2
View File
@@ -1,5 +1,7 @@
package client
import "sync/atomic"
type Dispatcher interface {
Dispatch(opcode uint32, fd int, data []byte)
}
@@ -9,11 +11,14 @@ type Proxy interface {
SetContext(ctx *Context)
ID() uint32
SetID(id uint32)
IsZombie() bool
MarkZombie()
}
type BaseProxy struct {
ctx *Context
id uint32
ctx *Context
id uint32
zombie atomic.Bool
}
func (p *BaseProxy) ID() uint32 {
@@ -31,3 +36,11 @@ func (p *BaseProxy) Context() *Context {
func (p *BaseProxy) SetContext(ctx *Context) {
p.ctx = ctx
}
func (p *BaseProxy) IsZombie() bool {
return p.zombie.Load()
}
func (p *BaseProxy) MarkZombie() {
p.zombie.Store(true)
}
@@ -32,6 +32,10 @@ func (ctx *Context) Unregister(p Proxy) {
ctx.objects.Delete(p.ID())
}
func (ctx *Context) DeleteID(id uint32) {
ctx.objects.Delete(id)
}
func (ctx *Context) GetProxy(id uint32) Proxy {
if val, ok := ctx.objects.Load(id); ok {
return val
@@ -72,7 +76,11 @@ func (ctx *Context) GetDispatch() func() error {
return func() error {
proxy, ok := ctx.objects.Load(senderID)
if !ok {
return fmt.Errorf("%w (senderID=%d)", ErrDispatchSenderNotFound, senderID)
return nil // Proxy already deleted via delete_id, silently ignore
}
if proxy.IsZombie() {
return nil // Zombie proxy, discard late events
}
sender, ok := proxy.(Dispatcher)
+3 -1
View File
@@ -61,7 +61,9 @@ in {
'';
};
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.path = lib.mkOption {
+2 -2
View File
@@ -4,13 +4,13 @@
lib,
dmsPkgs,
...
}: let
} @ args: let
cfg = config.programs.dankMaterialShell;
jsonFormat = pkgs.formats.json {};
common = import ./common.nix {inherit config pkgs lib dmsPkgs;};
in {
imports = [
./options.nix
(import ./options.nix args)
(lib.mkRemovedOptionModule ["programs" "dankMaterialShell" "enableNightMode"] "Night mode is now always available.")
(lib.mkRenamedOptionModule ["programs" "dankMaterialShell" "enableSystemd"] ["programs" "dankMaterialShell" "systemd" "enable"])
];
+2 -2
View File
@@ -4,12 +4,12 @@
lib,
dmsPkgs,
...
}: let
} @ args: let
cfg = config.programs.dankMaterialShell;
common = import ./common.nix {inherit config pkgs lib dmsPkgs;};
in {
imports = [
./options.nix
(import ./options.nix args)
];
config = lib.mkIf cfg.enable
+4 -2
View File
@@ -1,6 +1,6 @@
{
pkgs,
lib,
dmsPkgs,
...
}: let
inherit (lib) types;
@@ -62,7 +62,9 @@ in {
description = "Add needed dependencies to have system sound support";
};
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
View File
@@ -7,11 +7,11 @@
]
},
"locked": {
"lastModified": 1762435535,
"narHash": "sha256-QhzRn7pYN35IFpKjjxJAj3GPJECuC+VLhoGem3ezycc=",
"lastModified": 1762835999,
"narHash": "sha256-UykYGrGFOFTmDpKTLNxj1wvd1gbDG4TkqLNSbV0TYwk=",
"owner": "AvengeMedia",
"repo": "dgop",
"rev": "6cf638dde818f9f8a2e26d0243179c43cb3458d7",
"rev": "799301991cd5dcea9b64245f9d500dcc76615653",
"type": "github"
},
"original": {
@@ -22,11 +22,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1762363567,
"narHash": "sha256-YRqMDEtSMbitIMj+JLpheSz0pwEr0Rmy5mC7myl17xs=",
"lastModified": 1764950072,
"narHash": "sha256-BmPWzogsG2GsXZtlT+MTcAWeDK5hkbGRZTeZNW42fwA=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ae814fd3904b621d8ab97418f1d0f2eb0d3716f4",
"rev": "f61125a668a320878494449750330ca58b78c557",
"type": "github"
},
"original": {
@@ -36,10 +36,32 @@
"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": {
"inputs": {
"dgop": "dgop",
"nixpkgs": "nixpkgs"
"nixpkgs": "nixpkgs",
"quickshell": "quickshell"
}
}
},
+90 -49
View File
@@ -7,12 +7,17 @@
url = "github:AvengeMedia/dgop";
inputs.nixpkgs.follows = "nixpkgs";
};
quickshell = {
url = "git+https://git.outfoxxed.me/quickshell/quickshell?rev=26531fc46ef17e9365b03770edd3fb9206fcb460";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = {
self,
nixpkgs,
dgop,
quickshell,
...
}: let
forEachSystem = fn:
@@ -22,6 +27,7 @@
buildDmsPkgs = pkgs: {
dms-shell = self.packages.${pkgs.stdenv.hostPlatform.system}.default;
dgop = dgop.packages.${pkgs.stdenv.hostPlatform.system}.dgop;
quickshell = quickshell.packages.${pkgs.stdenv.hostPlatform.system}.default;
};
mkModuleWithDmsPkgs = path: args @ {pkgs, ...}: {
imports = [
@@ -46,67 +52,69 @@
+ "_"
+ (self.shortRev or "dirty");
in {
dms-shell = pkgs.buildGoModule (let
rootSrc = ./.;
in {
inherit version;
pname = "dms-shell";
src = ./core;
vendorHash = "sha256-2PCqiW4frxME8IlmwWH5ktznhd/G1bah5Ae4dp0HPTQ=";
dms-shell = pkgs.buildGoModule (
let
rootSrc = ./.;
in {
inherit version;
pname = "dms-shell";
src = ./core;
vendorHash = "sha256-2PCqiW4frxME8IlmwWH5ktznhd/G1bah5Ae4dp0HPTQ=";
subPackages = ["cmd/dms"];
subPackages = ["cmd/dms"];
ldflags = [
"-s"
"-w"
"-X main.Version=${version}"
];
ldflags = [
"-s"
"-w"
"-X main.Version=${version}"
];
nativeBuildInputs = [
pkgs.installShellFiles
pkgs.makeWrapper
];
nativeBuildInputs = with pkgs; [
installShellFiles
makeWrapper
];
postInstall = ''
mkdir -p $out/share/quickshell/dms
cp -r ${rootSrc}/quickshell/. $out/share/quickshell/dms/
postInstall = ''
mkdir -p $out/share/quickshell/dms
cp -r ${rootSrc}/quickshell/. $out/share/quickshell/dms/
chmod u+w $out/share/quickshell/dms/VERSION
echo "${version}" > $out/share/quickshell/dms/VERSION
chmod u+w $out/share/quickshell/dms/VERSION
echo "${version}" > $out/share/quickshell/dms/VERSION
# Install desktop file and icon
install -D ${rootSrc}/assets/dms-open.desktop \
$out/share/applications/dms-open.desktop
install -D ${rootSrc}/core/assets/danklogo.svg \
$out/share/hicolor/scalable/apps/danklogo.svg
# Install desktop file and icon
install -D ${rootSrc}/assets/dms-open.desktop \
$out/share/applications/dms-open.desktop
install -D ${rootSrc}/core/assets/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 \
$out/lib/systemd/user/dms.service
install -Dm644 ${rootSrc}/assets/systemd/dms.service \
$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/pkill ${pkgs.procps}/bin/pkill
substituteInPlace $out/lib/systemd/user/dms.service \
--replace-fail /usr/bin/dms $out/bin/dms \
--replace-fail /usr/bin/pkill ${pkgs.procps}/bin/pkill
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
substituteInPlace $out/share/quickshell/dms/Modules/Greetd/assets/dms-greeter \
--replace-fail /bin/bash ${pkgs.bashInteractive}/bin/bash
installShellCompletion --cmd dms \
--bash <($out/bin/dms completion bash) \
--fish <($out/bin/dms completion fish) \
--zsh <($out/bin/dms completion zsh)
'';
installShellCompletion --cmd dms \
--bash <($out/bin/dms completion bash) \
--fish <($out/bin/dms completion fish) \
--zsh <($out/bin/dms completion zsh)
'';
meta = {
description = "Desktop shell for wayland compositors built with Quickshell & GO";
homepage = "https://danklinux.com";
changelog = "https://github.com/AvengeMedia/DankMaterialShell/releases/tag/v${version}";
license = pkgs.lib.licenses.mit;
mainProgram = "dms";
platforms = pkgs.lib.platforms.linux;
};
});
meta = {
description = "Desktop shell for wayland compositors built with Quickshell & GO";
homepage = "https://danklinux.com";
changelog = "https://github.com/AvengeMedia/DankMaterialShell/releases/tag/v${version}";
license = pkgs.lib.licenses.mit;
mainProgram = "dms";
platforms = pkgs.lib.platforms.linux;
};
}
);
default = self.packages.${system}.dms-shell;
}
@@ -119,5 +127,38 @@
nixosModules.dankMaterialShell = mkModuleWithDmsPkgs ./distro/nix/nixos.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);
};
}
);
};
}
+58 -29
View File
@@ -197,14 +197,26 @@ const ACTION_ARGS = {
{ 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": {
args: [{ name: "opts", type: "screenshot", label: "Options" }]
args: [{ name: "show-pointer", type: "bool", label: "Show pointer" }]
},
"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": {
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)
return "";
const dmsAct = findDmsAction(action);
var dmsAct = findDmsAction(action);
if (dmsAct)
return dmsAct.label;
const compAct = findCompositorAction(action);
var base = action.split(" ")[0];
var compAct = findCompositorAction(base);
if (compAct)
return compAct.label;
@@ -337,7 +350,8 @@ function isValidAction(action) {
function isKnownCompositorAction(action) {
if (!action)
return false;
return findCompositorAction(action) !== null;
var base = action.split(" ")[0];
return findCompositorAction(base) !== null;
}
function buildSpawnAction(command, args) {
@@ -404,10 +418,10 @@ function parseCompositorActionArgs(action) {
if (!ACTION_ARGS[base])
return { base: action, args: {} };
var argConfig = ACTION_ARGS[base];
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++) {
if (argParts[i] === "focus=true" || argParts[i] === "focus=false") {
args.focus = argParts[i] === "focus=true";
@@ -415,14 +429,24 @@ function parseCompositorActionArgs(action) {
args.index = argParts[i];
}
}
} else if (base.startsWith("screenshot")) {
args.opts = {};
for (var j = 0; j < argParts.length; j += 2) {
if (j + 1 < argParts.length)
args.opts[argParts[j]] = argParts[j + 1];
break;
case "move-column-to-workspace-down":
case "move-column-to-workspace-up":
for (var k = 0; k < argParts.length; k++) {
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 };
@@ -437,24 +461,29 @@ function buildCompositorAction(base, args) {
if (!args || Object.keys(args).length === 0)
return base;
if (base === "move-column-to-workspace") {
switch (base) {
case "move-column-to-workspace":
if (args.index)
parts.push(args.index);
if (args.focus === true)
parts.push("focus=true");
else if (args.focus === false)
if (args.focus === false)
parts.push("focus=false");
} else if (base.startsWith("screenshot") && args.opts) {
for (var key in args.opts) {
if (args.opts[key] !== undefined && args.opts[key] !== "") {
parts.push(key);
parts.push(args.opts[key]);
}
break;
case "move-column-to-workspace-down":
case "move-column-to-workspace-up":
if (args.focus === false)
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(" ");
+1 -1
View File
@@ -295,7 +295,7 @@ Singleton {
property bool lockScreenShowPowerActions: true
property bool enableFprint: false
property int maxFprintTries: 3
property int maxFprintTries: 15
property bool fprintdAvailable: false
property string lockScreenActiveMonitor: "all"
property string lockScreenInactiveColor: "#000000"
+27 -10
View File
@@ -820,18 +820,35 @@ Singleton {
"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");
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;
}
+1 -1
View File
@@ -194,7 +194,7 @@ var SPEC = {
lockScreenShowPowerActions: { def: true },
enableFprint: { def: false },
maxFprintTries: { def: 3 },
maxFprintTries: { def: 15 },
fprintdAvailable: { def: false, persist: false },
lockScreenActiveMonitor: { def: "all" },
lockScreenInactiveColor: { def: "#000000" },
@@ -56,6 +56,8 @@ Row {
}
DankSlider {
id: volumeSlider
readonly property real actualVolumePercent: defaultSink ? Math.round(defaultSink.audio.volume * 100) : 0
anchors.verticalCenter: parent.verticalCenter
@@ -63,7 +65,6 @@ Row {
enabled: defaultSink !== null
minimum: 0
maximum: 100
value: defaultSink ? Math.min(100, Math.round(defaultSink.audio.volume * 100)) : 0
showValue: true
unit: "%"
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
}
}
+7 -3
View File
@@ -23,6 +23,7 @@ Loader {
property bool isRightBarEdge: false
property bool isTopBarEdge: false
property bool isBottomBarEdge: false
property string _registeredScreenName: ""
asynchronous: false
@@ -198,13 +199,16 @@ Loader {
if (!hasPopout)
return;
BarWidgetService.registerWidget(widgetId, parentScreen.name, item);
_registeredScreenName = parentScreen.name;
BarWidgetService.registerWidget(widgetId, _registeredScreenName, item);
}
function unregisterWidget() {
if (!widgetId || !parentScreen?.name)
if (!widgetId || !_registeredScreenName)
return;
BarWidgetService.unregisterWidget(widgetId, parentScreen.name);
BarWidgetService.unregisterWidget(widgetId, _registeredScreenName);
_registeredScreenName = "";
}
function getWidgetComponent(widgetId, components) {
@@ -17,7 +17,7 @@ BasePill {
DankIcon {
anchors.centerIn: parent
name: "content_paste"
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: Theme.widgetIconColor
}
}
@@ -173,7 +173,7 @@ BasePill {
DankIcon {
name: root.getNetworkIconName()
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: root.getNetworkIconColor()
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showNetworkIcon && NetworkService.networkAvailable
@@ -181,7 +181,7 @@ BasePill {
DankIcon {
name: "vpn_lock"
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: NetworkService.vpnConnected ? Theme.primary : Theme.outlineButton
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
@@ -189,7 +189,7 @@ BasePill {
DankIcon {
name: "bluetooth"
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
@@ -205,7 +205,7 @@ BasePill {
DankIcon {
id: audioIconV
name: root.getVolumeIconName()
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: Theme.widgetIconColor
anchors.centerIn: parent
}
@@ -230,7 +230,7 @@ BasePill {
DankIcon {
id: micIconV
name: root.getMicIconName()
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: root.getMicIconColor()
anchors.centerIn: parent
}
@@ -255,7 +255,7 @@ BasePill {
DankIcon {
id: brightnessIconV
name: root.getBrightnessIconName()
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: Theme.widgetIconColor
anchors.centerIn: parent
}
@@ -272,7 +272,7 @@ BasePill {
DankIcon {
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: root.getBatteryIconColor()
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showBatteryIcon && BatteryService.batteryAvailable
@@ -280,7 +280,7 @@ BasePill {
DankIcon {
name: "print"
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: Theme.primary
anchors.horizontalCenter: parent.horizontalCenter
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
@@ -288,7 +288,7 @@ BasePill {
DankIcon {
name: "settings"
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: root.isActive ? Theme.primary : Theme.widgetIconColor
anchors.horizontalCenter: parent.horizontalCenter
visible: root.hasNoVisibleIcons()
@@ -304,7 +304,7 @@ BasePill {
DankIcon {
id: networkIcon
name: root.getNetworkIconName()
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: root.getNetworkIconColor()
anchors.verticalCenter: parent.verticalCenter
visible: root.showNetworkIcon && NetworkService.networkAvailable
@@ -313,7 +313,7 @@ BasePill {
DankIcon {
id: vpnIcon
name: "vpn_lock"
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: NetworkService.vpnConnected ? Theme.primary : Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: root.showVpnIcon && NetworkService.vpnAvailable && NetworkService.vpnConnected
@@ -322,7 +322,7 @@ BasePill {
DankIcon {
id: bluetoothIcon
name: "bluetooth"
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: BluetoothService.connected ? Theme.primary : Theme.outlineButton
anchors.verticalCenter: parent.verticalCenter
visible: root.showBluetoothIcon && BluetoothService.available && BluetoothService.enabled
@@ -338,7 +338,7 @@ BasePill {
DankIcon {
id: audioIcon
name: root.getVolumeIconName()
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: Theme.widgetIconColor
anchors.centerIn: parent
}
@@ -364,7 +364,7 @@ BasePill {
DankIcon {
id: micIcon
name: root.getMicIconName()
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: root.getMicIconColor()
anchors.centerIn: parent
}
@@ -390,7 +390,7 @@ BasePill {
DankIcon {
id: brightnessIcon
name: root.getBrightnessIconName()
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: Theme.widgetIconColor
anchors.centerIn: parent
}
@@ -409,7 +409,7 @@ BasePill {
DankIcon {
id: batteryIcon
name: Theme.getBatteryIcon(BatteryService.batteryLevel, BatteryService.isCharging, BatteryService.batteryAvailable)
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: root.getBatteryIconColor()
anchors.verticalCenter: parent.verticalCenter
visible: root.showBatteryIcon && BatteryService.batteryAvailable
@@ -418,7 +418,7 @@ BasePill {
DankIcon {
id: printerIcon
name: "print"
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: Theme.primary
anchors.verticalCenter: parent.verticalCenter
visible: root.showPrinterIcon && CupsService.cupsAvailable && root.hasPrintJobs()
@@ -426,7 +426,7 @@ BasePill {
DankIcon {
name: "settings"
size: Theme.barIconSize(root.barThickness)
size: Theme.barIconSize(root.barThickness, -4)
color: root.isActive ? Theme.primary : Theme.widgetIconColor
anchors.verticalCenter: parent.verticalCenter
visible: root.hasNoVisibleIcons()
@@ -541,6 +541,34 @@ Item {
}
}
function getWorkspaceIndex(modelData) {
let isPlaceholder;
if (root.useExtWorkspace) {
isPlaceholder = modelData?.hidden === true;
} else if (CompositorService.isHyprland) {
isPlaceholder = modelData?.id === -1;
} else if (CompositorService.isDwl) {
isPlaceholder = modelData?.tag === -1;
} else if (CompositorService.isSway) {
isPlaceholder = modelData?.num === -1;
} else {
isPlaceholder = modelData === -1;
}
if (isPlaceholder)
return index + 1;
if (root.useExtWorkspace)
return index + 1;
if (CompositorService.isHyprland)
return modelData?.id || "";
if (CompositorService.isDwl)
return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
if (CompositorService.isSway)
return modelData?.num || "";
return modelData - 1;
}
readonly property bool hasNativeWorkspaceSupport: CompositorService.isNiri || CompositorService.isHyprland || CompositorService.isDwl || CompositorService.isSway
readonly property bool hasWorkspaces: getRealWorkspaces().length > 0
readonly property bool shouldShow: hasNativeWorkspaceSupport || (useExtWorkspace && hasWorkspaces)
@@ -862,7 +890,18 @@ Item {
id: rowLayout
Row {
spacing: 4
visible: loadedIcons.length > 0
visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex
StyledText {
topPadding: 2
rightPadding: isActive ? 4 : 0
visible: SettingsData.showWorkspaceIndex
text: {
return root.getWorkspaceIndex(modelData);
}
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
}
Repeater {
model: ScriptModel {
@@ -1045,31 +1084,7 @@ Item {
StyledText {
anchors.centerIn: parent
text: {
let isPlaceholder;
if (root.useExtWorkspace) {
isPlaceholder = modelData?.hidden === true;
} else if (CompositorService.isHyprland) {
isPlaceholder = modelData?.id === -1;
} else if (CompositorService.isDwl) {
isPlaceholder = modelData?.tag === -1;
} else if (CompositorService.isSway) {
isPlaceholder = modelData?.num === -1;
} else {
isPlaceholder = modelData === -1;
}
if (isPlaceholder)
return index + 1;
if (root.useExtWorkspace)
return index + 1;
if (CompositorService.isHyprland)
return modelData?.id || "";
if (CompositorService.isDwl)
return (modelData?.tag !== undefined) ? (modelData.tag + 1) : "";
if (CompositorService.isSway)
return modelData?.num || "";
return modelData - 1;
return root.getWorkspaceIndex(modelData);
}
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
+66 -129
View File
@@ -51,17 +51,9 @@ Item {
readonly property bool usePlayerVolume: activePlayer && activePlayer.volumeSupported && !__isChromeBrowser
readonly property real currentVolume: usePlayerVolume ? activePlayer.volume : (AudioService.sink?.audio?.volume ?? 0)
// Palette that stays stable across track switches until new colors are ready
property color dom: Qt.rgba(Theme.surface.r, Theme.surface.g, Theme.surface.b, 1.0)
property color acc: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.25)
property color _nextDom: dom
property color _nextAcc: acc
// Track-switch hold (prevents banner flicker only during switches)
property bool isSwitching: false
property bool paletteReady: false
property string _lastArtUrl: ""
property url _cqSource: ""
property string _bgArtSource: ""
// Derived "no players" state: always correct, no timers.
readonly property int _playerCount: allPlayers ? allPlayers.length : 0
@@ -69,7 +61,6 @@ Item {
readonly property bool _trulyIdle: activePlayer && activePlayer.playbackState === MprisPlaybackState.Stopped && !activePlayer.trackTitle && !activePlayer.trackArtist
readonly property bool showNoPlayerNow: (!_switchHold) && (_noneAvailable || _trulyIdle)
// Short hold only during track switches (not when players disappear)
property bool _switchHold: false
Timer {
id: _switchHoldTimer
@@ -86,11 +77,9 @@ Item {
}
isSwitching = true;
_switchHold = true;
paletteReady = false;
_switchHoldTimer.restart();
if (activePlayer.trackArtUrl) {
if (activePlayer.trackArtUrl)
loadArtwork(activePlayer.trackArtUrl);
}
}
property string activeTrackArtFile: ""
@@ -108,13 +97,13 @@ Item {
imageDownloader.command = ["curl", "-L", "-s", "--user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36", "-o", filename, url];
imageDownloader.targetFile = filename;
imageDownloader.running = true;
} else {
_preloadImage.source = url;
return;
}
_bgArtSource = url;
}
function maybeFinishSwitch() {
if (activePlayer && activePlayer.trackTitle !== "" && paletteReady) {
if (activePlayer && activePlayer.trackTitle !== "") {
isSwitching = false;
_switchHold = false;
}
@@ -219,9 +208,8 @@ Item {
property string targetFile: ""
onExited: exitCode => {
if (exitCode === 0 && targetFile) {
_preloadImage.source = "file://" + targetFile;
}
if (exitCode === 0 && targetFile)
_bgArtSource = "file://" + targetFile;
}
}
@@ -230,121 +218,70 @@ Item {
running: false
}
Image {
id: _preloadImage
source: ""
asynchronous: true
cache: true
visible: false
onStatusChanged: {
if (status === Image.Ready) {
_cqSource = source;
colorQuantizer.source = _cqSource;
} else if (status === Image.Error) {
_cqSource = "";
}
}
}
ColorQuantizer {
id: colorQuantizer
source: _cqSource !== "" ? _cqSource : undefined
depth: 8
rescaleSize: 32
onColorsChanged: {
if (!colors || colors.length === 0)
return;
function enhanceColor(color) {
const satBoost = 1.4;
const valueBoost = 1.2;
return Qt.hsva(color.hsvHue, Math.min(1, color.hsvSaturation * satBoost), Math.min(1, color.hsvValue * valueBoost), color.a);
}
function getExtremeColor(startIdx, direction = 1) {
let bestColor = colors[startIdx];
let bestScore = 0;
for (let i = startIdx; i >= 0 && i < colors.length; i += direction) {
const c = colors[i];
const saturation = c.hsvSaturation;
const brightness = c.hsvValue;
const contrast = Math.abs(brightness - 0.5) * 2;
const score = saturation * 0.7 + contrast * 0.3;
if (score > bestScore) {
bestScore = score;
bestColor = c;
}
}
return enhanceColor(bestColor);
}
_pendingDom = getExtremeColor(Math.floor(colors.length * 0.2), 1);
_pendingAcc = getExtremeColor(Math.floor(colors.length * 0.8), -1);
paletteApplyDelay.restart();
}
}
property color _pendingDom: dom
property color _pendingAcc: acc
Timer {
id: paletteApplyDelay
interval: 90
repeat: false
onTriggered: {
const dist = (c1, c2) => {
const dr = c1.r - c2.r, dg = c1.g - c2.g, db = c1.b - c2.b;
return Math.sqrt(dr * dr + dg * dg + db * db);
};
const domChanged = dist(_pendingDom, dom) > 0.02;
const accChanged = dist(_pendingAcc, acc) > 0.02;
if (domChanged || accChanged) {
dom = _pendingDom;
acc = _pendingAcc;
}
paletteReady = true;
maybeFinishSwitch();
}
}
property bool isSeeking: false
Rectangle {
Item {
id: bgContainer
anchors.fill: parent
radius: Theme.cornerRadius
opacity: 1.0
gradient: Gradient {
GradientStop {
position: 0.0
color: Qt.rgba(dom.r, dom.g, dom.b, paletteReady ? 0.38 : 0.06)
}
GradientStop {
position: 0.3
color: Qt.rgba(acc.r, acc.g, acc.b, paletteReady ? 0.28 : 0.05)
}
GradientStop {
position: 1.0
color: Qt.rgba(Theme.surface.r, Theme.surface.g, Theme.surface.b, paletteReady ? 0.92 : 0.985)
}
}
Behavior on opacity {
NumberAnimation {
duration: 160
}
}
}
visible: _bgArtSource !== ""
Behavior on dom {
ColorAnimation {
duration: 220
easing.type: Easing.InOutQuad
Image {
id: bgImage
anchors.centerIn: parent
width: Math.max(parent.width, parent.height) * 1.1
height: width
source: _bgArtSource
fillMode: Image.PreserveAspectCrop
asynchronous: true
cache: true
visible: false
onStatusChanged: {
if (status === Image.Ready)
maybeFinishSwitch();
}
}
}
Behavior on acc {
ColorAnimation {
duration: 220
easing.type: Easing.InOutQuad
Item {
id: blurredBg
anchors.fill: parent
visible: false
MultiEffect {
anchors.centerIn: parent
width: bgImage.width
height: bgImage.height
source: bgImage
blurEnabled: true
blurMax: 64
blur: 0.8
saturation: -0.2
brightness: -0.25
}
}
Rectangle {
id: bgMask
anchors.fill: parent
radius: Theme.cornerRadius
visible: false
layer.enabled: true
}
MultiEffect {
anchors.fill: parent
source: blurredBg
maskEnabled: true
maskSource: bgMask
maskThresholdMin: 0.5
maskSpreadAtMin: 1.0
opacity: 0.7
}
Rectangle {
anchors.fill: parent
radius: Theme.cornerRadius
color: Theme.surface
opacity: 0.3
}
}
+22 -3
View File
@@ -1086,14 +1086,33 @@ Item {
Row {
spacing: Theme.spacingM
anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.networkStatus !== "disconnected" || (BluetoothService.available && BluetoothService.enabled) || (AudioService.sink && AudioService.sink.audio)
visible: NetworkService.networkAvailable || (BluetoothService.available && BluetoothService.enabled) || (AudioService.sink && AudioService.sink.audio)
DankIcon {
name: NetworkService.networkStatus === "ethernet" ? "lan" : NetworkService.wifiSignalIcon
name: {
if (NetworkService.wifiToggling)
return "sync";
switch (NetworkService.networkStatus) {
case "ethernet":
return "lan";
case "vpn":
return NetworkService.ethernetConnected ? "lan" : NetworkService.wifiSignalIcon;
default:
return NetworkService.wifiSignalIcon;
}
}
size: Theme.iconSize - 2
color: NetworkService.networkStatus !== "disconnected" ? "white" : Qt.rgba(255, 255, 255, 0.5)
anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.networkStatus !== "disconnected"
visible: NetworkService.networkAvailable
}
DankIcon {
name: "vpn_lock"
size: Theme.iconSize - 2
color: NetworkService.vpnConnected ? Theme.primary : Qt.rgba(255, 255, 255, 0.5)
anchors.verticalCenter: parent.verticalCenter
visible: NetworkService.vpnAvailable && NetworkService.vpnConnected
}
DankIcon {
+5 -10
View File
@@ -59,12 +59,6 @@ Item {
"description": I18n.tr("Quick note-taking slideout panel"),
"icon": "sticky_note_2"
},
{
"id": "systemTray",
"name": I18n.tr("System Tray"),
"description": I18n.tr("System tray icons"),
"icon": "notifications"
}
];
}
@@ -199,8 +193,8 @@ Item {
DankDropdown {
width: parent.width - parent.leftPadding - parent.rightPadding
text: I18n.tr("Night Temperature")
description: I18n.tr("Color temperature for night mode")
text: SessionData.nightModeAutoEnabled ? I18n.tr("Night Temperature") : I18n.tr("Color Temperature")
description: SessionData.nightModeAutoEnabled ? I18n.tr("Color temperature for night mode") : I18n.tr("Warm color temperature to apply")
currentValue: SessionData.nightModeTemperature + "K"
options: {
var temps = [];
@@ -223,6 +217,7 @@ Item {
text: I18n.tr("Day Temperature")
description: I18n.tr("Color temperature for day time")
currentValue: SessionData.nightModeHighTemperature + "K"
visible: SessionData.nightModeAutoEnabled
options: {
var temps = [];
var minTemp = SessionData.nightModeTemperature;
@@ -821,7 +816,7 @@ Item {
const prefs = displaysTab.getScreenPreferences(parent.componentId);
const isAll = prefs.includes("all") || (typeof prefs[0] === "string" && prefs[0] === "all");
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;
}
onToggled: checked => {
@@ -905,4 +900,4 @@ Item {
}
}
}
}
}
+1 -1
View File
@@ -597,7 +597,7 @@ Item {
onSaveBind: (originalKey, newData) => {
KeybindsService.saveBind(originalKey, newData);
keybindsTab._editingKey = newData.key;
keybindsTab.expandedKey = modelData.action;
keybindsTab.expandedKey = newData.action;
}
onRemoveBind: key => {
const remainingKey = bindItem.keys.find(k => k.key !== key)?.key ?? "";
+1 -1
View File
@@ -506,7 +506,7 @@ Singleton {
DMSService.sendRequest("wayland.gamma.setTemperature", {
"low": temperature,
"high": 6500
"high": temperature
}, response => {
if (response.error) {
console.error("DisplayService: Failed to set temperature:", response.error);
+87 -72
View File
@@ -650,9 +650,10 @@ Item {
}
onWheel: wheel => {
if (!root.recording)
if (!root.recording) {
wheel.accepted = false;
return;
}
wheel.accepted = true;
const mods = [];
@@ -959,12 +960,12 @@ Item {
Layout.preferredWidth: 120
compactMode: true
currentValue: {
const action = root.editAction;
const base = root.editAction.split(" ")[0];
const cats = KeybindsService.getCompositorCategories();
for (const cat of cats) {
const actions = KeybindsService.getCompositorActions(cat);
for (const act of actions) {
if (act.id === action)
if (act.id === base)
return cat;
}
}
@@ -1024,12 +1025,13 @@ Item {
}
RowLayout {
id: optionsRow
Layout.fillWidth: true
spacing: Theme.spacingM
visible: root._actionType === "compositor" && !root.useCustomCompositor && Actions.getActionArgConfig(root.editAction)
property var argConfig: Actions.getActionArgConfig(root.editAction)
property var parsedArgs: Actions.parseCompositorActionArgs(root.editAction)
readonly property var argConfig: Actions.getActionArgConfig(root.editAction)
readonly property var parsedArgs: Actions.parseCompositorActionArgs(root.editAction)
StyledText {
text: I18n.tr("Options")
@@ -1048,56 +1050,75 @@ Item {
Layout.fillWidth: true
Layout.preferredHeight: 40
visible: {
const cfg = parent.parent.argConfig;
if (!cfg || !cfg.config || !cfg.config.args)
const cfg = optionsRow.argConfig;
if (!cfg?.config?.args)
return false;
const firstArg = cfg.config.args[0];
return firstArg && (firstArg.type === "text" || firstArg.type === "number");
}
placeholderText: {
const cfg = parent.parent.argConfig;
if (!cfg || !cfg.config || !cfg.config.args)
return "";
return cfg.config.args[0]?.placeholder || "";
placeholderText: optionsRow.argConfig?.config?.args?.[0]?.placeholder || ""
Connections {
target: optionsRow
function onParsedArgsChanged() {
const newText = optionsRow.parsedArgs?.args?.value || optionsRow.parsedArgs?.args?.index || "";
if (argValueField.text !== newText)
argValueField.text = newText;
}
}
text: parent.parent.parsedArgs?.args?.value || parent.parent.parsedArgs?.args?.index || ""
onTextChanged: {
const cfg = parent.parent.argConfig;
Component.onCompleted: {
text = optionsRow.parsedArgs?.args?.value || optionsRow.parsedArgs?.args?.index || "";
}
onEditingFinished: {
const cfg = optionsRow.argConfig;
if (!cfg)
return;
const base = parent.parent.parsedArgs?.base || root.editAction.split(" ")[0];
const args = cfg.config.args[0]?.type === "number" ? {
index: text
} : {
value: text
};
const parsed = optionsRow.parsedArgs;
const args = {};
if (cfg.config.args[0]?.type === "number")
args.index = text;
else
args.value = text;
if (parsed?.args?.focus === false)
args.focus = false;
root.updateEdit({
action: Actions.buildCompositorAction(base, args)
action: Actions.buildCompositorAction(parsed?.base || cfg.base, args)
});
}
}
RowLayout {
visible: {
const cfg = parent.parent.argConfig;
return cfg && cfg.base === "move-column-to-workspace";
const cfg = optionsRow.argConfig;
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
DankToggle {
id: focusToggle
checked: parent.parent.parent.parsedArgs?.args?.focus === true
onCheckedChanged: {
const cfg = parent.parent.parent.argConfig;
checked: optionsRow.parsedArgs?.args?.focus !== false
onToggled: newChecked => {
const cfg = optionsRow.argConfig;
if (!cfg)
return;
const parsed = parent.parent.parent.parsedArgs;
const args = {
index: parsed?.args?.index || "",
focus: checked
};
const parsed = optionsRow.parsedArgs;
const args = {};
if (cfg.base === "move-column-to-workspace")
args.index = parsed?.args?.index || "";
if (!newChecked)
args.focus = false;
root.updateEdit({
action: Actions.buildCompositorAction("move-column-to-workspace", args)
action: Actions.buildCompositorAction(cfg.base, args)
});
}
}
@@ -1110,53 +1131,22 @@ Item {
}
RowLayout {
visible: {
const cfg = parent.parent.argConfig;
return cfg && cfg.base && cfg.base.startsWith("screenshot");
}
visible: optionsRow.argConfig?.base?.startsWith("screenshot") ?? false
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 {
spacing: Theme.spacingXS
DankToggle {
id: showPointerToggle
checked: parent.parent.parent.parent.parsedArgs?.args?.opts?.["show-pointer"] === "true"
onCheckedChanged: {
const parsed = parent.parent.parent.parent.parsedArgs;
checked: optionsRow.parsedArgs?.args?.["show-pointer"] === true
onToggled: newChecked => {
const parsed = optionsRow.parsedArgs;
const base = parsed?.base || "screenshot";
const opts = parsed?.args?.opts || {};
opts["show-pointer"] = checked ? "true" : "";
const args = Object.assign({}, parsed?.args || {});
args["show-pointer"] = newChecked;
root.updateEdit({
action: Actions.buildCompositorAction(base, {
opts: opts
})
action: Actions.buildCompositorAction(base, args)
});
}
}
@@ -1167,6 +1157,31 @@ Item {
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
}
}
}
}
}
+78 -40
View File
@@ -17,12 +17,19 @@ Item {
property color playheadColor: Theme.primary
property real dpr: (root.window ? root.window.devicePixelRatio : 1)
function snap(v) { return Math.round(v * dpr) / dpr }
function snap(v) {
return Math.round(v * dpr) / dpr;
}
readonly property real playX: snap(root.width * root.value)
readonly property real midY: snap(height / 2)
Behavior on currentAmp { NumberAnimation { duration: 300; easing.type: Easing.OutCubic } }
Behavior on currentAmp {
NumberAnimation {
duration: 300
easing.type: Easing.OutCubic
}
}
onIsPlayingChanged: currentAmp = isPlaying ? amp : 0
Shape {
@@ -38,8 +45,16 @@ Item {
capStyle: ShapePath.RoundCap
joinStyle: ShapePath.RoundJoin
fillColor: "transparent"
PathMove { id: flatStart; x: 0; y: root.midY }
PathLine { id: flatEnd; x: root.width; y: root.midY }
PathMove {
id: flatStart
x: Math.min(root.width, snap(root.playX + playhead.width / 2))
y: root.midY
}
PathLine {
id: flatEnd
x: root.width
y: root.midY
}
}
}
@@ -48,7 +63,7 @@ Item {
anchors.fill: parent
clip: true
readonly property real startX: snap(root.lineWidth/2)
readonly property real startX: snap(root.lineWidth / 2)
readonly property real aaBias: (0.25 / root.dpr)
readonly property real endX: Math.max(startX, Math.min(root.playX - startX - aaBias, width))
@@ -77,7 +92,10 @@ Item {
capStyle: ShapePath.RoundCap
joinStyle: ShapePath.RoundJoin
fillColor: "transparent"
PathSvg { id: waveSvg; path: "" }
PathSvg {
id: waveSvg
path: ""
}
}
}
}
@@ -88,8 +106,8 @@ Item {
height: snap(root.lineWidth)
radius: width / 2
color: root.fillColor
x: waveClip.startX - width/2
y: root.midY - height/2 + root.currentAmp * Math.sin((waveClip.startX / root.wavelength) * 2 * Math.PI + root.phase)
x: waveClip.startX - width / 2
y: root.midY - height / 2 + root.currentAmp * Math.sin((waveClip.startX / root.wavelength) * 2 * Math.PI + root.phase)
visible: waveClip.endX > waveClip.startX
z: 2
}
@@ -100,8 +118,8 @@ Item {
height: snap(root.lineWidth)
radius: width / 2
color: root.fillColor
x: waveClip.endX - width/2
y: root.midY - height/2 + root.currentAmp * Math.sin((waveClip.endX / root.wavelength) * 2 * Math.PI + root.phase)
x: waveClip.endX - width / 2
y: root.midY - height / 2 + root.currentAmp * Math.sin((waveClip.endX / root.wavelength) * 2 * Math.PI + root.phase)
visible: waveClip.endX > waveClip.startX
z: 2
}
@@ -119,48 +137,68 @@ Item {
}
property real k: (2 * Math.PI) / Math.max(1e-6, wavelength)
function wrapMod(a, m) { let r = a % m; return r < 0 ? r + m : r }
function wrapMod(a, m) {
let r = a % m;
return r < 0 ? r + m : r;
}
readonly property real waveOffsetX: -wrapMod(phase / k, wavelength)
FrameAnimation {
running: root.visible && (root.isPlaying || root.currentAmp > 0)
onTriggered: {
if (root.isPlaying) root.phase += 0.03 * frameTime * 60
startCap.y = root.midY - startCap.height/2 + root.currentAmp * Math.sin((waveClip.startX / root.wavelength) * 2 * Math.PI + root.phase)
endCap.y = root.midY - endCap.height/2 + root.currentAmp * Math.sin((waveClip.endX / root.wavelength) * 2 * Math.PI + root.phase)
if (root.isPlaying)
root.phase += 0.03 * frameTime * 60;
startCap.y = root.midY - startCap.height / 2 + root.currentAmp * Math.sin((waveClip.startX / root.wavelength) * 2 * Math.PI + root.phase);
endCap.y = root.midY - endCap.height / 2 + root.currentAmp * Math.sin((waveClip.endX / root.wavelength) * 2 * Math.PI + root.phase);
}
}
function buildStaticWave() {
const start = waveClip.startX - 2 * root.wavelength
const end = width + 2 * root.wavelength
if (end <= start) { waveSvg.path = ""; return }
const kLocal = k
const halfPeriod = root.wavelength / 2
function y0(x) { return root.midY + root.currentAmp * Math.sin(kLocal * x) }
function dy0(x) { return root.currentAmp * Math.cos(kLocal * x) * kLocal }
let x0 = start
let d = `M ${x0} ${y0(x0)}`
while (x0 < end) {
const x1 = Math.min(x0 + halfPeriod, end)
const dx = x1 - x0
const yA = y0(x0), yB = y0(x1)
const dyA = dy0(x0), dyB = dy0(x1)
const c1x = x0 + dx/3
const c1y = yA + (dyA * dx)/3
const c2x = x1 - dx/3
const c2y = yB - (dyB * dx)/3
d += ` C ${c1x} ${c1y} ${c2x} ${c2y} ${x1} ${yB}`
x0 = x1
const start = waveClip.startX - 2 * root.wavelength;
const end = width + 2 * root.wavelength;
if (end <= start) {
waveSvg.path = "";
return;
}
waveSvg.path = d
const kLocal = k;
const halfPeriod = root.wavelength / 2;
function y0(x) {
return root.midY + root.currentAmp * Math.sin(kLocal * x);
}
function dy0(x) {
return root.currentAmp * Math.cos(kLocal * x) * kLocal;
}
let x0 = start;
let d = `M ${x0} ${y0(x0)}`;
while (x0 < end) {
const x1 = Math.min(x0 + halfPeriod, end);
const dx = x1 - x0;
const yA = y0(x0), yB = y0(x1);
const dyA = dy0(x0), dyB = dy0(x1);
const c1x = x0 + dx / 3;
const c1y = yA + (dyA * dx) / 3;
const c2x = x1 - dx / 3;
const c2y = yB - (dyB * dx) / 3;
d += ` C ${c1x} ${c1y} ${c2x} ${c2y} ${x1} ${yB}`;
x0 = x1;
}
waveSvg.path = d;
}
Component.onCompleted: { currentAmp = isPlaying ? amp : 0; buildStaticWave() }
onWidthChanged: { flatStart.x = 0; flatEnd.x = width; buildStaticWave() }
Component.onCompleted: {
currentAmp = isPlaying ? amp : 0;
buildStaticWave();
}
onWidthChanged: {
flatEnd.x = width;
buildStaticWave();
}
onHeightChanged: buildStaticWave()
onCurrentAmpChanged: buildStaticWave()
onWavelengthChanged: { k = (2 * Math.PI) / Math.max(1e-6, wavelength); buildStaticWave() }
onWavelengthChanged: {
k = (2 * Math.PI) / Math.max(1e-6, wavelength);
buildStaticWave();
}
}
Binary file not shown.
+16 -16
View File
@@ -11,21 +11,21 @@ text = '{{colors.background.default.hex}}'
cursor = '{{colors.primary.default.hex}}'
[colors.normal]
black = '{{dank16.color0.hex}}'
red = '{{dank16.color1.hex}}'
green = '{{dank16.color2.hex}}'
yellow = '{{dank16.color3.hex}}'
blue = '{{dank16.color4.hex}}'
magenta = '{{dank16.color5.hex}}'
cyan = '{{dank16.color6.hex}}'
white = '{{dank16.color7.hex}}'
black = '{{dank16.color0.default.hex}}'
red = '{{dank16.color1.default.hex}}'
green = '{{dank16.color2.default.hex}}'
yellow = '{{dank16.color3.default.hex}}'
blue = '{{dank16.color4.default.hex}}'
magenta = '{{dank16.color5.default.hex}}'
cyan = '{{dank16.color6.default.hex}}'
white = '{{dank16.color7.default.hex}}'
[colors.bright]
black = '{{dank16.color8.hex}}'
red = '{{dank16.color9.hex}}'
green = '{{dank16.color10.hex}}'
yellow = '{{dank16.color11.hex}}'
blue = '{{dank16.color12.hex}}'
magenta = '{{dank16.color13.hex}}'
cyan = '{{dank16.color14.hex}}'
white = '{{dank16.color15.hex}}'
black = '{{dank16.color8.default.hex}}'
red = '{{dank16.color9.default.hex}}'
green = '{{dank16.color10.default.hex}}'
yellow = '{{dank16.color11.default.hex}}'
blue = '{{dank16.color12.default.hex}}'
magenta = '{{dank16.color13.default.hex}}'
cyan = '{{dank16.color14.default.hex}}'
white = '{{dank16.color15.default.hex}}'
+16 -16
View File
@@ -4,19 +4,19 @@ background={{colors.background.default.hex_stripped}}
selection-foreground={{colors.on_surface.default.hex_stripped}}
selection-background={{colors.primary_container.default.hex_stripped}}
regular0={{dank16.color0.hex_stripped}}
regular1={{dank16.color1.hex_stripped}}
regular2={{dank16.color2.hex_stripped}}
regular3={{dank16.color3.hex_stripped}}
regular4={{dank16.color4.hex_stripped}}
regular5={{dank16.color5.hex_stripped}}
regular6={{dank16.color6.hex_stripped}}
regular7={{dank16.color7.hex_stripped}}
bright0={{dank16.color8.hex_stripped}}
bright1={{dank16.color9.hex_stripped}}
bright2={{dank16.color10.hex_stripped}}
bright3={{dank16.color11.hex_stripped}}
bright4={{dank16.color12.hex_stripped}}
bright5={{dank16.color13.hex_stripped}}
bright6={{dank16.color14.hex_stripped}}
bright7={{dank16.color15.hex_stripped}}
regular0={{dank16.color0.default.hex_stripped}}
regular1={{dank16.color1.default.hex_stripped}}
regular2={{dank16.color2.default.hex_stripped}}
regular3={{dank16.color3.default.hex_stripped}}
regular4={{dank16.color4.default.hex_stripped}}
regular5={{dank16.color5.default.hex_stripped}}
regular6={{dank16.color6.default.hex_stripped}}
regular7={{dank16.color7.default.hex_stripped}}
bright0={{dank16.color8.default.hex_stripped}}
bright1={{dank16.color9.default.hex_stripped}}
bright2={{dank16.color10.default.hex_stripped}}
bright3={{dank16.color11.default.hex_stripped}}
bright4={{dank16.color12.default.hex_stripped}}
bright5={{dank16.color13.default.hex_stripped}}
bright6={{dank16.color14.default.hex_stripped}}
bright7={{dank16.color15.default.hex_stripped}}
+16 -16
View File
@@ -4,19 +4,19 @@ cursor-color = {{colors.primary.default.hex}}
selection-background = {{colors.primary_container.default.hex}}
selection-foreground = {{colors.on_surface.default.hex}}
palette = 0={{dank16.color0.hex}}
palette = 1={{dank16.color1.hex}}
palette = 2={{dank16.color2.hex}}
palette = 3={{dank16.color3.hex}}
palette = 4={{dank16.color4.hex}}
palette = 5={{dank16.color5.hex}}
palette = 6={{dank16.color6.hex}}
palette = 7={{dank16.color7.hex}}
palette = 8={{dank16.color8.hex}}
palette = 9={{dank16.color9.hex}}
palette = 10={{dank16.color10.hex}}
palette = 11={{dank16.color11.hex}}
palette = 12={{dank16.color12.hex}}
palette = 13={{dank16.color13.hex}}
palette = 14={{dank16.color14.hex}}
palette = 15={{dank16.color15.hex}}
palette = 0={{dank16.color0.default.hex}}
palette = 1={{dank16.color1.default.hex}}
palette = 2={{dank16.color2.default.hex}}
palette = 3={{dank16.color3.default.hex}}
palette = 4={{dank16.color4.default.hex}}
palette = 5={{dank16.color5.default.hex}}
palette = 6={{dank16.color6.default.hex}}
palette = 7={{dank16.color7.default.hex}}
palette = 8={{dank16.color8.default.hex}}
palette = 9={{dank16.color9.default.hex}}
palette = 10={{dank16.color10.default.hex}}
palette = 11={{dank16.color11.default.hex}}
palette = 12={{dank16.color12.default.hex}}
palette = 13={{dank16.color13.default.hex}}
palette = 14={{dank16.color14.default.hex}}
palette = 15={{dank16.color15.default.hex}}
+16 -16
View File
@@ -7,19 +7,19 @@ selection_foreground {{colors.on_secondary.default.hex}}
selection_background {{colors.secondary_fixed_dim.default.hex}}
url_color {{colors.primary.default.hex}}
color0 {{dank16.color0.hex}}
color1 {{dank16.color1.hex}}
color2 {{dank16.color2.hex}}
color3 {{dank16.color3.hex}}
color4 {{dank16.color4.hex}}
color5 {{dank16.color5.hex}}
color6 {{dank16.color6.hex}}
color7 {{dank16.color7.hex}}
color8 {{dank16.color8.hex}}
color9 {{dank16.color9.hex}}
color10 {{dank16.color10.hex}}
color11 {{dank16.color11.hex}}
color12 {{dank16.color12.hex}}
color13 {{dank16.color13.hex}}
color14 {{dank16.color14.hex}}
color15 {{dank16.color15.hex}}
color0 {{dank16.color0.default.hex}}
color1 {{dank16.color1.default.hex}}
color2 {{dank16.color2.default.hex}}
color3 {{dank16.color3.default.hex}}
color4 {{dank16.color4.default.hex}}
color5 {{dank16.color5.default.hex}}
color6 {{dank16.color6.default.hex}}
color7 {{dank16.color7.default.hex}}
color8 {{dank16.color8.default.hex}}
color9 {{dank16.color9.default.hex}}
color10 {{dank16.color10.default.hex}}
color11 {{dank16.color11.default.hex}}
color12 {{dank16.color12.default.hex}}
color13 {{dank16.color13.default.hex}}
color14 {{dank16.color14.default.hex}}
color15 {{dank16.color15.default.hex}}
@@ -1,431 +1,299 @@
{
"$schema": "vscode://schemas/color-theme",
"name": "Dynamic Base16 DankShell",
"name": "Dynamic Base16 DankShell Dark",
"semanticHighlighting": true,
"colors": {
//
// Core foreground + background hierarchy
//
"foreground": "{{colors.on_surface.dark.hex}}",
"editor.background": "{{colors.background.dark.hex}}",
"editor.foreground": "{{colors.on_surface.dark.hex}}",
"editorLineNumber.foreground": "{{colors.outline.dark.hex}}",
"editorLineNumber.activeForeground": "{{colors.on_surface.dark.hex}}",
"editorCursor.foreground": "{{colors.primary.dark.hex}}",
"editor.selectionBackground": "{{colors.primary_container.dark.hex}}",
"editor.inactiveSelectionBackground": "{{colors.background.dark.hex}}",
"editor.lineHighlightBackground": "{{colors.background.dark.hex}}",
"editorIndentGuide.background": "{{colors.background.dark.hex}}",
"editorIndentGuide.activeBackground": "{{colors.outline.dark.hex}}",
"editorWhitespace.foreground": "{{colors.outline_variant.dark.hex}}",
"editorBracketMatch.background": "{{colors.background.dark.hex}}",
"editorBracketMatch.border": "{{colors.primary.dark.hex}}",
"errorForeground": "{{colors.error.dark.hex}}",
//
// Borders + dividers
//
"panel.border": "{{colors.outline_variant.dark.hex}}",
"panelTitle.activeBorder": "{{colors.primary.dark.hex}}",
"sideBar.border": "{{colors.outline_variant.dark.hex}}",
"editorGroup.border": "{{colors.outline_variant.dark.hex}}",
"tab.border": "{{colors.outline_variant.dark.hex}}",
"titleBar.border": "{{colors.outline_variant.dark.hex}}",
//
// Focus + active indicators
//
"focusBorder": "{{colors.primary.dark.hex}}",
"selection.background": "{{colors.primary_container.dark.hex}}66",
//
// Title bar, activity bar, sidebar
//
"titleBar.activeBackground": "{{colors.background.dark.hex}}",
"titleBar.activeForeground": "{{colors.on_surface.dark.hex}}",
"titleBar.inactiveForeground": "{{colors.outline.dark.hex}}",
"activityBar.background": "{{colors.background.dark.hex}}",
"activityBar.foreground": "{{colors.on_surface.dark.hex}}",
"activityBar.inactiveForeground": "{{colors.outline.dark.hex}}",
"activityBar.activeBorder": "{{colors.primary.dark.hex}}",
"activityBar.activeBackground": "{{colors.background.dark.hex}}",
"activityBarBadge.background": "{{colors.primary.dark.hex}}",
"activityBarBadge.foreground": "{{colors.on_primary.dark.hex}}",
"sideBar.background": "{{colors.background.dark.hex}}",
"sideBar.background": "{{colors.surface_container_low.dark.hex}}",
"sideBar.foreground": "{{colors.on_surface.dark.hex}}",
"sideBar.border": "{{colors.background.dark.hex}}",
"statusBarItem.hoverBackground": "{{colors.surface_container_low.dark.hex}}",
"statusBarItem.activeBackground": "{{colors.surface_container.dark.hex}}",
"sideBarTitle.foreground": "{{colors.on_surface.dark.hex}}",
"sideBarSectionHeader.background": "{{colors.background.dark.hex}}",
"panel.background": "{{colors.surface_container_low.dark.hex}}",
"panelTitle.activeForeground": "{{colors.on_surface.dark.hex}}",
"panelTitle.inactiveForeground": "{{colors.outline.dark.hex}}",
"sideBarSectionHeader.background": "{{colors.surface_container.dark.hex}}",
"sideBarSectionHeader.foreground": "{{colors.on_surface.dark.hex}}",
"list.activeSelectionBackground": "{{colors.primary_container.dark.hex}}",
"list.activeSelectionForeground": "{{colors.on_primary_container.dark.hex}}",
"list.inactiveSelectionBackground": "{{colors.surface_container.dark.hex}}",
"list.inactiveSelectionForeground": "{{colors.on_surface.dark.hex}}",
"list.hoverBackground": "{{colors.surface_container.dark.hex}}",
"list.hoverForeground": "{{colors.on_surface.dark.hex}}",
"list.focusBackground": "{{colors.surface_container_high.dark.hex}}",
"list.focusForeground": "{{colors.on_surface.dark.hex}}",
"list.highlightForeground": "{{colors.primary.dark.hex}}",
"statusBar.background": "{{colors.background.dark.hex}}",
"statusBar.foreground": "{{colors.on_surface.dark.hex}}",
"statusBar.border": "{{colors.background.dark.hex}}",
"statusBar.noFolderBackground": "{{colors.background.dark.hex}}",
"statusBar.debuggingBackground": "{{colors.error.dark.hex}}",
"statusBar.debuggingForeground": "{{colors.on_error.dark.hex}}",
"tab.activeBackground": "{{colors.background.dark.hex}}",
//
// Tabs + editor groups
//
"editorGroupHeader.tabsBackground": "{{colors.background.dark.hex}}",
"tab.activeBackground": "{{colors.surface_container_low.dark.hex}}",
"tab.inactiveBackground": "{{colors.background.dark.hex}}",
"tab.activeForeground": "{{colors.on_surface.dark.hex}}",
"tab.inactiveForeground": "{{colors.outline.dark.hex}}",
"tab.border": "{{colors.background.dark.hex}}",
"tab.activeBorder": "{{colors.primary.dark.hex}}",
"tab.unfocusedActiveBorder": "{{colors.outline.dark.hex}}",
"editorGroupHeader.tabsBackground": "{{colors.background.dark.hex}}",
"editorGroupHeader.noTabsBackground": "{{colors.background.dark.hex}}",
"titleBar.activeBackground": "{{colors.background.dark.hex}}",
"titleBar.activeForeground": "{{colors.on_surface.dark.hex}}",
"titleBar.inactiveBackground": "{{colors.background.dark.hex}}",
"titleBar.inactiveForeground": "{{colors.outline.dark.hex}}",
"titleBar.border": "{{colors.background.dark.hex}}",
"input.background": "{{colors.background.dark.hex}}",
"tab.activeBorderTop": "{{colors.primary.dark.hex}}",
//
// Lists (files, search results, etc.)
//
"list.activeSelectionBackground": "{{colors.primary_container.dark.hex}}",
"list.activeSelectionForeground": "{{colors.on_primary_container.dark.hex}}",
"list.inactiveSelectionBackground": "{{colors.surface_container.dark.hex}}",
"list.hoverBackground": "{{colors.surface_container.dark.hex}}",
"list.hoverForeground": "{{colors.on_surface.dark.hex}}",
"list.focusForeground": "{{colors.on_surface.dark.hex}}",
"list.focusOutline": "{{colors.primary.dark.hex}}",
"list.focusBackground": "{{colors.surface_container_high.dark.hex}}",
"list.highlightForeground": "{{colors.primary.dark.hex}}",
"list.errorForeground": "{{colors.error.dark.hex}}",
"list.warningForeground": "{{colors.secondary.dark.hex}}",
//
// Inputs + dropdowns
//
"input.background": "{{colors.surface_container_low.dark.hex}}",
"input.foreground": "{{colors.on_surface.dark.hex}}",
"input.border": "{{colors.outline.dark.hex}}",
"input.border": "{{colors.outline_variant.dark.hex}}",
"input.placeholderForeground": "{{colors.outline.dark.hex}}",
"inputOption.activeBorder": "{{colors.primary.dark.hex}}",
"inputValidation.errorBackground": "{{colors.error.dark.hex}}",
"inputValidation.errorBorder": "{{colors.error.dark.hex}}",
"dropdown.background": "{{colors.background.dark.hex}}",
"dropdown.background": "{{colors.surface_container_low.dark.hex}}",
"dropdown.foreground": "{{colors.on_surface.dark.hex}}",
"dropdown.border": "{{colors.outline.dark.hex}}",
"quickInput.background": "{{colors.background.dark.hex}}",
"dropdown.border": "{{colors.outline_variant.dark.hex}}",
"quickInput.background": "{{colors.surface_container_low.dark.hex}}",
"quickInput.foreground": "{{colors.on_surface.dark.hex}}",
"quickInputList.focusBackground": "{{colors.surface_container_high.dark.hex}}",
"quickInputList.focusForeground": "{{colors.on_surface.dark.hex}}",
"widget.shadow": "{{colors.background.dark.hex}}80",
//
// Buttons
//
"button.background": "{{colors.primary.dark.hex}}",
"button.foreground": "{{colors.on_primary.dark.hex}}",
"button.hoverBackground": "{{colors.primary_container.dark.hex}}",
"focusBorder": "{{colors.primary.dark.hex}}",
"badge.background": "{{colors.secondary.dark.hex}}",
"badge.foreground": "{{colors.on_secondary.dark.hex}}",
"panel.background": "{{colors.background.dark.hex}}",
"panel.border": "{{colors.primary.dark.hex}}",
"panelTitle.activeBorder": "{{colors.primary.dark.hex}}",
"panelTitle.activeForeground": "{{colors.on_surface.dark.hex}}",
"panelTitle.inactiveForeground": "{{colors.outline.dark.hex}}",
"terminal.background": "{{colors.background.dark.hex}}",
"terminal.foreground": "{{colors.on_surface.dark.hex}}",
"terminal.ansiBlack": "{{dank16.color0.hex}}",
"terminal.ansiRed": "{{dank16.color1.hex}}",
"terminal.ansiGreen": "{{dank16.color2.hex}}",
"terminal.ansiYellow": "{{dank16.color3.hex}}",
"terminal.ansiBlue": "{{dank16.color4.hex}}",
"terminal.ansiMagenta": "{{dank16.color5.hex}}",
"terminal.ansiCyan": "{{dank16.color6.hex}}",
"terminal.ansiWhite": "{{dank16.color7.hex}}",
"terminal.ansiBrightBlack": "{{dank16.color8.hex}}",
"terminal.ansiBrightRed": "{{dank16.color9.hex}}",
"terminal.ansiBrightGreen": "{{dank16.color10.hex}}",
"terminal.ansiBrightYellow": "{{dank16.color11.hex}}",
"terminal.ansiBrightBlue": "{{dank16.color12.hex}}",
"terminal.ansiBrightMagenta": "{{dank16.color13.hex}}",
"terminal.ansiBrightCyan": "{{dank16.color14.hex}}",
"terminal.ansiBrightWhite": "{{dank16.color15.hex}}",
"gitDecoration.modifiedResourceForeground": "{{colors.primary.dark.hex}}",
"gitDecoration.addedResourceForeground": "{{colors.primary.dark.hex}}",
"gitDecoration.stageModifiedResourceForeground": "{{colors.primary.dark.hex}}",
"gitDecoration.stageDeletedResourceForeground": "{{colors.error.dark.hex}}",
"gitDecoration.deletedResourceForeground": "{{colors.error.dark.hex}}",
"gitDecoration.untrackedResourceForeground": "{{colors.secondary.dark.hex}}",
"gitDecoration.ignoredResourceForeground": "{{colors.outline.dark.hex}}",
"gitDecoration.conflictingResourceForeground": "{{colors.error_container.dark.hex}}",
"gitDecoration.submoduleResourceForeground": "{{colors.primary.dark.hex}}",
"editorWidget.background": "{{colors.background.dark.hex}}",
"editorWidget.border": "{{colors.outline.dark.hex}}",
"editorSuggestWidget.background": "{{colors.background.dark.hex}}",
"editorSuggestWidget.border": "{{colors.outline.dark.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.surface_container_high.dark.hex}}",
"editorSuggestWidget.highlightForeground": "{{colors.primary.dark.hex}}",
"peekView.border": "{{colors.primary.dark.hex}}",
"peekViewEditor.background": "{{colors.background.dark.hex}}",
"peekViewResult.background": "{{colors.background.dark.hex}}",
"peekViewTitle.background": "{{colors.background.dark.hex}}",
"notificationCenter.border": "{{colors.outline.dark.hex}}",
"notifications.background": "{{colors.background.dark.hex}}",
"notifications.border": "{{colors.outline.dark.hex}}",
//
// Status bar
//
"statusBar.background": "{{colors.background.dark.hex}}",
"statusBar.foreground": "{{colors.on_surface.dark.hex}}",
"statusBar.border": "{{colors.outline_variant.dark.hex}}",
"statusBar.noFolderBackground": "{{colors.background.dark.hex}}",
"statusBar.noFolderForeground": "{{colors.on_surface.dark.hex}}",
"statusBar.debuggingBackground": "{{colors.error.dark.hex}}",
"statusBar.debuggingForeground": "{{colors.on_error.dark.hex}}",
//
// Notifications
//
"notificationCenterHeader.background": "{{colors.surface_container_low.dark.hex}}",
"notificationCenterHeader.foreground": "{{colors.on_surface.dark.hex}}",
"notificationCenter.border": "{{colors.outline_variant.dark.hex}}",
"notifications.background": "{{colors.surface_container_low.dark.hex}}",
"notifications.foreground": "{{colors.on_surface.dark.hex}}",
"notifications.border": "{{colors.outline_variant.dark.hex}}",
"notificationsErrorIcon.foreground": "{{colors.error.dark.hex}}",
"notificationsWarningIcon.foreground": "{{colors.secondary.dark.hex}}",
"notificationsInfoIcon.foreground": "{{colors.primary.dark.hex}}",
//
// Breadcrumbs
//
"breadcrumb.background": "{{colors.surface_container.dark.hex}}",
"breadcrumb.foreground": "{{colors.outline.dark.hex}}",
"breadcrumb.focusForeground": "{{colors.on_surface.dark.hex}}",
"breadcrumb.activeSelectionForeground": "{{colors.primary.dark.hex}}",
//
// Editor highlights + guides
//
"editorLineNumber.foreground": "{{colors.outline.dark.hex}}",
"editorLineNumber.activeForeground": "{{colors.primary.dark.hex}}",
"editorCursor.foreground": "{{colors.primary.dark.hex}}",
"editor.lineHighlightBackground": "{{colors.surface_container.dark.hex}}66",
"editor.wordHighlightBackground": "{{colors.secondary.dark.hex}}22",
"editor.wordHighlightStrongBackground": "{{colors.tertiary.dark.hex}}22",
"editor.selectionBackground": "{{colors.primary_container.dark.hex}}66",
"editor.inactiveSelectionBackground": "{{colors.primary_container.dark.hex}}33",
"editorWhitespace.foreground": "{{colors.outline.dark.hex}}66",
"editorIndentGuide.background1": "{{colors.outline.dark.hex}}33",
"editorIndentGuide.activeBackground1": "{{colors.primary.dark.hex}}99",
"editorOverviewRuler.border": "{{colors.outline_variant.dark.hex}}",
"editorOverviewRuler.errorForeground": "{{colors.error.dark.hex}}88",
"editorOverviewRuler.warningForeground": "{{colors.tertiary.dark.hex}}88",
"editorOverviewRuler.infoForeground": "{{colors.primary.dark.hex}}88",
"editorStickyScroll.background": "{{colors.background.dark.hex}}",
"editorStickyScrollHover.background": "{{colors.surface_container_low.dark.hex}}",
"editorBracketHighlight.unexpectedBracket.foreground": "{{colors.outline.dark.hex}}",
//
// Diff editor
//
"diffEditor.insertedTextBackground": "{{colors.secondary.dark.hex}}20",
"diffEditor.removedTextBackground": "{{colors.error.dark.hex}}20",
//
// Git
//
"gitDecoration.modifiedResourceForeground": "{{colors.primary.dark.hex}}",
"gitDecoration.addedResourceForeground": "{{colors.secondary.dark.hex}}",
"gitDecoration.deletedResourceForeground": "{{colors.error.dark.hex}}",
"gitDecoration.ignoredResourceForeground": "{{colors.outline.dark.hex}}",
//
// Peek, Hover, Widgets
//
"editorHoverWidget.background": "{{colors.surface_container_high.dark.hex}}",
"editorHoverWidget.border": "{{colors.outline.dark.hex}}",
"editorSuggestWidget.background": "{{colors.surface_container.dark.hex}}",
"editorSuggestWidget.foreground": "{{colors.on_surface.dark.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.surface_container_high.dark.hex}}",
"editorSuggestWidget.highlightForeground": "{{colors.primary.dark.hex}}",
//
// Scrollbar
//
"scrollbarSlider.background": "{{colors.outline.dark.hex}}40",
"scrollbarSlider.hoverBackground": "{{colors.outline.dark.hex}}60",
"scrollbarSlider.activeBackground": "{{colors.outline.dark.hex}}80",
"editorError.foreground": "{{colors.error.dark.hex}}",
"editorWarning.foreground": "{{colors.tertiary.dark.hex}}",
"editorInfo.foreground": "{{colors.primary.dark.hex}}",
"editorGutter.addedBackground": "{{colors.secondary.dark.hex}}",
"editorGutter.modifiedBackground": "{{colors.tertiary.dark.hex}}",
"editorGutter.deletedBackground": "{{colors.error.dark.hex}}",
"diffEditor.insertedTextBackground": "{{colors.secondary.dark.hex}}20",
"diffEditor.removedTextBackground": "{{colors.error.dark.hex}}20",
"merge.currentHeaderBackground": "{{colors.primary.dark.hex}}40",
"merge.incomingHeaderBackground": "{{colors.secondary.dark.hex}}40",
"menubar.selectionBackground": "{{colors.surface_container.dark.hex}}",
"menu.background": "{{colors.background.dark.hex}}",
"menu.foreground": "{{colors.on_surface.dark.hex}}",
"menu.selectionBackground": "{{colors.surface_container_high.dark.hex}}",
"menu.selectionForeground": "{{colors.on_surface.dark.hex}}",
"debugToolBar.background": "{{colors.background.dark.hex}}",
"debugExceptionWidget.background": "{{colors.background.dark.hex}}",
"debugExceptionWidget.border": "{{colors.error.dark.hex}}"
//
// Terminal (Dank16)
//
"terminal.background": "{{colors.background.dark.hex}}",
"terminal.foreground": "{{colors.on_surface.dark.hex}}",
"terminal.ansiBlack": "{{dank16.color0.dark.hex}}",
"terminal.ansiRed": "{{dank16.color1.dark.hex}}",
"terminal.ansiGreen": "{{dank16.color2.dark.hex}}",
"terminal.ansiYellow": "{{dank16.color3.dark.hex}}",
"terminal.ansiBlue": "{{dank16.color4.dark.hex}}",
"terminal.ansiMagenta": "{{dank16.color5.dark.hex}}",
"terminal.ansiCyan": "{{dank16.color6.dark.hex}}",
"terminal.ansiWhite": "{{dank16.color7.dark.hex}}",
"terminal.ansiBrightBlack": "{{dank16.color8.dark.hex}}",
"terminal.ansiBrightRed": "{{dank16.color9.dark.hex}}",
"terminal.ansiBrightGreen": "{{dank16.color10.dark.hex}}",
"terminal.ansiBrightYellow": "{{dank16.color11.dark.hex}}",
"terminal.ansiBrightBlue": "{{dank16.color12.dark.hex}}",
"terminal.ansiBrightMagenta": "{{dank16.color13.dark.hex}}",
"terminal.ansiBrightCyan": "{{dank16.color14.dark.hex}}",
"terminal.ansiBrightWhite": "{{dank16.color15.dark.hex}}"
},
//
// Token colors
//
"tokenColors": [
{
"scope": ["variable", "meta.object-literal.key"],
"scope": [
"comment"
],
"settings": {
"foreground": "{{colors.on_surface.dark.hex}}"
"foreground": "{{dank16.color8.dark.hex}}"
}
},
{
"scope": ["string", "constant.other.symbol"],
"scope": [
"keyword"
],
"settings": {
"foreground": "{{colors.secondary.dark.hex}}"
"foreground": "{{dank16.color5.dark.hex}}"
}
},
{
"scope": ["constant.numeric", "constant.language", "constant.character"],
"scope": [
"string"
],
"settings": {
"foreground": "{{colors.tertiary.dark.hex}}"
"foreground": "{{dank16.color3.dark.hex}}"
}
},
{
"scope": ["entity.name.type", "support.type", "entity.name.class"],
"scope": [
"constant",
"number"
],
"settings": {
"foreground": "{{colors.tertiary.dark.hex}}"
"foreground": "{{dank16.color12.dark.hex}}"
}
},
{
"scope": ["entity.name.function", "support.function"],
"scope": [
"variable"
],
"settings": {
"foreground": "{{colors.primary.dark.hex}}"
"foreground": "{{dank16.color15.dark.hex}}"
}
},
{
"scope": ["support.class", "support.variable", "variable.language"],
"scope": [
"entity.name.function"
],
"settings": {
"foreground": "{{colors.secondary.dark.hex}}"
"foreground": "{{dank16.color2.dark.hex}}"
}
},
{
"scope": ["invalid"],
"scope": [
"entity.name.class",
"support.type"
],
"settings": {
"foreground": "{{dank16.color12.dark.hex}}"
}
},
{
"scope": [
"invalid"
],
"settings": {
"foreground": "{{colors.error.dark.hex}}"
}
},
{
"scope": ["invalid.deprecated"],
"settings": {
"foreground": "{{colors.outline.dark.hex}}"
}
},
{
"scope": ["markup.heading"],
"scope": [
"markup.heading"
],
"settings": {
"foreground": "{{colors.primary.dark.hex}}",
"fontStyle": "bold"
}
},
{
"scope": ["markup.bold"],
"settings": {
"foreground": "{{colors.tertiary.dark.hex}}",
"fontStyle": "bold"
}
},
{
"scope": ["markup.italic"],
"settings": {
"foreground": "{{colors.primary.dark.hex}}",
"fontStyle": "italic"
}
},
{
"scope": ["markup.underline"],
"settings": {
"fontStyle": "underline"
}
},
{
"scope": ["markup.quote"],
"settings": {
"foreground": "{{colors.secondary.dark.hex}}"
}
},
{
"scope": ["markup.list"],
"settings": {
"foreground": "{{colors.on_surface.dark.hex}}"
}
},
{
"scope": ["markup.raw", "markup.inline.raw"],
"settings": {
"foreground": "{{colors.secondary.dark.hex}}"
}
},
{
"scope": ["comment", "punctuation.definition.comment"],
"settings": {
"foreground": "{{dank16.color8.hex}}"
}
},
{
"scope": "keyword",
"settings": {
"foreground": "{{dank16.color5.hex}}"
}
},
{
"scope": "storage.type",
"settings": {
"foreground": "{{dank16.color13.hex}}"
}
},
{
"scope": "storage.modifier",
"settings": {
"foreground": "{{dank16.color5.hex}}"
}
},
{
"scope": "variable",
"settings": {
"foreground": "{{dank16.color15.hex}}"
}
},
{
"scope": "variable.parameter",
"settings": {
"foreground": "{{dank16.color7.hex}}"
}
},
{
"scope": ["meta.object-literal.key", "meta.property.object", "variable.other.property"],
"settings": {
"foreground": "{{dank16.color4.hex}}"
}
},
{
"scope": "constant.other.symbol",
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": ["constant.numeric", "constant.language"],
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": "constant.character",
"settings": {
"foreground": "{{dank16.color3.hex}}"
}
},
{
"scope": ["entity.name.type", "entity.name.class"],
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": "support.type",
"settings": {
"foreground": "{{dank16.color13.hex}}"
}
},
{
"scope": ["entity.name.function", "support.function"],
"settings": {
"foreground": "{{dank16.color2.hex}}"
}
},
{
"scope": ["support.class", "support.variable"],
"settings": {
"foreground": "{{dank16.color15.hex}}"
}
},
{
"scope": "variable.language",
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": "entity.name.tag.yaml",
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": ["string.unquoted.plain.out.yaml", "string.unquoted.yaml"],
"settings": {
"foreground": "{{dank16.color15.hex}}"
}
},
{
"scope": "string",
"settings": {
"foreground": "{{dank16.color3.hex}}"
}
}
],
"semanticHighlighting": true,
//
// Semantic tokens
//
"semanticTokenColors": {
"variable": {
"foreground": "{{dank16.color15.hex}}"
},
"variable.readonly": {
"foreground": "{{dank16.color12.hex}}"
},
"property": {
"foreground": "{{dank16.color4.hex}}"
},
"function": {
"foreground": "{{dank16.color2.hex}}"
},
"method": {
"foreground": "{{dank16.color2.hex}}"
},
"type": {
"foreground": "{{dank16.color12.hex}}"
},
"class": {
"foreground": "{{dank16.color12.hex}}"
},
"typeParameter": {
"foreground": "{{dank16.color13.hex}}"
},
"enumMember": {
"foreground": "{{dank16.color12.hex}}"
},
"string": {
"foreground": "{{dank16.color3.hex}}"
},
"number": {
"foreground": "{{dank16.color12.hex}}"
},
"comment": {
"foreground": "{{dank16.color8.hex}}"
},
"keyword": {
"foreground": "{{dank16.color5.hex}}"
},
"operator": {
"foreground": "{{dank16.color15.hex}}"
"foreground": "{{dank16.color15.dark.hex}}"
},
"parameter": {
"foreground": "{{dank16.color7.hex}}"
"foreground": "{{dank16.color7.dark.hex}}"
},
"namespace": {
"foreground": "{{dank16.color15.hex}}"
"property": {
"foreground": "{{dank16.color4.dark.hex}}"
},
"function": {
"foreground": "{{dank16.color2.dark.hex}}"
},
"string": {
"foreground": "{{dank16.color3.dark.hex}}"
},
"number": {
"foreground": "{{dank16.color12.dark.hex}}"
},
"keyword": {
"foreground": "{{dank16.color5.dark.hex}}"
},
"comment": {
"foreground": "{{dank16.color8.dark.hex}}"
}
}
}
}
@@ -1,431 +1,251 @@
{
"$schema": "vscode://schemas/color-theme",
"name": "Dynamic Base16 DankShell",
"semanticHighlighting": true,
"colors": {
"foreground": "{{colors.on_surface.default.hex}}",
"editor.background": "{{colors.background.default.hex}}",
"editor.foreground": "{{colors.on_surface.default.hex}}",
"editorLineNumber.foreground": "{{colors.outline.default.hex}}",
"editorLineNumber.activeForeground": "{{colors.on_surface.default.hex}}",
"editorCursor.foreground": "{{colors.primary.default.hex}}",
"editor.selectionBackground": "{{colors.primary_container.default.hex}}",
"editor.inactiveSelectionBackground": "{{colors.background.default.hex}}",
"editor.lineHighlightBackground": "{{colors.background.default.hex}}",
"editorIndentGuide.background": "{{colors.background.default.hex}}",
"editorIndentGuide.activeBackground": "{{colors.outline.default.hex}}",
"editorWhitespace.foreground": "{{colors.outline_variant.default.hex}}",
"editorBracketMatch.background": "{{colors.background.default.hex}}",
"editorBracketMatch.border": "{{colors.primary.default.hex}}",
"errorForeground": "{{colors.error.default.hex}}",
"panel.border": "{{colors.outline_variant.default.hex}}",
"panelTitle.activeBorder": "{{colors.primary.default.hex}}",
"sideBar.border": "{{colors.outline_variant.default.hex}}",
"editorGroup.border": "{{colors.outline_variant.default.hex}}",
"tab.border": "{{colors.outline_variant.default.hex}}",
"titleBar.border": "{{colors.outline_variant.default.hex}}",
"focusBorder": "{{colors.primary.default.hex}}",
"selection.background": "{{colors.primary_container.default.hex}}66",
"titleBar.activeBackground": "{{colors.background.default.hex}}",
"titleBar.activeForeground": "{{colors.on_surface.default.hex}}",
"titleBar.inactiveForeground": "{{colors.outline.default.hex}}",
"activityBar.background": "{{colors.background.default.hex}}",
"activityBar.foreground": "{{colors.on_surface.default.hex}}",
"activityBar.inactiveForeground": "{{colors.outline.default.hex}}",
"activityBar.activeBorder": "{{colors.primary.default.hex}}",
"activityBar.activeBackground": "{{colors.background.default.hex}}",
"activityBarBadge.background": "{{colors.primary.default.hex}}",
"activityBarBadge.foreground": "{{colors.on_primary.default.hex}}",
"sideBar.background": "{{colors.background.default.hex}}",
"sideBar.background": "{{colors.surface_container_low.default.hex}}",
"sideBar.foreground": "{{colors.on_surface.default.hex}}",
"sideBar.border": "{{colors.background.default.hex}}",
"statusBarItem.hoverBackground": "{{colors.surface_container_low.default.hex}}",
"statusBarItem.activeBackground": "{{colors.surface_container.default.hex}}",
"sideBarTitle.foreground": "{{colors.on_surface.default.hex}}",
"sideBarSectionHeader.background": "{{colors.background.default.hex}}",
"panel.background": "{{colors.surface_container_low.default.hex}}",
"panelTitle.activeForeground": "{{colors.on_surface.default.hex}}",
"panelTitle.inactiveForeground": "{{colors.outline.default.hex}}",
"sideBarSectionHeader.background": "{{colors.surface_container.default.hex}}",
"sideBarSectionHeader.foreground": "{{colors.on_surface.default.hex}}",
"list.activeSelectionBackground": "{{colors.primary_container.default.hex}}",
"list.activeSelectionForeground": "{{colors.on_primary_container.default.hex}}",
"list.inactiveSelectionBackground": "{{colors.surface_container.default.hex}}",
"list.inactiveSelectionForeground": "{{colors.on_surface.default.hex}}",
"list.hoverBackground": "{{colors.surface_container.default.hex}}",
"list.hoverForeground": "{{colors.on_surface.default.hex}}",
"list.focusBackground": "{{colors.surface_container_high.default.hex}}",
"list.focusForeground": "{{colors.on_surface.default.hex}}",
"list.highlightForeground": "{{colors.primary.default.hex}}",
"statusBar.background": "{{colors.background.default.hex}}",
"statusBar.foreground": "{{colors.on_surface.default.hex}}",
"statusBar.border": "{{colors.background.default.hex}}",
"statusBar.noFolderBackground": "{{colors.background.default.hex}}",
"statusBar.debuggingBackground": "{{colors.error.default.hex}}",
"statusBar.debuggingForeground": "{{colors.on_error.default.hex}}",
"tab.activeBackground": "{{colors.background.default.hex}}",
"editorGroupHeader.tabsBackground": "{{colors.background.default.hex}}",
"tab.activeBackground": "{{colors.surface_container_low.default.hex}}",
"tab.inactiveBackground": "{{colors.background.default.hex}}",
"tab.activeForeground": "{{colors.on_surface.default.hex}}",
"tab.inactiveForeground": "{{colors.outline.default.hex}}",
"tab.border": "{{colors.background.default.hex}}",
"tab.activeBorder": "{{colors.primary.default.hex}}",
"tab.unfocusedActiveBorder": "{{colors.outline.default.hex}}",
"editorGroupHeader.tabsBackground": "{{colors.background.default.hex}}",
"editorGroupHeader.noTabsBackground": "{{colors.background.default.hex}}",
"titleBar.activeBackground": "{{colors.background.default.hex}}",
"titleBar.activeForeground": "{{colors.on_surface.default.hex}}",
"titleBar.inactiveBackground": "{{colors.background.default.hex}}",
"titleBar.inactiveForeground": "{{colors.outline.default.hex}}",
"titleBar.border": "{{colors.background.default.hex}}",
"input.background": "{{colors.background.default.hex}}",
"tab.activeBorderTop": "{{colors.primary.default.hex}}",
"list.activeSelectionBackground": "{{colors.primary_container.default.hex}}",
"list.activeSelectionForeground": "{{colors.on_primary_container.default.hex}}",
"list.inactiveSelectionBackground": "{{colors.surface_container.default.hex}}",
"list.hoverBackground": "{{colors.surface_container.default.hex}}",
"list.hoverForeground": "{{colors.on_surface.default.hex}}",
"list.focusForeground": "{{colors.on_surface.default.hex}}",
"list.focusOutline": "{{colors.primary.default.hex}}",
"list.focusBackground": "{{colors.surface_container_high.default.hex}}",
"list.highlightForeground": "{{colors.primary.default.hex}}",
"list.errorForeground": "{{colors.error.default.hex}}",
"list.warningForeground": "{{colors.secondary.default.hex}}",
"input.background": "{{colors.surface_container_low.default.hex}}",
"input.foreground": "{{colors.on_surface.default.hex}}",
"input.border": "{{colors.outline.default.hex}}",
"input.border": "{{colors.outline_variant.default.hex}}",
"input.placeholderForeground": "{{colors.outline.default.hex}}",
"inputOption.activeBorder": "{{colors.primary.default.hex}}",
"inputValidation.errorBackground": "{{colors.error.default.hex}}",
"inputValidation.errorBorder": "{{colors.error.default.hex}}",
"dropdown.background": "{{colors.background.default.hex}}",
"dropdown.background": "{{colors.surface_container_low.default.hex}}",
"dropdown.foreground": "{{colors.on_surface.default.hex}}",
"dropdown.border": "{{colors.outline.default.hex}}",
"quickInput.background": "{{colors.background.default.hex}}",
"dropdown.border": "{{colors.outline_variant.default.hex}}",
"quickInput.background": "{{colors.surface_container_low.default.hex}}",
"quickInput.foreground": "{{colors.on_surface.default.hex}}",
"quickInputList.focusBackground": "{{colors.surface_container_high.default.hex}}",
"quickInputList.focusForeground": "{{colors.on_surface.default.hex}}",
"widget.shadow": "{{colors.background.default.hex}}80",
"button.background": "{{colors.primary.default.hex}}",
"button.foreground": "{{colors.on_primary.default.hex}}",
"button.hoverBackground": "{{colors.primary_container.default.hex}}",
"focusBorder": "{{colors.primary.default.hex}}",
"badge.background": "{{colors.secondary.default.hex}}",
"badge.foreground": "{{colors.on_secondary.default.hex}}",
"panel.background": "{{colors.background.default.hex}}",
"panel.border": "{{colors.primary.default.hex}}",
"panelTitle.activeBorder": "{{colors.primary.default.hex}}",
"panelTitle.activeForeground": "{{colors.on_surface.default.hex}}",
"panelTitle.inactiveForeground": "{{colors.outline.default.hex}}",
"terminal.background": "{{colors.background.default.hex}}",
"terminal.foreground": "{{colors.on_surface.default.hex}}",
"terminal.ansiBlack": "{{dank16.color0.hex}}",
"terminal.ansiRed": "{{dank16.color1.hex}}",
"terminal.ansiGreen": "{{dank16.color2.hex}}",
"terminal.ansiYellow": "{{dank16.color3.hex}}",
"terminal.ansiBlue": "{{dank16.color4.hex}}",
"terminal.ansiMagenta": "{{dank16.color5.hex}}",
"terminal.ansiCyan": "{{dank16.color6.hex}}",
"terminal.ansiWhite": "{{dank16.color7.hex}}",
"terminal.ansiBrightBlack": "{{dank16.color8.hex}}",
"terminal.ansiBrightRed": "{{dank16.color9.hex}}",
"terminal.ansiBrightGreen": "{{dank16.color10.hex}}",
"terminal.ansiBrightYellow": "{{dank16.color11.hex}}",
"terminal.ansiBrightBlue": "{{dank16.color12.hex}}",
"terminal.ansiBrightMagenta": "{{dank16.color13.hex}}",
"terminal.ansiBrightCyan": "{{dank16.color14.hex}}",
"terminal.ansiBrightWhite": "{{dank16.color15.hex}}",
"gitDecoration.modifiedResourceForeground": "{{colors.primary.default.hex}}",
"gitDecoration.addedResourceForeground": "{{colors.primary.default.hex}}",
"gitDecoration.stageModifiedResourceForeground": "{{colors.primary.default.hex}}",
"gitDecoration.stageDeletedResourceForeground": "{{colors.error.default.hex}}",
"gitDecoration.deletedResourceForeground": "{{colors.error.default.hex}}",
"gitDecoration.untrackedResourceForeground": "{{colors.secondary.default.hex}}",
"gitDecoration.ignoredResourceForeground": "{{colors.outline.default.hex}}",
"gitDecoration.conflictingResourceForeground": "{{colors.error_container.default.hex}}",
"gitDecoration.submoduleResourceForeground": "{{colors.primary.default.hex}}",
"editorWidget.background": "{{colors.background.default.hex}}",
"editorWidget.border": "{{colors.outline.default.hex}}",
"editorSuggestWidget.background": "{{colors.background.default.hex}}",
"editorSuggestWidget.border": "{{colors.outline.default.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.surface_container_high.default.hex}}",
"editorSuggestWidget.highlightForeground": "{{colors.primary.default.hex}}",
"peekView.border": "{{colors.primary.default.hex}}",
"peekViewEditor.background": "{{colors.background.default.hex}}",
"peekViewResult.background": "{{colors.background.default.hex}}",
"peekViewTitle.background": "{{colors.background.default.hex}}",
"notificationCenter.border": "{{colors.outline.default.hex}}",
"notifications.background": "{{colors.background.default.hex}}",
"notifications.border": "{{colors.outline.default.hex}}",
"statusBar.background": "{{colors.background.default.hex}}",
"statusBar.foreground": "{{colors.on_surface.default.hex}}",
"statusBar.border": "{{colors.outline_variant.default.hex}}",
"statusBar.noFolderBackground": "{{colors.background.default.hex}}",
"statusBar.noFolderForeground": "{{colors.on_surface.default.hex}}",
"statusBar.debuggingBackground": "{{colors.error.default.hex}}",
"statusBar.debuggingForeground": "{{colors.on_error.default.hex}}",
"notificationCenterHeader.background": "{{colors.surface_container_low.default.hex}}",
"notificationCenterHeader.foreground": "{{colors.on_surface.default.hex}}",
"notificationCenter.border": "{{colors.outline_variant.default.hex}}",
"notifications.background": "{{colors.surface_container_low.default.hex}}",
"notifications.foreground": "{{colors.on_surface.default.hex}}",
"notifications.border": "{{colors.outline_variant.default.hex}}",
"notificationsErrorIcon.foreground": "{{colors.error.default.hex}}",
"notificationsWarningIcon.foreground": "{{colors.secondary.default.hex}}",
"notificationsInfoIcon.foreground": "{{colors.primary.default.hex}}",
"breadcrumb.background": "{{colors.surface_container.default.hex}}",
"breadcrumb.foreground": "{{colors.outline.default.hex}}",
"breadcrumb.focusForeground": "{{colors.on_surface.default.hex}}",
"breadcrumb.activeSelectionForeground": "{{colors.primary.default.hex}}",
"editorLineNumber.foreground": "{{colors.outline.default.hex}}",
"editorLineNumber.activeForeground": "{{colors.primary.default.hex}}",
"editorCursor.foreground": "{{colors.primary.default.hex}}",
"editor.lineHighlightBackground": "{{colors.surface_container.default.hex}}66",
"editor.wordHighlightBackground": "{{colors.secondary.default.hex}}22",
"editor.wordHighlightStrongBackground": "{{colors.tertiary.default.hex}}22",
"editor.selectionBackground": "{{colors.primary_container.default.hex}}66",
"editor.inactiveSelectionBackground": "{{colors.primary_container.default.hex}}33",
"editorWhitespace.foreground": "{{colors.outline.default.hex}}66",
"editorIndentGuide.background1": "{{colors.outline.default.hex}}33",
"editorIndentGuide.activeBackground1": "{{colors.primary.default.hex}}99",
"editorOverviewRuler.border": "{{colors.outline_variant.default.hex}}",
"editorOverviewRuler.errorForeground": "{{colors.error.default.hex}}88",
"editorOverviewRuler.warningForeground": "{{colors.tertiary.default.hex}}88",
"editorOverviewRuler.infoForeground": "{{colors.primary.default.hex}}88",
"editorStickyScroll.background": "{{colors.background.default.hex}}",
"editorStickyScrollHover.background": "{{colors.surface_container_low.default.hex}}",
"editorBracketHighlight.unexpectedBracket.foreground": "{{colors.outline.default.hex}}",
"diffEditor.insertedTextBackground": "{{colors.secondary.default.hex}}20",
"diffEditor.removedTextBackground": "{{colors.error.default.hex}}20",
"gitDecoration.modifiedResourceForeground": "{{colors.primary.default.hex}}",
"gitDecoration.addedResourceForeground": "{{colors.secondary.default.hex}}",
"gitDecoration.deletedResourceForeground": "{{colors.error.default.hex}}",
"gitDecoration.ignoredResourceForeground": "{{colors.outline.default.hex}}",
"editorHoverWidget.background": "{{colors.surface_container_high.default.hex}}",
"editorHoverWidget.border": "{{colors.outline.default.hex}}",
"editorSuggestWidget.background": "{{colors.surface_container.default.hex}}",
"editorSuggestWidget.foreground": "{{colors.on_surface.default.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.surface_container_high.default.hex}}",
"editorSuggestWidget.highlightForeground": "{{colors.primary.default.hex}}",
"scrollbarSlider.background": "{{colors.outline.default.hex}}40",
"scrollbarSlider.hoverBackground": "{{colors.outline.default.hex}}60",
"scrollbarSlider.activeBackground": "{{colors.outline.default.hex}}80",
"editorError.foreground": "{{colors.error.default.hex}}",
"editorWarning.foreground": "{{colors.tertiary.default.hex}}",
"editorInfo.foreground": "{{colors.primary.default.hex}}",
"editorGutter.addedBackground": "{{colors.secondary.default.hex}}",
"editorGutter.modifiedBackground": "{{colors.tertiary.default.hex}}",
"editorGutter.deletedBackground": "{{colors.error.default.hex}}",
"diffEditor.insertedTextBackground": "{{colors.secondary.default.hex}}20",
"diffEditor.removedTextBackground": "{{colors.error.default.hex}}20",
"merge.currentHeaderBackground": "{{colors.primary.default.hex}}40",
"merge.incomingHeaderBackground": "{{colors.secondary.default.hex}}40",
"menubar.selectionBackground": "{{colors.surface_container.default.hex}}",
"menu.background": "{{colors.background.default.hex}}",
"menu.foreground": "{{colors.on_surface.default.hex}}",
"menu.selectionBackground": "{{colors.surface_container_high.default.hex}}",
"menu.selectionForeground": "{{colors.on_surface.default.hex}}",
"debugToolBar.background": "{{colors.background.default.hex}}",
"debugExceptionWidget.background": "{{colors.background.default.hex}}",
"debugExceptionWidget.border": "{{colors.error.default.hex}}"
"terminal.background": "{{colors.background.default.hex}}",
"terminal.foreground": "{{colors.on_surface.default.hex}}",
"terminal.ansiBlack": "{{dank16.color0.default.hex}}",
"terminal.ansiRed": "{{dank16.color1.default.hex}}",
"terminal.ansiGreen": "{{dank16.color2.default.hex}}",
"terminal.ansiYellow": "{{dank16.color3.default.hex}}",
"terminal.ansiBlue": "{{dank16.color4.default.hex}}",
"terminal.ansiMagenta": "{{dank16.color5.default.hex}}",
"terminal.ansiCyan": "{{dank16.color6.default.hex}}",
"terminal.ansiWhite": "{{dank16.color7.default.hex}}",
"terminal.ansiBrightBlack": "{{dank16.color8.default.hex}}",
"terminal.ansiBrightRed": "{{dank16.color9.default.hex}}",
"terminal.ansiBrightGreen": "{{dank16.color10.default.hex}}",
"terminal.ansiBrightYellow": "{{dank16.color11.default.hex}}",
"terminal.ansiBrightBlue": "{{dank16.color12.default.hex}}",
"terminal.ansiBrightMagenta": "{{dank16.color13.default.hex}}",
"terminal.ansiBrightCyan": "{{dank16.color14.default.hex}}",
"terminal.ansiBrightWhite": "{{dank16.color15.default.hex}}"
},
"tokenColors": [
{
"scope": ["variable", "meta.object-literal.key"],
"settings": {
"foreground": "{{colors.on_surface.default.hex}}"
}
},
{
"scope": ["string", "constant.other.symbol"],
"settings": {
"foreground": "{{colors.secondary.default.hex}}"
}
},
{
"scope": ["constant.numeric", "constant.language", "constant.character"],
"settings": {
"foreground": "{{colors.tertiary.default.hex}}"
}
},
{
"scope": ["entity.name.type", "support.type", "entity.name.class"],
"settings": {
"foreground": "{{colors.tertiary.default.hex}}"
}
},
{
"scope": ["entity.name.function", "support.function"],
"settings": {
"foreground": "{{colors.primary.default.hex}}"
}
},
{
"scope": ["support.class", "support.variable", "variable.language"],
"settings": {
"foreground": "{{colors.secondary.default.hex}}"
}
},
{
"scope": ["invalid"],
"settings": {
"foreground": "{{colors.error.default.hex}}"
}
},
{
"scope": ["invalid.deprecated"],
"scope": [
"comment"
],
"settings": {
"foreground": "{{colors.outline.default.hex}}"
}
},
{
"scope": ["markup.heading"],
"scope": [
"keyword"
],
"settings": {
"foreground": "{{colors.primary.default.hex}}",
"fontStyle": "bold"
"foreground": "{{dank16.color5.default.hex}}"
}
},
{
"scope": ["markup.bold"],
"scope": [
"string"
],
"settings": {
"foreground": "{{colors.tertiary.default.hex}}",
"fontStyle": "bold"
"foreground": "{{dank16.color2.default.hex}}"
}
},
{
"scope": ["markup.italic"],
"scope": [
"constant",
"number"
],
"settings": {
"foreground": "{{colors.primary.default.hex}}",
"fontStyle": "italic"
"foreground": "{{dank16.color12.default.hex}}"
}
},
{
"scope": ["markup.underline"],
"settings": {
"fontStyle": "underline"
}
},
{
"scope": ["markup.quote"],
"settings": {
"foreground": "{{colors.secondary.default.hex}}"
}
},
{
"scope": ["markup.list"],
"scope": [
"variable"
],
"settings": {
"foreground": "{{colors.on_surface.default.hex}}"
}
},
{
"scope": ["markup.raw", "markup.inline.raw"],
"scope": [
"entity.name.function"
],
"settings": {
"foreground": "{{dank16.color4.default.hex}}"
}
},
{
"scope": [
"entity.name.class",
"support.type"
],
"settings": {
"foreground": "{{colors.secondary.default.hex}}"
}
},
{
"scope": ["comment", "punctuation.definition.comment"],
"scope": [
"invalid"
],
"settings": {
"foreground": "{{dank16.color8.hex}}"
"foreground": "{{colors.error.default.hex}}"
}
},
{
"scope": "keyword",
"scope": [
"markup.heading"
],
"settings": {
"foreground": "{{dank16.color5.hex}}"
}
},
{
"scope": "storage.type",
"settings": {
"foreground": "{{dank16.color13.hex}}"
}
},
{
"scope": "storage.modifier",
"settings": {
"foreground": "{{dank16.color5.hex}}"
}
},
{
"scope": "variable",
"settings": {
"foreground": "{{dank16.color15.hex}}"
}
},
{
"scope": "variable.parameter",
"settings": {
"foreground": "{{dank16.color7.hex}}"
}
},
{
"scope": ["meta.object-literal.key", "meta.property.object", "variable.other.property"],
"settings": {
"foreground": "{{dank16.color4.hex}}"
}
},
{
"scope": "constant.other.symbol",
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": ["constant.numeric", "constant.language"],
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": "constant.character",
"settings": {
"foreground": "{{dank16.color3.hex}}"
}
},
{
"scope": ["entity.name.type", "entity.name.class"],
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": "support.type",
"settings": {
"foreground": "{{dank16.color13.hex}}"
}
},
{
"scope": ["entity.name.function", "support.function"],
"settings": {
"foreground": "{{dank16.color2.hex}}"
}
},
{
"scope": ["support.class", "support.variable"],
"settings": {
"foreground": "{{dank16.color15.hex}}"
}
},
{
"scope": "variable.language",
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": "entity.name.tag.yaml",
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": ["string.unquoted.plain.out.yaml", "string.unquoted.yaml"],
"settings": {
"foreground": "{{dank16.color15.hex}}"
}
},
{
"scope": "string",
"settings": {
"foreground": "{{dank16.color3.hex}}"
"foreground": "{{colors.primary.default.hex}}",
"fontStyle": "bold"
}
}
],
"semanticHighlighting": true,
"semanticTokenColors": {
"variable": {
"foreground": "{{dank16.color15.hex}}"
},
"variable.readonly": {
"foreground": "{{dank16.color12.hex}}"
},
"property": {
"foreground": "{{dank16.color4.hex}}"
},
"function": {
"foreground": "{{dank16.color2.hex}}"
},
"method": {
"foreground": "{{dank16.color2.hex}}"
},
"type": {
"foreground": "{{dank16.color12.hex}}"
},
"class": {
"foreground": "{{dank16.color12.hex}}"
},
"typeParameter": {
"foreground": "{{dank16.color13.hex}}"
},
"enumMember": {
"foreground": "{{dank16.color12.hex}}"
},
"string": {
"foreground": "{{dank16.color3.hex}}"
},
"number": {
"foreground": "{{dank16.color12.hex}}"
},
"comment": {
"foreground": "{{dank16.color8.hex}}"
},
"keyword": {
"foreground": "{{dank16.color5.hex}}"
},
"operator": {
"foreground": "{{dank16.color15.hex}}"
"foreground": "{{colors.on_surface.default.hex}}"
},
"parameter": {
"foreground": "{{dank16.color7.hex}}"
"foreground": "{{colors.on_surface.default.hex}}"
},
"namespace": {
"foreground": "{{dank16.color15.hex}}"
"property": {
"foreground": "{{dank16.color4.default.hex}}"
},
"function": {
"foreground": "{{dank16.color4.default.hex}}"
},
"method": {
"foreground": "{{dank16.color4.default.hex}}"
},
"type": {
"foreground": "{{colors.secondary.default.hex}}"
},
"class": {
"foreground": "{{colors.secondary.default.hex}}"
},
"string": {
"foreground": "{{dank16.color2.default.hex}}"
},
"number": {
"foreground": "{{dank16.color12.default.hex}}"
},
"keyword": {
"foreground": "{{dank16.color5.default.hex}}"
},
"comment": {
"foreground": "{{colors.outline.default.hex}}"
}
}
}
@@ -1,35 +1,74 @@
{
"$schema": "vscode://schemas/color-theme",
"name": "Dynamic Base16 DankShell",
"name": "Dynamic Base16 DankShell Light",
"semanticHighlighting": true,
"colors": {
"foreground": "{{colors.on_surface.light.hex}}",
"selection.background": "{{colors.primary_container.light.hex}}66",
"errorForeground": "{{colors.error.light.hex}}",
"focusBorder": "{{colors.primary.light.hex}}",
"editor.background": "{{colors.background.light.hex}}",
"editor.foreground": "{{colors.on_surface.light.hex}}",
"editorLineNumber.foreground": "{{colors.outline.light.hex}}",
"editorLineNumber.activeForeground": "{{colors.on_surface.light.hex}}",
"editorLineNumber.activeForeground": "{{colors.primary.light.hex}}",
"editorCursor.foreground": "{{colors.primary.light.hex}}",
"editor.selectionBackground": "{{colors.primary_container.light.hex}}",
"editor.inactiveSelectionBackground": "{{colors.background.light.hex}}",
"editor.lineHighlightBackground": "{{colors.background.light.hex}}",
"editorIndentGuide.background": "{{colors.background.light.hex}}",
"editorIndentGuide.activeBackground": "{{colors.outline.light.hex}}",
"editorWhitespace.foreground": "{{colors.outline_variant.light.hex}}",
"editorBracketMatch.background": "{{colors.background.light.hex}}",
"editor.selectionBackground": "{{colors.primary_container.light.hex}}B3",
"editor.inactiveSelectionBackground": "{{colors.primary_container.light.hex}}33",
"editor.lineHighlightBackground": "{{colors.surface_container.light.hex}}66",
"editor.lineHighlightBorder": "{{colors.outline_variant.light.hex}}18",
"editor.wordHighlightBackground": "{{colors.secondary.light.hex}}22",
"editor.wordHighlightStrongBackground": "{{colors.tertiary.light.hex}}22",
"editor.findMatchBackground": "{{colors.secondary.light.hex}}33",
"editor.findMatchHighlightBackground": "{{colors.secondary.light.hex}}22",
"editor.findRangeHighlightBackground": "{{colors.surface_container.light.hex}}33",
"editorWhitespace.foreground": "{{colors.outline.light.hex}}66",
"editorIndentGuide.background1": "{{colors.outline.light.hex}}99",
"editorIndentGuide.activeBackground1": "{{colors.primary.light.hex}}CC",
"editorBracketMatch.background": "{{colors.primary_container.light.hex}}33",
"editorBracketMatch.border": "{{colors.primary.light.hex}}",
"editorGutter.addedBackground": "{{colors.secondary.light.hex}}AA",
"editorGutter.modifiedBackground": "{{colors.tertiary.light.hex}}AA",
"editorGutter.deletedBackground": "{{colors.error.light.hex}}AA",
"editorError.foreground": "{{colors.error.light.hex}}",
"editorWarning.foreground": "{{colors.tertiary.light.hex}}",
"editorInfo.foreground": "{{colors.primary.light.hex}}",
"editorOverviewRuler.border": "{{colors.outline_variant.light.hex}}",
"editorOverviewRuler.errorForeground": "{{colors.error.light.hex}}88",
"editorOverviewRuler.warningForeground": "{{colors.tertiary.light.hex}}88",
"editorOverviewRuler.infoForeground": "{{colors.primary.light.hex}}88",
"editorWidget.background": "{{colors.surface_container.light.hex}}",
"editorWidget.border": "{{colors.outline_variant.light.hex}}",
"editorHoverWidget.background": "{{colors.surface_container_high.light.hex}}",
"editorHoverWidget.border": "{{colors.outline.light.hex}}",
"editorSuggestWidget.background": "{{colors.surface_container.light.hex}}",
"editorSuggestWidget.foreground": "{{colors.on_surface.light.hex}}",
"editorSuggestWidget.border": "{{colors.outline_variant.light.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.surface_container_high.light.hex}}",
"editorSuggestWidget.highlightForeground": "{{colors.primary.light.hex}}",
"editorGroup.border": "{{colors.outline_variant.light.hex}}",
"editorGroup.dropBackground": "{{colors.primary_container.light.hex}}33",
"editorGroupHeader.tabsBackground": "{{colors.background.light.hex}}",
"editorGroupHeader.noTabsBackground": "{{colors.background.light.hex}}",
"tab.border": "{{colors.outline_variant.light.hex}}",
"tab.activeBackground": "{{colors.surface_container_high.light.hex}}",
"tab.inactiveBackground": "{{colors.surface_container.light.hex}}",
"tab.activeForeground": "{{colors.on_surface.light.hex}}",
"tab.inactiveForeground": "{{colors.outline.light.hex}}",
"tab.activeBorder": "{{colors.primary.light.hex}}",
"tab.unfocusedActiveBorder": "{{colors.outline.light.hex}}",
"activityBar.background": "{{colors.background.light.hex}}",
"activityBar.foreground": "{{colors.on_surface.light.hex}}",
"activityBar.inactiveForeground": "{{colors.outline.light.hex}}",
"activityBar.activeBorder": "{{colors.primary.light.hex}}",
"activityBar.activeBackground": "{{colors.background.light.hex}}",
"activityBarBadge.background": "{{colors.primary.light.hex}}",
"activityBarBadge.foreground": "{{colors.on_primary.light.hex}}",
"sideBar.background": "{{colors.background.light.hex}}",
"sideBar.background": "{{colors.surface_container.light.hex}}",
"sideBar.foreground": "{{colors.on_surface.light.hex}}",
"sideBar.border": "{{colors.background.light.hex}}",
"sideBar.border": "{{colors.outline_variant.light.hex}}",
"sideBarTitle.foreground": "{{colors.on_surface.light.hex}}",
"sideBarSectionHeader.background": "{{colors.background.light.hex}}",
"sideBarSectionHeader.background": "{{colors.surface_container_low.light.hex}}",
"sideBarSectionHeader.foreground": "{{colors.on_surface.light.hex}}",
"list.activeSelectionBackground": "{{colors.primary_container.light.hex}}",
"list.activeSelectionForeground": "{{colors.on_primary_container.light.hex}}",
"list.inactiveSelectionBackground": "{{colors.surface_container.light.hex}}",
@@ -39,83 +78,64 @@
"list.focusBackground": "{{colors.surface_container_high.light.hex}}",
"list.focusForeground": "{{colors.on_surface.light.hex}}",
"list.highlightForeground": "{{colors.primary.light.hex}}",
"statusBar.background": "{{colors.background.light.hex}}",
"list.errorForeground": "{{colors.error.light.hex}}",
"list.warningForeground": "{{colors.tertiary.light.hex}}",
"statusBar.background": "{{colors.surface_container.light.hex}}",
"statusBar.foreground": "{{colors.on_surface.light.hex}}",
"statusBar.border": "{{colors.background.light.hex}}",
"statusBar.noFolderBackground": "{{colors.background.light.hex}}",
"statusBar.border": "{{colors.outline_variant.light.hex}}",
"statusBar.noFolderBackground": "{{colors.surface_container.light.hex}}",
"statusBar.noFolderForeground": "{{colors.on_surface.light.hex}}",
"statusBar.debuggingBackground": "{{colors.error.light.hex}}",
"statusBar.debuggingForeground": "{{colors.on_error.light.hex}}",
"tab.activeBackground": "{{colors.background.light.hex}}",
"tab.inactiveBackground": "{{colors.background.light.hex}}",
"tab.activeForeground": "{{colors.on_surface.light.hex}}",
"tab.inactiveForeground": "{{colors.outline.light.hex}}",
"tab.border": "{{colors.background.light.hex}}",
"tab.activeBorder": "{{colors.primary.light.hex}}",
"tab.unfocusedActiveBorder": "{{colors.outline.light.hex}}",
"editorGroupHeader.tabsBackground": "{{colors.background.light.hex}}",
"editorGroupHeader.noTabsBackground": "{{colors.background.light.hex}}",
"titleBar.activeBackground": "{{colors.background.light.hex}}",
"titleBar.activeForeground": "{{colors.on_surface.light.hex}}",
"titleBar.inactiveBackground": "{{colors.background.light.hex}}",
"titleBar.inactiveForeground": "{{colors.outline.light.hex}}",
"titleBar.border": "{{colors.background.light.hex}}",
"input.background": "{{colors.background.light.hex}}",
"titleBar.border": "{{colors.outline_variant.light.hex}}",
"input.background": "{{colors.surface_container_low.light.hex}}",
"input.foreground": "{{colors.on_surface.light.hex}}",
"input.border": "{{colors.outline.light.hex}}",
"input.border": "{{colors.outline_variant.light.hex}}",
"input.placeholderForeground": "{{colors.outline.light.hex}}",
"inputOption.activeBorder": "{{colors.primary.light.hex}}",
"inputValidation.errorBackground": "{{colors.error.light.hex}}",
"inputValidation.errorBackground": "{{colors.error_container.light.hex}}",
"inputValidation.errorBorder": "{{colors.error.light.hex}}",
"dropdown.background": "{{colors.background.light.hex}}",
"dropdown.background": "{{colors.surface_container_low.light.hex}}",
"dropdown.foreground": "{{colors.on_surface.light.hex}}",
"dropdown.border": "{{colors.outline.light.hex}}",
"quickInput.background": "{{colors.background.light.hex}}",
"dropdown.border": "{{colors.outline_variant.light.hex}}",
"quickInput.background": "{{colors.surface_container.light.hex}}",
"quickInput.foreground": "{{colors.on_surface.light.hex}}",
"quickInputList.focusBackground": "{{colors.surface_container_high.light.hex}}",
"quickInputList.focusForeground": "{{colors.on_surface.light.hex}}",
"button.background": "{{colors.primary.light.hex}}",
"button.foreground": "{{colors.on_primary.light.hex}}",
"button.hoverBackground": "{{colors.primary_container.light.hex}}",
"focusBorder": "{{colors.primary.light.hex}}",
"badge.background": "{{colors.secondary.light.hex}}",
"badge.foreground": "{{colors.on_secondary.light.hex}}",
"panel.background": "{{colors.background.light.hex}}",
"panel.border": "{{colors.primary.light.hex}}",
"panel.background": "{{colors.surface_container.light.hex}}",
"panel.border": "{{colors.outline_variant.light.hex}}",
"panelTitle.activeBorder": "{{colors.primary.light.hex}}",
"panelTitle.activeForeground": "{{colors.on_surface.light.hex}}",
"panelTitle.inactiveForeground": "{{colors.outline.light.hex}}",
"terminal.background": "{{colors.background.light.hex}}",
"terminal.foreground": "{{colors.on_surface.light.hex}}",
"terminal.ansiBlack": "{{dank16.color0.hex}}",
"terminal.ansiRed": "{{dank16.color1.hex}}",
"terminal.ansiGreen": "{{dank16.color2.hex}}",
"terminal.ansiYellow": "{{dank16.color3.hex}}",
"terminal.ansiBlue": "{{dank16.color4.hex}}",
"terminal.ansiMagenta": "{{dank16.color5.hex}}",
"terminal.ansiCyan": "{{dank16.color6.hex}}",
"terminal.ansiWhite": "{{dank16.color7.hex}}",
"terminal.ansiBrightBlack": "{{dank16.color8.hex}}",
"terminal.ansiBrightRed": "{{dank16.color9.hex}}",
"terminal.ansiBrightGreen": "{{dank16.color10.hex}}",
"terminal.ansiBrightYellow": "{{dank16.color11.hex}}",
"terminal.ansiBrightBlue": "{{dank16.color12.hex}}",
"terminal.ansiBrightMagenta": "{{dank16.color13.hex}}",
"terminal.ansiBrightCyan": "{{dank16.color14.hex}}",
"terminal.ansiBrightWhite": "{{dank16.color15.hex}}",
"terminal.ansiBlack": "{{dank16.color0.light.hex}}",
"terminal.ansiRed": "{{dank16.color1.light.hex}}",
"terminal.ansiGreen": "{{dank16.color2.light.hex}}",
"terminal.ansiYellow": "{{dank16.color3.light.hex}}",
"terminal.ansiBlue": "{{dank16.color4.light.hex}}",
"terminal.ansiMagenta": "{{dank16.color5.light.hex}}",
"terminal.ansiCyan": "{{dank16.color6.light.hex}}",
"terminal.ansiWhite": "{{dank16.color7.light.hex}}",
"terminal.ansiBrightBlack": "{{dank16.color8.light.hex}}",
"terminal.ansiBrightRed": "{{dank16.color9.light.hex}}",
"terminal.ansiBrightGreen": "{{dank16.color10.light.hex}}",
"terminal.ansiBrightYellow": "{{dank16.color11.light.hex}}",
"terminal.ansiBrightBlue": "{{dank16.color12.light.hex}}",
"terminal.ansiBrightMagenta": "{{dank16.color13.light.hex}}",
"terminal.ansiBrightCyan": "{{dank16.color14.light.hex}}",
"terminal.ansiBrightWhite": "{{dank16.color15.light.hex}}",
"gitDecoration.modifiedResourceForeground": "{{colors.primary.light.hex}}",
"gitDecoration.addedResourceForeground": "{{colors.primary.light.hex}}",
"gitDecoration.addedResourceForeground": "{{colors.secondary.light.hex}}",
"gitDecoration.stageModifiedResourceForeground": "{{colors.primary.light.hex}}",
"gitDecoration.stageDeletedResourceForeground": "{{colors.error.light.hex}}",
"gitDecoration.deletedResourceForeground": "{{colors.error.light.hex}}",
@@ -123,309 +143,276 @@
"gitDecoration.ignoredResourceForeground": "{{colors.outline.light.hex}}",
"gitDecoration.conflictingResourceForeground": "{{colors.error_container.light.hex}}",
"gitDecoration.submoduleResourceForeground": "{{colors.primary.light.hex}}",
"editorWidget.background": "{{colors.background.light.hex}}",
"editorWidget.border": "{{colors.outline.light.hex}}",
"editorSuggestWidget.background": "{{colors.background.light.hex}}",
"editorSuggestWidget.border": "{{colors.outline.light.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.surface_container_high.light.hex}}",
"editorSuggestWidget.highlightForeground": "{{colors.primary.light.hex}}",
"peekView.border": "{{colors.primary.light.hex}}",
"peekViewEditor.background": "{{colors.background.light.hex}}",
"peekViewResult.background": "{{colors.background.light.hex}}",
"peekViewTitle.background": "{{colors.background.light.hex}}",
"notificationCenter.border": "{{colors.outline.light.hex}}",
"notifications.background": "{{colors.background.light.hex}}",
"notifications.border": "{{colors.outline.light.hex}}",
"peekViewEditor.background": "{{colors.surface_container_high.light.hex}}",
"peekViewResult.background": "{{colors.surface_container.light.hex}}",
"peekViewTitle.background": "{{colors.surface_container_low.light.hex}}",
"notificationCenter.border": "{{colors.outline_variant.light.hex}}",
"notifications.background": "{{colors.surface_container.light.hex}}",
"notifications.border": "{{colors.outline_variant.light.hex}}",
"breadcrumb.background": "{{colors.surface_container_high.light.hex}}",
"breadcrumb.foreground": "{{colors.outline.light.hex}}",
"breadcrumb.focusForeground": "{{colors.on_surface.light.hex}}",
"breadcrumb.activeSelectionForeground": "{{colors.primary.light.hex}}",
"scrollbarSlider.background": "{{colors.outline.light.hex}}40",
"scrollbarSlider.hoverBackground": "{{colors.outline.light.hex}}60",
"scrollbarSlider.activeBackground": "{{colors.outline.light.hex}}80",
"editorError.foreground": "{{colors.error.light.hex}}",
"editorWarning.foreground": "{{colors.tertiary.light.hex}}",
"editorInfo.foreground": "{{colors.primary.light.hex}}",
"editorGutter.addedBackground": "{{colors.secondary.light.hex}}",
"editorGutter.modifiedBackground": "{{colors.tertiary.light.hex}}",
"editorGutter.deletedBackground": "{{colors.error.light.hex}}",
"diffEditor.insertedTextBackground": "{{colors.secondary.light.hex}}20",
"diffEditor.removedTextBackground": "{{colors.error.light.hex}}20",
"merge.currentHeaderBackground": "{{colors.primary.light.hex}}40",
"merge.incomingHeaderBackground": "{{colors.secondary.light.hex}}40",
"menubar.selectionBackground": "{{colors.surface_container.light.hex}}",
"menu.background": "{{colors.background.light.hex}}",
"menubar.selectionBackground": "{{colors.primary_container.light.hex}}",
"menubar.selectionForeground": "{{colors.on_primary_container.light.hex}}",
"menu.background": "{{colors.surface_container.light.hex}}",
"menu.foreground": "{{colors.on_surface.light.hex}}",
"menu.selectionBackground": "{{colors.surface_container_high.light.hex}}",
"menu.selectionForeground": "{{colors.on_surface.light.hex}}",
"debugToolBar.background": "{{colors.background.light.hex}}",
"debugExceptionWidget.background": "{{colors.background.light.hex}}",
"menu.selectionBackground": "{{colors.primary_container.light.hex}}",
"menu.selectionForeground": "{{colors.on_primary_container.light.hex}}",
"debugToolBar.background": "{{colors.surface_container.light.hex}}",
"debugExceptionWidget.background": "{{colors.surface_container.light.hex}}",
"debugExceptionWidget.border": "{{colors.error.light.hex}}"
},
"tokenColors": [
{
"scope": ["variable", "meta.object-literal.key"],
"settings": {
"foreground": "{{colors.on_surface.light.hex}}"
}
},
{
"scope": ["string", "constant.other.symbol"],
"settings": {
"foreground": "{{colors.secondary.light.hex}}"
}
},
{
"scope": ["constant.numeric", "constant.language", "constant.character"],
"settings": {
"foreground": "{{colors.tertiary.light.hex}}"
}
},
{
"scope": ["entity.name.type", "support.type", "entity.name.class"],
"settings": {
"foreground": "{{colors.tertiary.light.hex}}"
}
},
{
"scope": ["entity.name.function", "support.function"],
"settings": {
"foreground": "{{colors.primary.light.hex}}"
}
},
{
"scope": ["support.class", "support.variable", "variable.language"],
"settings": {
"foreground": "{{colors.secondary.light.hex}}"
}
},
{
"scope": ["invalid"],
"settings": {
"foreground": "{{colors.error.light.hex}}"
}
},
{
"scope": ["invalid.deprecated"],
"name": "Comments",
"scope": [
"comment",
"punctuation.definition.comment",
"string.comment"
],
"settings": {
"foreground": "{{colors.outline.light.hex}}"
}
},
{
"scope": ["markup.heading"],
"name": "Keywords and storage",
"scope": [
"keyword",
"punctuation.definition.keyword",
"storage",
"storage.type"
],
"settings": {
"foreground": "{{dank16.color5.light.hex}}"
}
},
{
"name": "Strings",
"scope": [
"string",
"string punctuation.section.embedded source"
],
"settings": {
"foreground": "{{dank16.color2.light.hex}}"
}
},
{
"name": "Constants and numbers",
"scope": [
"constant.numeric",
"constant.language",
"constant.other",
"constant.character"
],
"settings": {
"foreground": "{{dank16.color0.light.hex}}"
}
},
{
"name": "Variables (plain)",
"scope": [
"variable",
"variable.other"
],
"settings": {
"foreground": "{{colors.on_surface.light.hex}}"
}
},
{
"name": "Parameters",
"scope": [
"variable.parameter",
"variable.parameter.function"
],
"settings": {
"foreground": "{{colors.on_surface.light.hex}}"
}
},
{
"name": "Object properties and keys",
"scope": [
"meta.object-literal.key",
"meta.property.object",
"variable.other.property",
"entity.name.tag.yaml"
],
"settings": {
"foreground": "{{dank16.color4.light.hex}}"
}
},
{
"name": "Functions",
"scope": [
"entity.name.function",
"support.function"
],
"settings": {
"foreground": "{{dank16.color4.light.hex}}"
}
},
{
"name": "Types and classes",
"scope": [
"entity.name.type",
"entity.name.class",
"support.type"
],
"settings": {
"foreground": "{{colors.secondary.light.hex}}"
}
},
{
"name": "Language builtins and special variables",
"scope": [
"support.class",
"support.variable",
"variable.language",
"support.constant"
],
"settings": {
"foreground": "{{dank16.color4.light.hex}}"
}
},
{
"name": "Invalid",
"scope": [
"invalid"
],
"settings": {
"foreground": "{{colors.error.light.hex}}"
}
},
{
"name": "Invalid deprecated",
"scope": [
"invalid.deprecated"
],
"settings": {
"foreground": "{{colors.outline.light.hex}}"
}
},
{
"name": "Headings",
"scope": [
"markup.heading",
"markup.heading entity.name"
],
"settings": {
"foreground": "{{colors.primary.light.hex}}",
"fontStyle": "bold"
}
},
{
"scope": ["markup.bold"],
"name": "Bold",
"scope": [
"markup.bold"
],
"settings": {
"foreground": "{{colors.tertiary.light.hex}}",
"fontStyle": "bold"
}
},
{
"scope": ["markup.italic"],
"name": "Italic",
"scope": [
"markup.italic"
],
"settings": {
"foreground": "{{colors.primary.light.hex}}",
"fontStyle": "italic"
}
},
{
"scope": ["markup.underline"],
"name": "Underline",
"scope": [
"markup.underline"
],
"settings": {
"fontStyle": "underline"
}
},
{
"scope": ["markup.quote"],
"name": "Quotes",
"scope": [
"markup.quote"
],
"settings": {
"foreground": "{{colors.secondary.light.hex}}"
"foreground": "{{colors.outline.light.hex}}"
}
},
{
"scope": ["markup.list"],
"name": "Lists",
"scope": [
"markup.list"
],
"settings": {
"foreground": "{{colors.on_surface.light.hex}}"
}
},
{
"scope": ["markup.raw", "markup.inline.raw"],
"name": "Inline code in prose",
"scope": [
"markup.raw",
"markup.inline.raw"
],
"settings": {
"foreground": "{{colors.secondary.light.hex}}"
}
},
{
"scope": ["comment", "punctuation.definition.comment"],
"settings": {
"foreground": "{{dank16.color8.hex}}"
}
},
{
"scope": "keyword",
"settings": {
"foreground": "{{dank16.color5.hex}}"
}
},
{
"scope": "storage.type",
"settings": {
"foreground": "{{dank16.color13.hex}}"
}
},
{
"scope": "storage.modifier",
"settings": {
"foreground": "{{dank16.color5.hex}}"
}
},
{
"scope": "variable",
"settings": {
"foreground": "{{dank16.color15.hex}}"
}
},
{
"scope": "variable.parameter",
"settings": {
"foreground": "{{dank16.color7.hex}}"
}
},
{
"scope": ["meta.object-literal.key", "meta.property.object", "variable.other.property"],
"settings": {
"foreground": "{{dank16.color4.hex}}"
}
},
{
"scope": "constant.other.symbol",
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": ["constant.numeric", "constant.language"],
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": "constant.character",
"settings": {
"foreground": "{{dank16.color3.hex}}"
}
},
{
"scope": ["entity.name.type", "entity.name.class"],
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": "support.type",
"settings": {
"foreground": "{{dank16.color13.hex}}"
}
},
{
"scope": ["entity.name.function", "support.function"],
"settings": {
"foreground": "{{dank16.color2.hex}}"
}
},
{
"scope": ["support.class", "support.variable"],
"settings": {
"foreground": "{{dank16.color15.hex}}"
}
},
{
"scope": "variable.language",
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": "entity.name.tag.yaml",
"settings": {
"foreground": "{{dank16.color12.hex}}"
}
},
{
"scope": ["string.unquoted.plain.out.yaml", "string.unquoted.yaml"],
"settings": {
"foreground": "{{dank16.color15.hex}}"
}
},
{
"scope": "string",
"settings": {
"foreground": "{{dank16.color3.hex}}"
"foreground": "{{dank16.color4.light.hex}}"
}
}
],
"semanticHighlighting": true,
"semanticTokenColors": {
"variable": {
"foreground": "{{dank16.color15.hex}}"
"foreground": "{{colors.on_surface.light.hex}}"
},
"variable.readonly": {
"foreground": "{{dank16.color12.hex}}"
},
"property": {
"foreground": "{{dank16.color4.hex}}"
},
"function": {
"foreground": "{{dank16.color2.hex}}"
},
"method": {
"foreground": "{{dank16.color2.hex}}"
},
"type": {
"foreground": "{{dank16.color12.hex}}"
},
"class": {
"foreground": "{{dank16.color12.hex}}"
},
"typeParameter": {
"foreground": "{{dank16.color13.hex}}"
},
"enumMember": {
"foreground": "{{dank16.color12.hex}}"
},
"string": {
"foreground": "{{dank16.color3.hex}}"
},
"number": {
"foreground": "{{dank16.color12.hex}}"
},
"comment": {
"foreground": "{{dank16.color8.hex}}"
},
"keyword": {
"foreground": "{{dank16.color5.hex}}"
},
"operator": {
"foreground": "{{dank16.color15.hex}}"
"foreground": "{{dank16.color1.light.hex}}"
},
"parameter": {
"foreground": "{{dank16.color7.hex}}"
"foreground": "{{colors.on_surface.light.hex}}"
},
"property": {
"foreground": "{{dank16.color4.light.hex}}"
},
"function": {
"foreground": "{{dank16.color4.light.hex}}"
},
"method": {
"foreground": "{{dank16.color4.light.hex}}"
},
"type": {
"foreground": "{{colors.secondary.light.hex}}"
},
"class": {
"foreground": "{{colors.secondary.light.hex}}"
},
"typeParameter": {
"foreground": "{{colors.tertiary.light.hex}}"
},
"enumMember": {
"foreground": "{{dank16.color1.light.hex}}"
},
"string": {
"foreground": "{{dank16.color2.light.hex}}"
},
"number": {
"foreground": "{{dank16.color1.light.hex}}"
},
"comment": {
"foreground": "{{colors.outline.light.hex}}"
},
"keyword": {
"foreground": "{{dank16.color5.light.hex}}"
},
"operator": {
"foreground": "{{colors.on_surface.light.hex}}"
},
"namespace": {
"foreground": "{{dank16.color15.hex}}"
"foreground": "{{colors.on_surface.light.hex}}"
}
}
}
}
@@ -1,315 +0,0 @@
{
"$schema": "vscode://schemas/color-theme",
"name": "Dynamic Base16 DankShell",
"colors": {
"editor.background": "{{colors.background.default.hex}}",
"editor.foreground": "{{colors.on_surface.default.hex}}",
"editorLineNumber.foreground": "{{colors.outline.default.hex}}",
"editorLineNumber.activeForeground": "{{colors.on_surface.default.hex}}",
"editorCursor.foreground": "{{colors.primary.default.hex}}",
"editor.selectionBackground": "{{colors.primary_container.default.hex}}",
"editor.inactiveSelectionBackground": "{{colors.background.default.hex}}",
"editor.lineHighlightBackground": "{{colors.background.default.hex}}",
"editorIndentGuide.background": "{{colors.background.default.hex}}",
"editorIndentGuide.activeBackground": "{{colors.outline.default.hex}}",
"editorWhitespace.foreground": "{{colors.outline_variant.default.hex}}",
"editorBracketMatch.background": "{{colors.background.default.hex}}",
"editorBracketMatch.border": "{{colors.primary.default.hex}}",
"activityBar.background": "{{colors.background.default.hex}}",
"activityBar.foreground": "{{colors.on_surface.default.hex}}",
"activityBar.activeBorder": "{{colors.primary.default.hex}}",
"activityBar.activeBackground": "{{colors.background.default.hex}}",
"activityBarBadge.background": "{{colors.primary.default.hex}}",
"activityBarBadge.foreground": "{{colors.on_primary.default.hex}}",
"sideBar.background": "{{colors.background.default.hex}}",
"sideBar.foreground": "{{colors.on_surface.default.hex}}",
"sideBar.border": "{{colors.background.default.hex}}",
"sideBarTitle.foreground": "{{colors.on_surface.default.hex}}",
"sideBarSectionHeader.background": "{{colors.background.default.hex}}",
"sideBarSectionHeader.foreground": "{{colors.on_surface.default.hex}}",
"list.activeSelectionBackground": "{{colors.primary_container.default.hex}}",
"list.activeSelectionForeground": "{{colors.on_primary_container.default.hex}}",
"list.inactiveSelectionBackground": "{{colors.surface_container.default.hex}}",
"list.inactiveSelectionForeground": "{{colors.on_surface.default.hex}}",
"list.hoverBackground": "{{colors.surface_container.default.hex}}",
"list.hoverForeground": "{{colors.on_surface.default.hex}}",
"list.focusBackground": "{{colors.surface_container_high.default.hex}}",
"list.focusForeground": "{{colors.on_surface.default.hex}}",
"list.highlightForeground": "{{colors.primary.default.hex}}",
"statusBar.background": "{{colors.background.default.hex}}",
"statusBar.foreground": "{{colors.on_surface.default.hex}}",
"statusBar.border": "{{colors.background.default.hex}}",
"statusBar.noFolderBackground": "{{colors.background.default.hex}}",
"statusBar.debuggingBackground": "{{colors.error.default.hex}}",
"statusBar.debuggingForeground": "{{colors.on_error.default.hex}}",
"tab.activeBackground": "{{colors.background.default.hex}}",
"tab.inactiveBackground": "{{colors.background.default.hex}}",
"tab.activeForeground": "{{colors.on_surface.default.hex}}",
"tab.inactiveForeground": "{{colors.outline.default.hex}}",
"tab.border": "{{colors.background.default.hex}}",
"tab.activeBorder": "{{colors.primary.default.hex}}",
"tab.unfocusedActiveBorder": "{{colors.outline.default.hex}}",
"editorGroupHeader.tabsBackground": "{{colors.background.default.hex}}",
"editorGroupHeader.noTabsBackground": "{{colors.background.default.hex}}",
"titleBar.activeBackground": "{{colors.background.default.hex}}",
"titleBar.activeForeground": "{{colors.on_surface.default.hex}}",
"titleBar.inactiveBackground": "{{colors.background.default.hex}}",
"titleBar.inactiveForeground": "{{colors.outline.default.hex}}",
"titleBar.border": "{{colors.background.default.hex}}",
"input.background": "{{colors.background.default.hex}}",
"input.foreground": "{{colors.on_surface.default.hex}}",
"input.border": "{{colors.outline.default.hex}}",
"input.placeholderForeground": "{{colors.outline.default.hex}}",
"inputOption.activeBorder": "{{colors.primary.default.hex}}",
"inputValidation.errorBackground": "{{colors.error.default.hex}}",
"inputValidation.errorBorder": "{{colors.error.default.hex}}",
"dropdown.background": "{{colors.background.default.hex}}",
"dropdown.foreground": "{{colors.on_surface.default.hex}}",
"dropdown.border": "{{colors.outline.default.hex}}",
"quickInput.background": "{{colors.background.default.hex}}",
"quickInput.foreground": "{{colors.on_surface.default.hex}}",
"quickInputList.focusBackground": "{{colors.surface_container_high.default.hex}}",
"quickInputList.focusForeground": "{{colors.on_surface.default.hex}}",
"button.background": "{{colors.primary.default.hex}}",
"button.foreground": "{{colors.on_primary.default.hex}}",
"button.hoverBackground": "{{colors.primary_container.default.hex}}",
"focusBorder": "{{colors.primary.default.hex}}",
"badge.background": "{{colors.secondary.default.hex}}",
"badge.foreground": "{{colors.on_secondary.default.hex}}",
"panel.background": "{{colors.background.default.hex}}",
"panel.border": "{{colors.primary.default.hex}}",
"panelTitle.activeBorder": "{{colors.primary.default.hex}}",
"panelTitle.activeForeground": "{{colors.on_surface.default.hex}}",
"panelTitle.inactiveForeground": "{{colors.outline.default.hex}}",
"terminal.background": "{{colors.background.default.hex}}",
"terminal.foreground": "{{colors.on_surface.default.hex}}",
"gitDecoration.modifiedResourceForeground": "{{colors.primary.default.hex}}",
"gitDecoration.addedResourceForeground": "{{colors.primary.default.hex}}",
"gitDecoration.stageModifiedResourceForeground": "{{colors.primary.default.hex}}",
"gitDecoration.stageDeletedResourceForeground": "{{colors.error.default.hex}}",
"gitDecoration.deletedResourceForeground": "{{colors.error.default.hex}}",
"gitDecoration.untrackedResourceForeground": "{{colors.secondary.default.hex}}",
"gitDecoration.ignoredResourceForeground": "{{colors.outline.default.hex}}",
"gitDecoration.conflictingResourceForeground": "{{colors.error_container.default.hex}}",
"gitDecoration.submoduleResourceForeground": "{{colors.primary.default.hex}}",
"editorWidget.background": "{{colors.background.default.hex}}",
"editorWidget.border": "{{colors.outline.default.hex}}",
"editorSuggestWidget.background": "{{colors.background.default.hex}}",
"editorSuggestWidget.border": "{{colors.outline.default.hex}}",
"editorSuggestWidget.selectedBackground": "{{colors.surface_container_high.default.hex}}",
"editorSuggestWidget.highlightForeground": "{{colors.primary.default.hex}}",
"peekView.border": "{{colors.primary.default.hex}}",
"peekViewEditor.background": "{{colors.background.default.hex}}",
"peekViewResult.background": "{{colors.background.default.hex}}",
"peekViewTitle.background": "{{colors.background.default.hex}}",
"notificationCenter.border": "{{colors.outline.default.hex}}",
"notifications.background": "{{colors.background.default.hex}}",
"notifications.border": "{{colors.outline.default.hex}}",
"breadcrumb.foreground": "{{colors.outline.default.hex}}",
"breadcrumb.focusForeground": "{{colors.on_surface.default.hex}}",
"breadcrumb.activeSelectionForeground": "{{colors.primary.default.hex}}",
"scrollbarSlider.background": "{{colors.outline.default.hex}}40",
"scrollbarSlider.hoverBackground": "{{colors.outline.default.hex}}60",
"scrollbarSlider.activeBackground": "{{colors.outline.default.hex}}80",
"editorError.foreground": "{{colors.error.default.hex}}",
"editorWarning.foreground": "{{colors.tertiary.default.hex}}",
"editorInfo.foreground": "{{colors.primary.default.hex}}",
"editorGutter.addedBackground": "{{colors.secondary.default.hex}}",
"editorGutter.modifiedBackground": "{{colors.tertiary.default.hex}}",
"editorGutter.deletedBackground": "{{colors.error.default.hex}}",
"diffEditor.insertedTextBackground": "{{colors.secondary.default.hex}}20",
"diffEditor.removedTextBackground": "{{colors.error.default.hex}}20",
"merge.currentHeaderBackground": "{{colors.primary.default.hex}}40",
"merge.incomingHeaderBackground": "{{colors.secondary.default.hex}}40",
"menubar.selectionBackground": "{{colors.surface_container.default.hex}}",
"menu.background": "{{colors.background.default.hex}}",
"menu.foreground": "{{colors.on_surface.default.hex}}",
"menu.selectionBackground": "{{colors.surface_container_high.default.hex}}",
"menu.selectionForeground": "{{colors.on_surface.default.hex}}",
"debugToolBar.background": "{{colors.background.default.hex}}",
"debugExceptionWidget.background": "{{colors.background.default.hex}}",
"debugExceptionWidget.border": "{{colors.error.default.hex}}"
},
"tokenColors": [
{
"scope": ["comment", "punctuation.definition.comment"],
"settings": {
"foreground": "{{colors.outline.default.hex}}",
"fontStyle": "italic"
}
},
{
"scope": ["keyword", "storage.type", "storage.modifier"],
"settings": {
"foreground": "{{colors.primary.default.hex}}"
}
},
{
"scope": ["variable", "meta.object-literal.key"],
"settings": {
"foreground": "{{colors.on_surface.default.hex}}"
}
},
{
"scope": ["string", "constant.other.symbol"],
"settings": {
"foreground": "{{colors.secondary.default.hex}}"
}
},
{
"scope": ["constant.numeric", "constant.language", "constant.character"],
"settings": {
"foreground": "{{colors.tertiary.default.hex}}"
}
},
{
"scope": ["entity.name.type", "support.type", "entity.name.class"],
"settings": {
"foreground": "{{colors.tertiary.default.hex}}"
}
},
{
"scope": ["entity.name.function", "support.function"],
"settings": {
"foreground": "{{colors.primary.default.hex}}"
}
},
{
"scope": ["support.class", "support.variable", "variable.language"],
"settings": {
"foreground": "{{colors.secondary.default.hex}}"
}
},
{
"scope": ["invalid"],
"settings": {
"foreground": "{{colors.error.default.hex}}"
}
},
{
"scope": ["invalid.deprecated"],
"settings": {
"foreground": "{{colors.outline.default.hex}}"
}
},
{
"scope": ["markup.heading"],
"settings": {
"foreground": "{{colors.primary.default.hex}}",
"fontStyle": "bold"
}
},
{
"scope": ["markup.bold"],
"settings": {
"foreground": "{{colors.tertiary.default.hex}}",
"fontStyle": "bold"
}
},
{
"scope": ["markup.italic"],
"settings": {
"foreground": "{{colors.primary.default.hex}}",
"fontStyle": "italic"
}
},
{
"scope": ["markup.underline"],
"settings": {
"fontStyle": "underline"
}
},
{
"scope": ["markup.quote"],
"settings": {
"foreground": "{{colors.secondary.default.hex}}"
}
},
{
"scope": ["markup.list"],
"settings": {
"foreground": "{{colors.on_surface.default.hex}}"
}
},
{
"scope": ["markup.raw", "markup.inline.raw"],
"settings": {
"foreground": "{{colors.secondary.default.hex}}"
}
}
],
"semanticHighlighting": true,
"semanticTokenColors": {
"variable.readonly": {
"foreground": "{{colors.tertiary.default.hex}}"
},
"property": {
"foreground": "{{colors.on_surface.default.hex}}"
},
"function": {
"foreground": "{{colors.primary.default.hex}}"
},
"method": {
"foreground": "{{colors.primary.default.hex}}"
},
"type": {
"foreground": "{{colors.tertiary.default.hex}}"
},
"class": {
"foreground": "{{colors.tertiary.default.hex}}"
},
"enumMember": {
"foreground": "{{colors.tertiary.default.hex}}"
},
"string": {
"foreground": "{{colors.secondary.default.hex}}"
},
"number": {
"foreground": "{{colors.tertiary.default.hex}}"
},
"comment": {
"foreground": "{{colors.outline.default.hex}}",
"fontStyle": "italic"
},
"keyword": {
"foreground": "{{colors.primary.default.hex}}"
},
"operator": {
"foreground": "{{colors.on_surface.default.hex}}"
},
"parameter": {
"foreground": "{{colors.on_surface.default.hex}}"
},
"namespace": {
"foreground": "{{colors.secondary.default.hex}}"
}
}
}
+2 -2
View File
@@ -9,5 +9,5 @@ cursor_border = '{{colors.primary.default.hex}}'
selection_bg = '{{colors.primary_container.default.hex}}'
selection_fg = '{{colors.on_surface.default.hex}}'
ansi = ['{{dank16.color0.hex}}', '{{dank16.color1.hex}}', '{{dank16.color2.hex}}', '{{dank16.color3.hex}}', '{{dank16.color4.hex}}', '{{dank16.color5.hex}}', '{{dank16.color6.hex}}', '{{dank16.color7.hex}}']
brights = ['{{dank16.color8.hex}}', '{{dank16.color9.hex}}', '{{dank16.color10.hex}}', '{{dank16.color11.hex}}', '{{dank16.color12.hex}}', '{{dank16.color13.hex}}', '{{dank16.color14.hex}}', '{{dank16.color15.hex}}']
ansi = ['{{dank16.color0.default.hex}}', '{{dank16.color1.default.hex}}', '{{dank16.color2.default.hex}}', '{{dank16.color3.default.hex}}', '{{dank16.color4.default.hex}}', '{{dank16.color5.default.hex}}', '{{dank16.color6.default.hex}}', '{{dank16.color7.default.hex}}']
brights = ['{{dank16.color8.default.hex}}', '{{dank16.color9.default.hex}}', '{{dank16.color10.default.hex}}', '{{dank16.color11.default.hex}}', '{{dank16.color12.default.hex}}', '{{dank16.color13.default.hex}}', '{{dank16.color14.default.hex}}', '{{dank16.color15.default.hex}}']
-298
View File
@@ -1,298 +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:-}" | 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_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"
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_config "ghostty" "ghostty.toml" "$cfg_file"
append_config "kitty" "kitty.toml" "$cfg_file"
append_config "foot" "foot.toml" "$cfg_file"
append_config "alacritty" "alacritty.toml" "$cfg_file"
append_config "wezterm" "wezterm.toml" "$cfg_file"
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() {
local primary="$1" surface="$2" light_flag="$3"
local args=("$primary" --json)
[[ -n "$light_flag" ]] && args+=("$light_flag")
[[ -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
}
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)
trap "rm -f '$TMP_CFG'" RETURN
build_merged_config "$mode" "$run_user" "$TMP_CFG"
local light_flag=""
[[ "$mode" == "light" ]] && light_flag="--light"
local primary surface dank16_dark dank16_light import_args=()
if [[ -n "$stock_colors" ]]; then
log "Using stock/custom theme colors with matugen base"
primary=$(echo "$stock_colors" | sed -n 's/.*"primary"[^{]*{[^}]*"dark"[^{]*{[^}]*"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" ]] && { err "Failed to extract primary from stock colors"; return 1; }
dank16_dark=$(generate_dank16 "$primary" "$surface" "")
dank16_light=$(generate_dank16 "$primary" "$surface" "--light")
local dank16_current
[[ "$mode" == "light" ]] && dank16_current="$dank16_light" || dank16_current="$dank16_dark"
[[ "$TERMINALS_ALWAYS_DARK" == "true" && "$mode" == "light" ]] && dank16_current="$dank16_dark"
import_args+=(--import-json-string "{\"colors\": $stock_colors, \"dank16\": $dank16_current}")
log "Running matugen color hex with stock color overrides"
if ! matugen color hex "$primary" -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=$(echo "$mat_json" | sed -n 's/.*"primary"[[:space:]]*:[[:space:]]*{[^}]*"dark"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
surface=$(echo "$mat_json" | sed -n 's/.*"surface"[[:space:]]*:[[:space:]]*{[^}]*"dark"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
[[ -z "$primary" ]] && { err "Failed to extract primary color"; return 1; }
dank16_dark=$(generate_dank16 "$primary" "$surface" "")
dank16_light=$(generate_dank16 "$primary" "$surface" "--light")
local dank16_current
[[ "$mode" == "light" ]] && dank16_current="$dank16_light" || dank16_current="$dank16_dark"
[[ "$TERMINALS_ALWAYS_DARK" == "true" && "$mode" == "light" ]] && dank16_current="$dank16_dark"
import_args+=(--import-json-string "{\"dank16\": $dank16_current}")
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