diff --git a/.github/workflows/go-ci.yml b/.github/workflows/go-ci.yml index ad212163b..02fc28264 100644 --- a/.github/workflows/go-ci.yml +++ b/.github/workflows/go-ci.yml @@ -50,8 +50,11 @@ jobs: - name: Build dms run: go build -v ./cmd/dms + - name: Build dms (embedded shell) + run: make build + - name: Build dms (distropkg) - run: go build -v -tags distro_binary ./cmd/dms + run: go build -v -tags 'distro_binary withshell' ./cmd/dms - name: Build dankinstall run: go build -v ./cmd/dankinstall diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0f57d0b1c..ec39ed261 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -74,6 +74,9 @@ jobs: gzip -9 -k dankinstall-${{ matrix.arch }} sha256sum dankinstall-${{ matrix.arch }}.gz > dankinstall-${{ matrix.arch }}.gz.sha256 + - name: Sync embedded shell + run: make sync-shell + - name: Build dms (${{ matrix.arch }}) env: GOOS: linux @@ -82,7 +85,7 @@ jobs: run: | set -eux cd cmd/dms - go build -trimpath -ldflags "-s -w -X main.Version=${TAG}" \ + go build -trimpath -tags withshell -ldflags "-s -w -X main.Version=${TAG}" \ -o ../../dms-${{ matrix.arch }} cd ../.. gzip -9 -k dms-${{ matrix.arch }} @@ -105,7 +108,7 @@ jobs: run: | set -eux cd cmd/dms - go build -trimpath -tags distro_binary -ldflags "-s -w -X main.Version=${TAG}" \ + go build -trimpath -tags 'distro_binary withshell' -ldflags "-s -w -X main.Version=${TAG}" \ -o ../../dms-distropkg-${{ matrix.arch }} cd ../.. gzip -9 -k dms-distropkg-${{ matrix.arch }} @@ -379,18 +382,16 @@ jobs: ## Installation Steps - 1. **Install quickshell assets:** - ```bash - mkdir -p ~/.config/quickshell - cp -r dms ~/.config/quickshell/ - ``` + The Quickshell UI is embedded in the `dms` binary. The bundled `dms/` + tree is optional — pass it with `-c` or `DMS_SHELL_DIR` to run a + modified copy instead of the embedded UI. - 2. **Install the DMS CLI binaries:** + 1. **Install the DMS CLI binaries:** ```bash sudo install -m 755 bin/dms /usr/local/bin/dms ``` - 3. **Install shell completions (optional):** + 2. **Install shell completions (optional):** ```bash # Bash sudo install -m 644 completions/completion.bash /usr/share/bash-completion/completions/dms @@ -402,7 +403,7 @@ jobs: sudo install -m 644 completions/completion.zsh /usr/share/zsh/site-functions/_dms ``` - 4. **Start the shell:** + 3. **Start the shell:** ```bash dms run ``` diff --git a/.gitignore b/.gitignore index f7a36aa87..4a25a19bf 100644 --- a/.gitignore +++ b/.gitignore @@ -129,3 +129,5 @@ distro/void/masterdir*/ # Often gets built core/dms + +core/internal/shellembed/dist diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 4494ec202..581e03792 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,5 +1,13 @@ This file is more of a quick reference so I know what to account for before next releases. +# Next +- Go core migrated to dankgo (shared log/paths/errdefs/ipc/shellapp) +- Quickshell UI embedded in the dms binary, -c / DMS_SHELL_DIR override - breaking for setups relying on ~/.config/quickshell/dms auto-discovery (XDG search removed) +- release binaries + all -git packages build with withshell; sockets/pidfiles/env names unchanged, IPC APIVersion 28 wire-compatible +- -git packages (AUR dms-shell-git, fedora, opensuse, void, debian, ubuntu) no longer install /usr/share/quickshell/dms; dms greeter install falls back to the embedded UI when the greeter package is present +- ping over the socket returns {"pong":true} instead of "pong" +- startup: embedded UI resolution is keyed by build-time .dankrev (no per-start content hashing) + # 1.5.0 - Overhauled shadows - App ID changed to com.danklinux.dms - breaking for window rules diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a51f22f24..78565aee3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -36,6 +36,29 @@ This will provide: The dev shell automatically creates the `.qmlls.ini` file in the `quickshell/` directory. +## Building and running + +The Quickshell UI is embedded into the `dms` binary at build time. `make build` copies `quickshell/` into `core/internal/shellembed/dist/` (generated, never committed) and compiles with the `withshell` tag. `make dev` builds without the tag — that binary carries no UI and requires an explicit config dir. + +```bash +make build # embedded binary at core/bin/dms +make dev # untagged development build +make run # dev build, then launch against the live quickshell/ tree +``` + +The UI config dir resolves in order: `-c `, `DMS_SHELL_DIR`, the dir a running instance is using, then the embedded UI. Each candidate must contain `shell.qml`. `make run` uses `-c $(pwd)/quickshell`, so QML edits hot-reload from the working tree. + +The Go core depends on [dankgo](https://github.com/AvengeMedia/dankgo) for logging, XDG paths, the IPC transport, and the quickshell process lifecycle. To develop against a local dankgo checkout, create a gitignored `go.work` at the repo root: + +``` +go 1.26.1 + +use ( + ./core + ../dankgo +) +``` + ## Shared widgets (dank-qml-common) Everything under `quickshell/DankCommon/` (core widgets, the file browser, scroll physics, bundled fonts) is shared across the DMS suite and lives in the `dank-qml-common` submodule. It is a normal git worktree: diff --git a/Makefile b/Makefile index 2a5ba6b32..e68a6a543 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ SHELL_INSTALL_DIR=$(DATA_DIR)/quickshell/dms ASSETS_DIR=assets APPLICATIONS_DIR=$(DATA_DIR)/applications -.PHONY: all build clean lint-qml install install-bin install-shell install-completions install-systemd install-icon install-desktop uninstall uninstall-bin uninstall-shell uninstall-completions uninstall-systemd uninstall-icon uninstall-desktop help +.PHONY: all build dev run clean lint-qml install install-bin install-shell install-completions install-systemd install-icon install-desktop uninstall uninstall-bin uninstall-shell uninstall-completions uninstall-systemd uninstall-icon uninstall-desktop help all: build @@ -27,6 +27,12 @@ build: @$(MAKE) -C $(CORE_DIR) build @echo "Build complete" +dev: + @$(MAKE) -C $(CORE_DIR) dev + +run: dev + @$(BUILD_DIR)/$(BINARY_NAME) run -c $(CURDIR)/$(SHELL_DIR) + clean: @echo "Cleaning build artifacts..." @$(MAKE) -C $(CORE_DIR) clean diff --git a/core/Makefile b/core/Makefile index cad9b95ec..305ba35ba 100644 --- a/core/Makefile +++ b/core/Makefile @@ -3,6 +3,8 @@ BINARY_NAME_INSTALL=dankinstall SOURCE_DIR=cmd/dms SOURCE_DIR_INSTALL=cmd/dankinstall BUILD_DIR=bin +SHELL_SRC=../quickshell +EMBED_DIR=internal/shellembed/dist PREFIX ?= /usr/local INSTALL_DIR=$(PREFIX)/bin @@ -22,16 +24,28 @@ BUILD_LDFLAGS=-ldflags='-s -w -X main.Version=$(VERSION) -X main.buildTime=$(BUI # Architecture to build for dist target (amd64, arm64, or all) ARCH ?= all -.PHONY: all build dankinstall dist clean install install-all install-dankinstall uninstall uninstall-all uninstall-dankinstall install-config uninstall-config test fmt vet deps print-version help +.PHONY: all build sync-shell dankinstall dist clean install install-all install-dankinstall uninstall uninstall-all uninstall-dankinstall install-config uninstall-config test fmt vet deps print-version help # Default target all: build +# Copy the quickshell UI into the embed dir (gitignored) so tagged builds +# can bake it into the binary. Dev-only files are stripped. -L dereferences +# the DankCommon submodule symlink; go:embed rejects symlinks. +sync-shell: + @test -e $(SHELL_SRC)/DankCommon/Widgets/DankIcon.qml || { echo "DankCommon missing: run git submodule update --init"; exit 1; } + @rm -rf $(EMBED_DIR) + @cp -rL $(SHELL_SRC) $(EMBED_DIR) + @rm -rf $(EMBED_DIR)/scripts $(EMBED_DIR)/.claude $(EMBED_DIR)/.git* $(EMBED_DIR)/.github + @rm -f $(EMBED_DIR)/.qmlls.ini $(EMBED_DIR)/AGENTS.md $(EMBED_DIR)/qmlformat-all.sh + @rm -f $(EMBED_DIR)/translations/*.py $(EMBED_DIR)/translations/WORKFLOW.md + @cd $(EMBED_DIR) && find . -type f -print0 | LC_ALL=C sort -z | xargs -0 sha256sum | sha256sum | cut -c1-16 > .dankrev + # Build the main binary (dms) -build: +build: sync-shell @echo "Building $(BINARY_NAME)..." @mkdir -p $(BUILD_DIR) - CGO_ENABLED=0 $(GO) build $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) ./$(SOURCE_DIR) + CGO_ENABLED=0 $(GO) build -tags withshell $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME) ./$(SOURCE_DIR) @echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME)" dankinstall: @@ -41,14 +55,14 @@ dankinstall: @echo "Build complete: $(BUILD_DIR)/$(BINARY_NAME_INSTALL)" # Build distro binaries for amd64 and arm64 (Linux only, no update/greeter support) -dist: +dist: sync-shell ifeq ($(ARCH),all) @echo "Building $(BINARY_NAME) for distribution (amd64 and arm64)..." @mkdir -p $(BUILD_DIR) @echo "Building for linux/amd64..." - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -tags distro_binary $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./$(SOURCE_DIR) + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GO) build -tags 'distro_binary withshell' $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 ./$(SOURCE_DIR) @echo "Building for linux/arm64..." - CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build -tags distro_binary $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 ./$(SOURCE_DIR) + CGO_ENABLED=0 GOOS=linux GOARCH=arm64 $(GO) build -tags 'distro_binary withshell' $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64 ./$(SOURCE_DIR) @echo "Distribution builds complete:" @echo " $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64" @echo " $(BUILD_DIR)/$(BINARY_NAME)-linux-arm64" @@ -56,7 +70,7 @@ else @echo "Building $(BINARY_NAME) for distribution ($(ARCH))..." @mkdir -p $(BUILD_DIR) @echo "Building for linux/$(ARCH)..." - CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) $(GO) build -tags distro_binary $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-$(ARCH) ./$(SOURCE_DIR) + CGO_ENABLED=0 GOOS=linux GOARCH=$(ARCH) $(GO) build -tags 'distro_binary withshell' $(BUILD_LDFLAGS) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-$(ARCH) ./$(SOURCE_DIR) @echo "Distribution build complete:" @echo " $(BUILD_DIR)/$(BINARY_NAME)-linux-$(ARCH)" endif @@ -99,7 +113,7 @@ uninstall-dankinstall: clean: @echo "Cleaning build artifacts..." - @rm -rf $(BUILD_DIR) + @rm -rf $(BUILD_DIR) $(EMBED_DIR) @echo "Clean complete" test: @@ -141,7 +155,8 @@ print-version: help: @echo "Available targets:" @echo " all - Build the main binary (dms) (default)" - @echo " build - Build the main binary (dms)" + @echo " build - Build the main binary (dms) with the embedded UI" + @echo " sync-shell - Copy quickshell/ into the embed dir (runs before tagged builds)" @echo " dankinstall - Build dankinstall binary" @echo " dist - Build dms for linux amd64/arm64 (no update/greeter)" @echo " Use ARCH=amd64 or ARCH=arm64 to build only one" diff --git a/core/cmd/dms/commands_common.go b/core/cmd/dms/commands_common.go index 48dec44d3..f64f5bef9 100644 --- a/core/cmd/dms/commands_common.go +++ b/core/cmd/dms/commands_common.go @@ -6,7 +6,6 @@ import ( "regexp" "strings" - "github.com/AvengeMedia/DankMaterialShell/core/internal/config" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" "github.com/AvengeMedia/DankMaterialShell/core/internal/server" @@ -19,63 +18,6 @@ var versionCmd = &cobra.Command{ Run: runVersion, } -var runCmd = &cobra.Command{ - Use: "run", - Short: "Launch quickshell with DMS configuration", - Long: "Launch quickshell with DMS configuration (qs -c dms)", - PreRunE: findConfig, - Run: func(cmd *cobra.Command, args []string) { - daemon, _ := cmd.Flags().GetBool("daemon") - session, _ := cmd.Flags().GetBool("session") - if v, _ := cmd.Flags().GetString("log-level"); v != "" { - if err := os.Setenv("DMS_LOG_LEVEL", v); err != nil { - log.Fatalf("Failed to set DMS_LOG_LEVEL: %v", err) - } - } - if v, _ := cmd.Flags().GetString("log-file"); v != "" { - if err := os.Setenv("DMS_LOG_FILE", v); err != nil { - log.Fatalf("Failed to set DMS_LOG_FILE: %v", err) - } - } - log.ApplyEnvOverrides() - config.CleanupStrayHyprlandConfFile(log.Infof) - if daemon { - runShellDaemon(session) - } else { - runShellInteractive(session) - } - }, -} - -var restartCmd = &cobra.Command{ - Use: "restart", - Short: "Restart quickshell with DMS configuration", - Long: "Kill existing DMS shell processes and restart quickshell with DMS configuration", - PreRunE: findConfig, - Run: func(cmd *cobra.Command, args []string) { - restartShell() - }, -} - -var restartDetachedCmd = &cobra.Command{ - Use: "restart-detached ", - Hidden: true, - Args: cobra.ExactArgs(1), - PreRunE: findConfig, - Run: func(cmd *cobra.Command, args []string) { - runDetachedRestart(args[0]) - }, -} - -var killCmd = &cobra.Command{ - Use: "kill", - Short: "Kill running DMS shell processes", - Long: "Kill all running quickshell processes with DMS configuration", - Run: func(cmd *cobra.Command, args []string) { - killShell() - }, -} - var ipcCmd = &cobra.Command{ Use: "ipc", Short: "Send IPC commands to running DMS shell", @@ -743,12 +685,9 @@ func checkAllPluginsCLI() error { } func getCommonCommands() []*cobra.Command { - return []*cobra.Command{ + commands := shellApp.Commands() + return append(commands, []*cobra.Command{ versionCmd, - runCmd, - restartCmd, - restartDetachedCmd, - killCmd, ipcCmd, debugSrvCmd, pluginsCmd, @@ -775,5 +714,5 @@ func getCommonCommands() []*cobra.Command { trashCmd, systemCmd, switchUserCmd, - } + }...) } diff --git a/core/cmd/dms/commands_doctor.go b/core/cmd/dms/commands_doctor.go index 936657811..654e0c2d4 100644 --- a/core/cmd/dms/commands_doctor.go +++ b/core/cmd/dms/commands_doctor.go @@ -401,12 +401,12 @@ func checkVersions(qsMissingFeatures bool) []checkResult { } func getDMSShellVersion() (version, path string) { - if err := findConfig(nil, nil); err == nil && configPath != "" { - versionFile := filepath.Join(configPath, "VERSION") + if err := shellApp.ResolveConfig(nil, nil); err == nil && shellApp.ConfigPath() != "" { + versionFile := filepath.Join(shellApp.ConfigPath(), "VERSION") if data, err := os.ReadFile(versionFile); err == nil { - return strings.TrimSpace(string(data)), configPath + return strings.TrimSpace(string(data)), shellApp.ConfigPath() } - return "installed", configPath + return "installed", shellApp.ConfigPath() } if dmsPath, err := config.LocateDMSConfig(); err == nil { @@ -450,8 +450,8 @@ func checkDMSInstallation() []checkResult { var results []checkResult dmsPath := "" - if err := findConfig(nil, nil); err == nil && configPath != "" { - dmsPath = configPath + if err := shellApp.ResolveConfig(nil, nil); err == nil && shellApp.ConfigPath() != "" { + dmsPath = shellApp.ConfigPath() } else if path, err := config.LocateDMSConfig(); err == nil { dmsPath = path } diff --git a/core/cmd/dms/commands_features.go b/core/cmd/dms/commands_features.go index b3da0abdf..4cacf50b5 100644 --- a/core/cmd/dms/commands_features.go +++ b/core/cmd/dms/commands_features.go @@ -26,7 +26,7 @@ var updateCmd = &cobra.Command{ Use: "update", Short: "Update DankMaterialShell to the latest version", Long: "Update DankMaterialShell to the latest version using the appropriate package manager for your distribution", - PreRunE: findConfig, + PreRunE: shellApp.ResolveConfig, Run: func(cmd *cobra.Command, args []string) { runUpdate() }, @@ -98,7 +98,7 @@ func runUpdate() { } log.Info("Update complete! Restarting DMS...") - restartShell() + shellApp.Restart() } func updateArchLinux() error { diff --git a/core/cmd/dms/commands_greeter.go b/core/cmd/dms/commands_greeter.go index 86ffdb16d..0d6746dc5 100644 --- a/core/cmd/dms/commands_greeter.go +++ b/core/cmd/dms/commands_greeter.go @@ -278,7 +278,7 @@ func installGreeter(nonInteractive bool) error { } fmt.Println("\nDetecting DMS installation...") - dmsPath, err := greeter.DetectDMSPath() + dmsPath, err := detectDMSPath() if err != nil { return err } @@ -708,7 +708,7 @@ func syncGreeter(nonInteractive bool, forceAuth bool, local bool, profileOnly bo fmt.Printf("✓ Using local DMS path: %s\n", dmsPath) } } else { - dmsPath, err = greeter.DetectDMSPath() + dmsPath, err = detectDMSPath() if err != nil { return err } @@ -944,6 +944,24 @@ func resolveDMSLocalCandidate(path string) (string, bool) { return "", false } +// detectDMSPath finds the QML tree the greeter copies from. On-disk installs +// win; with a packaged greeter the embedded UI is a valid copy source, but an +// extraction path must never be persisted into greetd config, so unpackaged +// setups still require a real install. +func detectDMSPath() (string, error) { + path, err := greeter.DetectDMSPath() + if err == nil { + return path, nil + } + if !greeter.IsGreeterPackaged() { + return "", err + } + if resolveErr := shellApp.ResolveConfig(nil, nil); resolveErr != nil { + return "", err + } + return shellApp.ConfigPath(), nil +} + func resolveLocalDMSPath() (string, error) { if override := strings.TrimSpace(os.Getenv("DMS_LOCAL_PATH")); override != "" { if resolved, ok := resolveDMSLocalCandidate(override); ok { diff --git a/core/cmd/dms/commands_root.go b/core/cmd/dms/commands_root.go index b263ae6b7..9d6bb757b 100644 --- a/core/cmd/dms/commands_root.go +++ b/core/cmd/dms/commands_root.go @@ -1,19 +1,9 @@ package main import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/AvengeMedia/DankMaterialShell/core/internal/config" - "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/spf13/cobra" ) -var customConfigPath string -var configPath string - var rootCmd = &cobra.Command{ Use: "dms", Short: "dms CLI", @@ -21,54 +11,5 @@ var rootCmd = &cobra.Command{ } func init() { - rootCmd.PersistentFlags().StringVarP(&customConfigPath, "config", "c", "", "Specify a custom path to the DMS config directory") -} - -func findConfig(cmd *cobra.Command, args []string) error { - if customConfigPath != "" { - log.Debug("Custom config path provided via -c flag: %s", customConfigPath) - shellPath := filepath.Join(customConfigPath, "shell.qml") - - info, statErr := os.Stat(shellPath) - - if statErr == nil && !info.IsDir() { - configPath = customConfigPath - log.Debug("Using config from: %s", configPath) - return nil - } - - if statErr != nil { - return fmt.Errorf("custom config path error: %w", statErr) - } - - return fmt.Errorf("path is a directory, not a file: %s", shellPath) - } - - configStateFile := filepath.Join(getRuntimeDir(), "danklinux.path") - if data, readErr := os.ReadFile(configStateFile); readErr == nil { - if len(getAllDMSPIDs()) == 0 { - os.Remove(configStateFile) - } else { - statePath := strings.TrimSpace(string(data)) - shellPath := filepath.Join(statePath, "shell.qml") - - if info, statErr := os.Stat(shellPath); statErr == nil && !info.IsDir() { - log.Debug("Using config from active session state file: %s", statePath) - configPath = statePath - log.Debug("Using config from: %s", configPath) - return nil - } - os.Remove(configStateFile) - } - } - - log.Debug("No custom path or active session, searching default XDG locations...") - var err error - configPath, err = config.LocateDMSConfig() - if err != nil { - return err - } - - log.Debug("Using config from: %s", configPath) - return nil + rootCmd.PersistentFlags().StringVarP(shellApp.CustomConfigVar(), "config", "c", "", "Path to a UI config dir (containing shell.qml) to use instead of the embedded UI (env: DMS_SHELL_DIR)") } diff --git a/core/cmd/dms/commands_screenshot.go b/core/cmd/dms/commands_screenshot.go index 5c626f72a..4fa6b7a80 100644 --- a/core/cmd/dms/commands_screenshot.go +++ b/core/cmd/dms/commands_screenshot.go @@ -231,16 +231,16 @@ func setPopoutScreenshotMode(begin bool) { fn = "begin" } cmdArgs := []string{"ipc"} - if pid, ok := getFirstDMSPID(); ok { + if pid, ok := shellApp.SessionPID(); ok { cmdArgs = append(cmdArgs, "--pid", strconv.Itoa(pid)) } else { - if err := findConfig(nil, nil); err != nil { + if err := shellApp.ResolveConfig(nil, nil); err != nil { return } if qsHasAnyDisplay() { cmdArgs = append(cmdArgs, "--any-display") } - cmdArgs = append(cmdArgs, "-p", configPath) + cmdArgs = append(cmdArgs, "-p", shellApp.ConfigPath()) } cmdArgs = append(cmdArgs, "call", "screenshot", fn) _ = exec.Command("qs", cmdArgs...).Run() diff --git a/core/cmd/dms/main.go b/core/cmd/dms/main.go index cf8ba99eb..f1e5914be 100644 --- a/core/cmd/dms/main.go +++ b/core/cmd/dms/main.go @@ -12,13 +12,6 @@ import ( var Version = "dev" func init() { - runCmd.Flags().BoolP("daemon", "d", false, "Run in daemon mode") - runCmd.Flags().Bool("daemon-child", false, "Internal flag for daemon child process") - runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)") - runCmd.Flags().String("log-level", "", "Log level: debug, info, warn, error, fatal (overrides DMS_LOG_LEVEL)") - runCmd.Flags().String("log-file", "", "Append logs to this file in addition to stderr (overrides DMS_LOG_FILE)") - runCmd.Flags().MarkHidden("daemon-child") - greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd, greeterLaunchSessionCmd) authCmd.AddCommand(authSyncCmd, authResolveLockCmd, authListServicesCmd, authValidateCmd) setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd) diff --git a/core/cmd/dms/main_distro.go b/core/cmd/dms/main_distro.go index f5a7a2209..2897085ce 100644 --- a/core/cmd/dms/main_distro.go +++ b/core/cmd/dms/main_distro.go @@ -12,13 +12,6 @@ import ( var Version = "dev" func init() { - runCmd.Flags().BoolP("daemon", "d", false, "Run in daemon mode") - runCmd.Flags().Bool("daemon-child", false, "Internal flag for daemon child process") - runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)") - runCmd.Flags().String("log-level", "", "Log level: debug, info, warn, error, fatal (overrides DMS_LOG_LEVEL)") - runCmd.Flags().String("log-file", "", "Append logs to this file in addition to stderr (overrides DMS_LOG_FILE)") - runCmd.Flags().MarkHidden("daemon-child") - greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd, greeterLaunchSessionCmd) authCmd.AddCommand(authSyncCmd, authResolveLockCmd, authListServicesCmd, authValidateCmd) setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd) diff --git a/core/cmd/dms/server_client.go b/core/cmd/dms/server_client.go index fdd0abaac..27fb16857 100644 --- a/core/cmd/dms/server_client.go +++ b/core/cmd/dms/server_client.go @@ -101,11 +101,8 @@ func getServerSocketPath() string { runtimeDir = os.TempDir() } - if parentPID, ok := sessionParentPID(os.Getenv("WAYLAND_DISPLAY")); ok { - sessionSock := filepath.Join(runtimeDir, fmt.Sprintf("danklinux-%d.sock", parentPID)) - if _, err := os.Stat(sessionSock); err == nil { - return sessionSock - } + if sessionSock, ok := shellApp.SessionSocketPath(); ok { + return sessionSock } entries, err := os.ReadDir(runtimeDir) diff --git a/core/cmd/dms/shell.go b/core/cmd/dms/shell.go index 41df6ea11..cdbe39e2d 100644 --- a/core/cmd/dms/shell.go +++ b/core/cmd/dms/shell.go @@ -4,591 +4,20 @@ import ( "context" "encoding/json" "fmt" - "io" "os" "os/exec" - "os/signal" "path/filepath" "slices" "strconv" "strings" "sync" - "syscall" "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server" ) type ipcTargets map[string]map[string][]string -// getProcessExitCode returns the exit code from a ProcessState. -// For normal exits, returns the exit code directly. -// For signal termination, returns 128 + signal number (Unix convention). -func getProcessExitCode(state *os.ProcessState) int { - if state == nil { - return 1 - } - if code := state.ExitCode(); code != -1 { - return code - } - // Process was killed by signal - extract signal number - if status, ok := state.Sys().(syscall.WaitStatus); ok { - if status.Signaled() { - return 128 + int(status.Signal()) - } - } - return 1 -} - -var isSessionManaged bool - -func execDetachedRestart(targetPID int) { - selfPath, err := os.Executable() - if err != nil { - return - } - - cmd := exec.Command(selfPath, "restart-detached", strconv.Itoa(targetPID)) - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setsid: true, - } - cmd.Start() -} - -func runDetachedRestart(targetPIDStr string) { - targetPID, err := strconv.Atoi(targetPIDStr) - if err != nil { - return - } - - time.Sleep(200 * time.Millisecond) - - proc, err := os.FindProcess(targetPID) - if err == nil { - proc.Signal(syscall.SIGTERM) - } - - time.Sleep(500 * time.Millisecond) - - killShell() - runShellDaemon(false) -} - -func getRuntimeDir() string { - if runtime := os.Getenv("XDG_RUNTIME_DIR"); runtime != "" { - return runtime - } - return os.TempDir() -} - -func appendLogEnv(env []string) []string { - if v := os.Getenv("DMS_LOG_LEVEL"); v != "" { - env = append(env, "DMS_LOG_LEVEL="+v) - } - if v := os.Getenv("DMS_LOG_FILE"); v != "" { - env = append(env, "DMS_LOG_FILE="+v) - } - return env -} - -func withDMSExecutable(env []string) []string { - selfPath, err := os.Executable() - if err != nil { - return env - } - return append(env, "DMS_EXECUTABLE="+selfPath) -} - -func hasSystemdRun() bool { - _, err := exec.LookPath("systemd-run") - return err == nil -} - -func getPIDFilePath() string { - return filepath.Join(getRuntimeDir(), fmt.Sprintf("danklinux-%d.pid", os.Getpid())) -} - -func getSessionFilePath() string { - return filepath.Join(getRuntimeDir(), fmt.Sprintf("danklinux-%d.session", os.Getpid())) -} - -func writePIDFile(childPID int) error { - pidFile := getPIDFilePath() - if display := os.Getenv("WAYLAND_DISPLAY"); display != "" { - if err := os.WriteFile(getSessionFilePath(), []byte(display), 0o644); err != nil { - log.Warnf("Failed to write session file: %v", err) - } - } - return os.WriteFile(pidFile, []byte(strconv.Itoa(childPID)), 0o644) -} - -func removePIDFile() { - os.Remove(getPIDFilePath()) - os.Remove(getSessionFilePath()) -} - -func getAllDMSPIDs() []int { - dir := getRuntimeDir() - entries, err := os.ReadDir(dir) - if err != nil { - return nil - } - - var pids []int - - for _, entry := range entries { - if !strings.HasPrefix(entry.Name(), "danklinux-") || !strings.HasSuffix(entry.Name(), ".pid") { - continue - } - - pidFile := filepath.Join(dir, entry.Name()) - data, err := os.ReadFile(pidFile) - if err != nil { - continue - } - - childPID, err := strconv.Atoi(strings.TrimSpace(string(data))) - if err != nil { - os.Remove(pidFile) - continue - } - - proc, err := os.FindProcess(childPID) - if err != nil { - os.Remove(pidFile) - continue - } - - if err := proc.Signal(syscall.Signal(0)); err != nil { - os.Remove(pidFile) - continue - } - - pids = append(pids, childPID) - - parentPIDStr := strings.TrimPrefix(entry.Name(), "danklinux-") - parentPIDStr = strings.TrimSuffix(parentPIDStr, ".pid") - if parentPID, err := strconv.Atoi(parentPIDStr); err == nil { - if parentProc, err := os.FindProcess(parentPID); err == nil { - if err := parentProc.Signal(syscall.Signal(0)); err == nil { - pids = append(pids, parentPID) - } - } - } - } - - return pids -} - -func runShellInteractive(session bool) { - isSessionManaged = session - go printASCII() - fmt.Fprintf(os.Stderr, "dms %s\n", Version) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - socketPath := server.GetSocketPath() - - configStateFile := filepath.Join(getRuntimeDir(), "danklinux.path") - if err := os.WriteFile(configStateFile, []byte(configPath), 0o644); err != nil { - log.Warnf("Failed to write config state file: %v", err) - } - defer os.Remove(configStateFile) - - errChan := make(chan error, 2) - - go func() { - defer func() { - if r := recover(); r != nil { - errChan <- fmt.Errorf("server panic: %v", r) - } - }() - server.CLIVersion = Version - if err := server.Start(false); err != nil { - errChan <- fmt.Errorf("server error: %w", err) - } - }() - - ensureFontCache() - log.Infof("Spawning quickshell with -p %s", configPath) - - cmd := exec.CommandContext(ctx, "qs", "-p", configPath) - cmd.Env = withDMSExecutable(append(os.Environ(), "DMS_SOCKET="+socketPath)) - if os.Getenv("QT_LOGGING_RULES") == "" { - if qtRules := log.GetQtLoggingRules(); qtRules != "" { - cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules) - } - } - - if isSessionManaged && hasSystemdRun() { - cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope") - } - - homeDir, err := os.UserHomeDir() - if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" { - if !strings.HasPrefix(configPath, homeDir) { - cmd.Env = append(cmd.Env, "DMS_DISABLE_HOT_RELOAD=1") - } - } - - if os.Getenv("QT_QPA_PLATFORMTHEME") == "" { - cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME=gtk3") - } - if os.Getenv("QT_QPA_PLATFORMTHEME_QT6") == "" { - cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME_QT6=gtk3") - } - if os.Getenv("QT_QPA_PLATFORM") == "" { - cmd.Env = append(cmd.Env, "QT_QPA_PLATFORM=wayland;xcb") - } - if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" { - cmd.Env = append(cmd.Env, "QSG_USE_SIMPLE_ANIMATION_DRIVER=1") - } - - cmd.Env = appendLogEnv(cmd.Env) - - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - tracker := &stderrTracker{parent: os.Stderr} - cmd.Stderr = tracker - - startTime := time.Now() - if err := cmd.Start(); err != nil { - log.Fatalf("Error starting quickshell: %v", err) - } - - // Write PID file for the quickshell child process - if err := writePIDFile(cmd.Process.Pid); err != nil { - log.Warnf("Failed to write PID file: %v", err) - } - defer removePIDFile() - - defer func() { - if cmd.Process != nil { - cmd.Process.Signal(syscall.SIGTERM) - } - }() - - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1) - - go func() { - if err := cmd.Wait(); err != nil { - errChan <- fmt.Errorf("quickshell exited: %w", err) - } else { - errChan <- fmt.Errorf("quickshell exited") - } - }() - - for { - select { - case sig := <-sigChan: - if sig == syscall.SIGUSR1 { - if isSessionManaged { - log.Infof("Received SIGUSR1, exiting for systemd restart...") - cancel() - cmd.Process.Signal(syscall.SIGTERM) - os.Remove(socketPath) - os.Exit(1) - } - log.Infof("Received SIGUSR1, spawning detached restart process...") - execDetachedRestart(os.Getpid()) - return - } - - // Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released) - select { - case <-errChan: - cancel() - os.Remove(socketPath) - exitCode := getProcessExitCode(cmd.ProcessState) - logStartupFailure(startTime, exitCode, tracker) - os.Exit(exitCode) - case <-time.After(500 * time.Millisecond): - } - - log.Infof("\nReceived signal %v, shutting down...", sig) - cancel() - cmd.Process.Signal(syscall.SIGTERM) - os.Remove(socketPath) - return - - case err := <-errChan: - log.Error(err) - cancel() - if cmd.Process != nil { - cmd.Process.Signal(syscall.SIGTERM) - } - os.Remove(socketPath) - exitCode := getProcessExitCode(cmd.ProcessState) - logStartupFailure(startTime, exitCode, tracker) - os.Exit(exitCode) - } - } -} - -func restartShell() { - pids := getAllDMSPIDs() - - if len(pids) == 0 { - log.Info("No running DMS shell instances found. Starting daemon...") - runShellDaemon(false) - return - } - - currentPid := os.Getpid() - uniquePids := make(map[int]bool) - - for _, pid := range pids { - if pid != currentPid { - uniquePids[pid] = true - } - } - - for pid := range uniquePids { - proc, err := os.FindProcess(pid) - if err != nil { - log.Errorf("Error finding process %d: %v", pid, err) - continue - } - - if err := proc.Signal(syscall.Signal(0)); err != nil { - continue - } - - if err := proc.Signal(syscall.SIGUSR1); err != nil { - log.Errorf("Error sending SIGUSR1 to process %d: %v", pid, err) - } else { - log.Infof("Sent SIGUSR1 to DMS process with PID %d", pid) - } - } -} - -func killShell() { - pids := getAllDMSPIDs() - - if len(pids) == 0 { - log.Info("No running DMS shell instances found.") - return - } - - currentPid := os.Getpid() - uniquePids := make(map[int]bool) - - for _, pid := range pids { - if pid != currentPid { - uniquePids[pid] = true - } - } - - for pid := range uniquePids { - proc, err := os.FindProcess(pid) - if err != nil { - log.Errorf("Error finding process %d: %v", pid, err) - continue - } - - if err := proc.Signal(syscall.Signal(0)); err != nil { - continue - } - - if err := proc.Kill(); err != nil { - log.Errorf("Error killing process %d: %v", pid, err) - } else { - log.Infof("Killed DMS process with PID %d", pid) - } - } - - dir := getRuntimeDir() - entries, err := os.ReadDir(dir) - if err != nil { - return - } - - for _, entry := range entries { - if !strings.HasPrefix(entry.Name(), "danklinux-") { - continue - } - if strings.HasSuffix(entry.Name(), ".pid") || strings.HasSuffix(entry.Name(), ".session") { - os.Remove(filepath.Join(dir, entry.Name())) - } - } -} - -func runShellDaemon(session bool) { - isSessionManaged = session - isDaemonChild := slices.Contains(os.Args, "--daemon-child") - - if !isDaemonChild { - fmt.Fprintf(os.Stderr, "dms %s\n", Version) - - cmd := exec.Command(os.Args[0], "run", "-d", "--daemon-child") - cmd.Env = os.Environ() - - cmd.SysProcAttr = &syscall.SysProcAttr{ - Setsid: true, - } - - if err := cmd.Start(); err != nil { - log.Fatalf("Error starting daemon: %v", err) - } - - log.Infof("DMS shell daemon started (PID: %d)", cmd.Process.Pid) - return - } - - fmt.Fprintf(os.Stderr, "dms %s\n", Version) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - socketPath := server.GetSocketPath() - - configStateFile := filepath.Join(getRuntimeDir(), "danklinux.path") - if err := os.WriteFile(configStateFile, []byte(configPath), 0o644); err != nil { - log.Warnf("Failed to write config state file: %v", err) - } - defer os.Remove(configStateFile) - - errChan := make(chan error, 2) - - go func() { - defer func() { - if r := recover(); r != nil { - errChan <- fmt.Errorf("server panic: %v", r) - } - }() - server.CLIVersion = Version - if err := server.Start(false); err != nil { - errChan <- fmt.Errorf("server error: %w", err) - } - }() - - ensureFontCache() - log.Infof("Spawning quickshell with -p %s", configPath) - - cmd := exec.CommandContext(ctx, "qs", "-p", configPath) - cmd.Env = withDMSExecutable(append(os.Environ(), "DMS_SOCKET="+socketPath)) - if os.Getenv("QT_LOGGING_RULES") == "" { - if qtRules := log.GetQtLoggingRules(); qtRules != "" { - cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules) - } - } - - // ! TODO - remove when QS 0.3 is up and we can use the pragma - cmd.Env = append(cmd.Env, "QS_APP_ID=com.danklinux.dms") - - if isSessionManaged && hasSystemdRun() { - cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope") - } - - homeDir, err := os.UserHomeDir() - if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" { - if !strings.HasPrefix(configPath, homeDir) { - cmd.Env = append(cmd.Env, "DMS_DISABLE_HOT_RELOAD=1") - } - } - - if os.Getenv("QT_QPA_PLATFORMTHEME") == "" { - cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME=gtk3") - } - if os.Getenv("QT_QPA_PLATFORMTHEME_QT6") == "" { - cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME_QT6=gtk3") - } - if os.Getenv("QT_QPA_PLATFORM") == "" { - cmd.Env = append(cmd.Env, "QT_QPA_PLATFORM=wayland;xcb") - } - if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" { - cmd.Env = append(cmd.Env, "QSG_USE_SIMPLE_ANIMATION_DRIVER=1") - } - - cmd.Env = appendLogEnv(cmd.Env) - - devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0) - if err != nil { - log.Fatalf("Error opening /dev/null: %v", err) - } - defer devNull.Close() - - cmd.Stdin = devNull - cmd.Stdout = devNull - tracker := &stderrTracker{parent: devNull} - cmd.Stderr = tracker - - startTime := time.Now() - if err := cmd.Start(); err != nil { - log.Fatalf("Error starting daemon: %v", err) - } - - // Write PID file for the quickshell child process - if err := writePIDFile(cmd.Process.Pid); err != nil { - log.Warnf("Failed to write PID file: %v", err) - } - defer removePIDFile() - - defer func() { - if cmd.Process != nil { - cmd.Process.Signal(syscall.SIGTERM) - } - }() - - sigChan := make(chan os.Signal, 1) - signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1) - - go func() { - if err := cmd.Wait(); err != nil { - errChan <- fmt.Errorf("quickshell exited: %w", err) - } else { - errChan <- fmt.Errorf("quickshell exited") - } - }() - - for { - select { - case sig := <-sigChan: - if sig == syscall.SIGUSR1 { - if isSessionManaged { - log.Infof("Received SIGUSR1, exiting for systemd restart...") - cancel() - cmd.Process.Signal(syscall.SIGTERM) - os.Remove(socketPath) - os.Exit(1) - } - log.Infof("Received SIGUSR1, spawning detached restart process...") - execDetachedRestart(os.Getpid()) - return - } - - // Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released) - select { - case <-errChan: - cancel() - os.Remove(socketPath) - exitCode := getProcessExitCode(cmd.ProcessState) - logStartupFailure(startTime, exitCode, tracker) - os.Exit(exitCode) - case <-time.After(500 * time.Millisecond): - } - - cancel() - cmd.Process.Signal(syscall.SIGTERM) - os.Remove(socketPath) - return - - case <-errChan: - cancel() - if cmd.Process != nil { - cmd.Process.Signal(syscall.SIGTERM) - } - os.Remove(socketPath) - exitCode := getProcessExitCode(cmd.ProcessState) - logStartupFailure(startTime, exitCode, tracker) - os.Exit(exitCode) - } - } -} - var qsHasAnyDisplay = sync.OnceValue(func() bool { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() @@ -632,17 +61,17 @@ func parseTargetsFromIPCShowOutput(output string) ipcTargets { func buildQsIPCBaseArgs() ([]string, error) { cmdArgs := []string{"ipc"} - switch pid, ok := getSessionDMSPID(); { + switch pid, ok := shellApp.SessionPID(); { case ok: cmdArgs = append(cmdArgs, "--pid", strconv.Itoa(pid)) default: - if err := findConfig(nil, nil); err != nil { + if err := shellApp.ResolveConfig(nil, nil); err != nil { return nil, err } if qsHasAnyDisplay() { cmdArgs = append(cmdArgs, "--any-display") } - cmdArgs = append(cmdArgs, "-p", configPath) + cmdArgs = append(cmdArgs, "-p", shellApp.ConfigPath()) } return cmdArgs, nil } @@ -699,101 +128,6 @@ func getShellIPCCompletions(args []string, _ string) []string { return nil } -func getFirstDMSPID() (int, bool) { - dir := getRuntimeDir() - entries, err := os.ReadDir(dir) - if err != nil { - return 0, false - } - - for _, entry := range entries { - if !strings.HasPrefix(entry.Name(), "danklinux-") || !strings.HasSuffix(entry.Name(), ".pid") { - continue - } - - data, err := os.ReadFile(filepath.Join(dir, entry.Name())) - if err != nil { - continue - } - - pid, err := strconv.Atoi(strings.TrimSpace(string(data))) - if err != nil { - continue - } - - proc, err := os.FindProcess(pid) - if err != nil { - continue - } - - if proc.Signal(syscall.Signal(0)) != nil { - continue - } - - return pid, true - } - - return 0, false -} - -func sessionParentPID(display string) (int, bool) { - if display == "" { - return 0, false - } - - dir := getRuntimeDir() - entries, err := os.ReadDir(dir) - if err != nil { - return 0, false - } - - for _, entry := range entries { - name := entry.Name() - if !strings.HasPrefix(name, "danklinux-") || !strings.HasSuffix(name, ".session") { - continue - } - - data, err := os.ReadFile(filepath.Join(dir, name)) - if err != nil || strings.TrimSpace(string(data)) != display { - continue - } - - parentStr := strings.TrimSuffix(strings.TrimPrefix(name, "danklinux-"), ".session") - parentPID, err := strconv.Atoi(parentStr) - if err != nil { - continue - } - - return parentPID, true - } - - return 0, false -} - -func getSessionDMSPID() (int, bool) { - parentPID, ok := sessionParentPID(os.Getenv("WAYLAND_DISPLAY")) - if !ok { - return getFirstDMSPID() - } - - data, err := os.ReadFile(filepath.Join(getRuntimeDir(), fmt.Sprintf("danklinux-%d.pid", parentPID))) - if err != nil { - return getFirstDMSPID() - } - - pid, err := strconv.Atoi(strings.TrimSpace(string(data))) - if err != nil { - return getFirstDMSPID() - } - - proc, err := os.FindProcess(pid) - if err != nil || proc.Signal(syscall.Signal(0)) != nil { - return getFirstDMSPID() - } - - return pid, true -} - func runShellIPCCommand(args []string) { if len(args) == 0 { printIPCHelp() @@ -946,36 +280,12 @@ func rebuildFontCache() { } } -type stderrTracker struct { - mu sync.Mutex - buf strings.Builder - parent io.Writer -} - -func (s *stderrTracker) Write(p []byte) (n int, err error) { - s.mu.Lock() - defer s.mu.Unlock() - if s.buf.Len() < 8192 { - s.buf.Write(p) - } - if s.parent != nil { - return s.parent.Write(p) - } - return len(p), nil -} - -func (s *stderrTracker) String() string { - s.mu.Lock() - defer s.mu.Unlock() - return s.buf.String() -} - // logStartupFailure logs diagnostic advice if qs crashes within 5s of launch. -func logStartupFailure(startTime time.Time, exitCode int, tracker *stderrTracker) { - if time.Since(startTime) >= 5*time.Second || exitCode == 0 || exitCode > 128 { +func logStartupFailure(exitCode int, uptime time.Duration, stderrTail string) { + if uptime >= 5*time.Second || exitCode == 0 || exitCode > 128 { return } - if containsFontCrashSignature(tracker.String()) { + if containsFontCrashSignature(stderrTail) { log.Errorf("DMS startup failed due to a potential font/rendering crash. Try running 'fc-cache -fv' and restarting DMS.") } else { log.Errorf("DMS startup failed (exit code %d). Run 'dms doctor' for more diagnostics.", exitCode) diff --git a/core/cmd/dms/shellapp.go b/core/cmd/dms/shellapp.go new file mode 100644 index 000000000..473b65441 --- /dev/null +++ b/core/cmd/dms/shellapp.go @@ -0,0 +1,82 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/AvengeMedia/DankMaterialShell/core/internal/config" + "github.com/AvengeMedia/DankMaterialShell/core/internal/log" + "github.com/AvengeMedia/DankMaterialShell/core/internal/server" + "github.com/AvengeMedia/DankMaterialShell/core/internal/shellembed" + "github.com/AvengeMedia/dankgo/shellapp" +) + +var shellApp = shellapp.New(shellapp.Config{ + ID: "danklinux", + EnvPrefix: "DMS", + QSAppID: "com.danklinux.dms", + Version: Version, + Embedded: embeddedShell{}, + Boot: bootBackend, + PreLaunch: preLaunch, + ExtraEnv: dmsExtraEnv, + OnUIExit: logStartupFailure, +}) + +type embeddedShell struct{} + +func (embeddedShell) Available() bool { return shellembed.Available() } + +func (embeddedShell) Extract(baseDir string) (string, error) { return shellembed.Extract(baseDir) } + +func (embeddedShell) Prune(baseDir, keep string) { shellembed.Prune(baseDir, keep) } + +type dmsBackend struct { + srv *server.Server + done chan error +} + +func (b *dmsBackend) SocketPath() string { return b.srv.SocketPath() } + +func (b *dmsBackend) Close() { b.srv.Close() } + +func (b *dmsBackend) Done() <-chan error { return b.done } + +func bootBackend(ctx context.Context) (shellapp.Backend, error) { + config.CleanupStrayHyprlandConfFile(log.Infof) + server.CLIVersion = Version + + srv := server.New() + if err := srv.Listen(); err != nil { + return nil, err + } + + backend := &dmsBackend{srv: srv, done: make(chan error, 1)} + go func() { + defer func() { + if r := recover(); r != nil { + backend.done <- fmt.Errorf("server panic: %v", r) + } + }() + backend.done <- srv.Serve(false) + }() + + return backend, nil +} + +func preLaunch() { + go printASCII() + ensureFontCache() +} + +func dmsExtraEnv(string) []string { + var env []string + if selfPath, err := os.Executable(); err == nil { + env = append(env, "DMS_EXECUTABLE="+selfPath) + } + if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" { + env = append(env, "QSG_USE_SIMPLE_ANIMATION_DRIVER=1") + } + return env +} diff --git a/core/go.mod b/core/go.mod index 8195a8e38..92c7410be 100644 --- a/core/go.mod +++ b/core/go.mod @@ -1,14 +1,13 @@ module github.com/AvengeMedia/DankMaterialShell/core -go 1.26.1 +go 1.26.4 require ( github.com/Wifx/gonetworkmanager/v2 v2.2.0 - github.com/alecthomas/chroma/v2 v2.24.1 + github.com/alecthomas/chroma/v2 v2.27.0 github.com/charmbracelet/bubbles v1.0.0 github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/lipgloss v1.1.0 - github.com/charmbracelet/log v1.0.0 github.com/fsnotify/fsnotify v1.10.1 github.com/godbus/dbus/v5 v5.2.2 github.com/holoplot/go-evdev v0.0.0-20260504100651-66d1748fe847 @@ -18,12 +17,12 @@ require ( github.com/stretchr/testify v1.11.1 github.com/yeqown/go-qrcode/v2 v2.2.5 github.com/yeqown/go-qrcode/writer/standard v1.3.0 - github.com/yuin/goldmark v1.8.2 + github.com/yuin/goldmark v1.8.4 github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc - go.etcd.io/bbolt v1.4.3 + go.etcd.io/bbolt v1.5.0 go4.org/mem v0.0.0-20240501181205-ae6ca9944745 - golang.org/x/image v0.39.0 - tailscale.com v1.96.5 + golang.org/x/image v0.44.0 + tailscale.com v1.100.0 ) require ( @@ -31,28 +30,28 @@ require ( github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/akutz/memconn v0.1.0 // indirect + github.com/charmbracelet/log v1.0.0 // indirect github.com/clipperhouse/displaywidth v0.11.0 // indirect github.com/clipperhouse/uax29/v2 v2.7.0 // indirect - github.com/cloudflare/circl v1.6.3 // indirect - github.com/coder/websocket v1.8.14 // indirect - github.com/cyphar/filepath-securejoin v0.6.1 // indirect - github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d // indirect - github.com/dlclark/regexp2 v1.12.0 // indirect + github.com/cloudflare/circl v1.6.4 // indirect + github.com/coder/websocket v1.8.15 // indirect + github.com/dblohm7/wingoes v0.0.0-20260526185140-fb298caac7ca // indirect + github.com/dlclark/regexp2/v2 v2.5.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/fogleman/gg v1.3.0 // indirect github.com/fxamacker/cbor/v2 v2.9.2 // indirect github.com/go-git/gcfg/v2 v2.0.2 // indirect - github.com/go-git/go-billy/v6 v6.0.0-20260504142752-cb8e9d337266 // indirect - github.com/go-json-experiment/json v0.0.0-20260430182902-b6187a392ed4 // indirect + github.com/go-git/go-billy/v6 v6.0.0-alpha.1 // indirect + github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 // indirect github.com/go-logfmt/logfmt v0.6.1 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/hdevalence/ed25519consensus v0.2.0 // indirect github.com/jsimonetti/rtnetlink v1.4.2 // indirect github.com/kevinburke/ssh_config v1.6.0 // indirect - github.com/klauspost/cpuid/v2 v2.3.0 // indirect - github.com/mdlayher/netlink v1.11.1 // indirect - github.com/mdlayher/socket v0.6.0 // indirect + github.com/klauspost/cpuid/v2 v2.4.0 // indirect + github.com/mdlayher/netlink v1.11.2 // indirect + github.com/mdlayher/socket v0.6.1 // indirect github.com/mitchellh/go-ps v1.0.0 // indirect github.com/pjbgf/sha1cd v0.6.0 // indirect github.com/pkg/errors v0.9.1 // indirect @@ -62,14 +61,15 @@ require ( github.com/x448/float16 v0.8.4 // indirect github.com/yeqown/reedsolomon v1.0.0 // indirect go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect - golang.org/x/crypto v0.50.0 // indirect - golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f // indirect - golang.org/x/net v0.53.0 // indirect - golang.org/x/sync v0.20.0 // indirect + golang.org/x/crypto v0.54.0 // indirect + golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 // indirect + golang.org/x/net v0.57.0 // indirect + golang.org/x/sync v0.22.0 // indirect golang.zx2c4.com/wireguard/windows v1.0.1 // indirect ) require ( + github.com/AvengeMedia/dankgo v0.0.0-20260718184203-ed59ffc6e599 github.com/atotto/clipboard v0.1.4 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/charmbracelet/colorprofile v0.4.3 // indirect @@ -79,22 +79,22 @@ require ( github.com/charmbracelet/x/term v0.2.2 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/go-git/go-git/v6 v6.0.0-alpha.2 + github.com/go-git/go-git/v6 v6.0.0-alpha.4 github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/lucasb-eyer/go-colorful v1.4.0 - github.com/mattn/go-isatty v0.0.22 + github.com/mattn/go-isatty v0.0.23 github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.23 // indirect + github.com/mattn/go-runewidth v0.0.24 // indirect github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect - github.com/muesli/termenv v0.16.0 + github.com/muesli/termenv v0.16.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/afero v1.15.0 github.com/spf13/pflag v1.0.10 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/sys v0.43.0 - golang.org/x/text v0.36.0 + golang.org/x/sys v0.47.0 + golang.org/x/text v0.40.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/core/go.sum b/core/go.sum index e1bb4ebd9..836ab74eb 100644 --- a/core/go.sum +++ b/core/go.sum @@ -1,5 +1,7 @@ filippo.io/edwards25519 v1.2.0 h1:crnVqOiS4jqYleHd9vaKZ+HKtHfllngJIiOpNpoJsjo= filippo.io/edwards25519 v1.2.0/go.mod h1:xzAOLCNug/yB62zG1bQ8uziwrIqIuxhctzJT18Q77mc= +github.com/AvengeMedia/dankgo v0.0.0-20260718184203-ed59ffc6e599 h1:hxy5SRGREsQ6v4xUOxWnWVPWD6ZU+H0jA+LHcTwXbCg= +github.com/AvengeMedia/dankgo v0.0.0-20260718184203-ed59ffc6e599/go.mod h1:7p7cfydr4WM1G6eOPFlANXF3IV5du3FoA4CbDPprHAo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= @@ -11,8 +13,8 @@ github.com/akutz/memconn v0.1.0/go.mod h1:Jo8rI7m0NieZyLI5e2CDlRdRqRRB4S7Xp77ukD github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0= github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k= github.com/alecthomas/chroma/v2 v2.2.0/go.mod h1:vf4zrexSH54oEjJ7EdB65tGNHmH3pGZmVkgTP5RHvAs= -github.com/alecthomas/chroma/v2 v2.24.1 h1:m5ffpfZbIb++k8AqFEKy9uVgY12xIQtBsQlc6DfZJQM= -github.com/alecthomas/chroma/v2 v2.24.1/go.mod h1:l+ohZ9xRXIbGe7cIW+YZgOGbvuVLjMps/FYN/CwuabI= +github.com/alecthomas/chroma/v2 v2.27.0 h1:FodwmyOBgJULFYmDqibcp9pvfDLWdtPRh9v/r5BXYZs= +github.com/alecthomas/chroma/v2 v2.27.0/go.mod h1:NjJ3ciIgrqBNeIkWZ4e46nseoLDslxU1LmfCoL+wcY8= github.com/alecthomas/repr v0.0.0-20220113201626-b1b626ac65ae/go.mod h1:2kn6fqh/zIyPLmm3ugklbEi5hg5wS435eygvNfaDQL8= github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs= github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= @@ -48,25 +50,23 @@ github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSE github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= -github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= -github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= -github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= -github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/cloudflare/circl v1.6.4 h1:pOXuDTCEYyzydgUpQ0CQz3LsinKjiSk6nNP5Lt5K64U= +github.com/cloudflare/circl v1.6.4/go.mod h1:YxarevkLlbaHuWsxG6vmYNWBEsSp4pnp7j+4VljMavY= +github.com/coder/websocket v1.8.15 h1:6B2JPeOGlpff2Uz6vOEH1Vzpi0iUz20A+lPVhPHtNUA= +github.com/coder/websocket v1.8.15/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creachadair/taskgroup v0.13.2 h1:3KyqakBuFsm3KkXi/9XIb0QcA8tEzLHLgaoidf0MdVc= github.com/creachadair/taskgroup v0.13.2/go.mod h1:i3V1Zx7H8RjwljUEeUWYT30Lmb9poewSb2XI1yTwD0g= -github.com/cyphar/filepath-securejoin v0.6.1 h1:5CeZ1jPXEiYt3+Z6zqprSAgSWiggmpVyciv8syjIpVE= -github.com/cyphar/filepath-securejoin v0.6.1/go.mod h1:A8hd4EnAeyujCJRrICiOWqjS1AX0a9kM5XL+NwKoYSc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d h1:QRKpU+9ZBDs62LyBfwhZkJdB5DJX2Sm3p4kUh7l1aA0= -github.com/dblohm7/wingoes v0.0.0-20250822163801-6d8e6105c62d/go.mod h1:SUxUaAK/0UG5lYyZR1L1nC4AaYYvSSYTWQSH3FPcxKU= +github.com/dblohm7/wingoes v0.0.0-20260526185140-fb298caac7ca h1:h1Awca4lQOspNR/2eeo04Ricn5NixDX9mb17WSAgLhQ= +github.com/dblohm7/wingoes v0.0.0-20260526185140-fb298caac7ca/go.mod h1:2TGl1jRJrRpbzykmg7asHm3h08TqutUgQqY5v9k/g3c= github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8= -github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dlclark/regexp2/v2 v2.5.1 h1:E5Ug7Dh264W1ymdySmiHNcDG7fmsR307APCE5R07a20= +github.com/dlclark/regexp2/v2 v2.5.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= @@ -85,14 +85,14 @@ github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= github.com/go-git/gcfg/v2 v2.0.2 h1:MY5SIIfTGGEMhdA7d7JePuVVxtKL7Hp+ApGDJAJ7dpo= github.com/go-git/gcfg/v2 v2.0.2/go.mod h1:/lv2NsxvhepuMrldsFilrgct6pxzpGdSRC13ydTLSLs= -github.com/go-git/go-billy/v6 v6.0.0-20260504142752-cb8e9d337266 h1:wH21vHuv323v9x78JNFNJ6P7HEAsdwr9yq2k9/o4zEE= -github.com/go-git/go-billy/v6 v6.0.0-20260504142752-cb8e9d337266/go.mod h1:CdBVp7CXl9l3sOyNEog46cP1Pvx/hjCe9AD0mtaIUYU= -github.com/go-git/go-git-fixtures/v6 v6.0.0-20260405195209-b16dd39735e0 h1:XoTsdvaghuVfIr7HpNTmFDLu2nz3I2iGqyn6Uk6MkJc= -github.com/go-git/go-git-fixtures/v6 v6.0.0-20260405195209-b16dd39735e0/go.mod h1:1Lr7/vYEYyl6Ir9Ku0tKrCIRreM5zovv0Jdx2MPSM4s= -github.com/go-git/go-git/v6 v6.0.0-alpha.2 h1:T3loNtDuAixNzXtlQxZhnYiYpaQ3CA4vn9RssAniEeI= -github.com/go-git/go-git/v6 v6.0.0-alpha.2/go.mod h1:oCD3i19CTz7gBpeb11ZZqL91WzqbMq9avn5KpUYy/Ak= -github.com/go-json-experiment/json v0.0.0-20260430182902-b6187a392ed4 h1:2WmHkJINIjgXXYDGik8d3oJvFA3DAwPy00csDJ3vo+o= -github.com/go-json-experiment/json v0.0.0-20260430182902-b6187a392ed4/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= +github.com/go-git/go-billy/v6 v6.0.0-alpha.1 h1:xVjAR4oUvrKy7/Xuw/lLlV3gkxR3KO2H8W+MamuVVsQ= +github.com/go-git/go-billy/v6 v6.0.0-alpha.1/go.mod h1:eaCUpHbedW7//EwcYmUDfJe2N6sJC9O12AT0OTqJR1E= +github.com/go-git/go-git-fixtures/v6 v6.0.0-alpha.1 h1:gmqi2jvsreu0s8JMLylYDFq4sbjHwwlhktMw0DUg3mA= +github.com/go-git/go-git-fixtures/v6 v6.0.0-alpha.1/go.mod h1:ECf1MqJlBdYpKggBrOXjo/0EnvRZx6D++I86UYjPgAQ= +github.com/go-git/go-git/v6 v6.0.0-alpha.4 h1:aDTc2UGanmaE7FkGLSlBEB9nohMnQ+RKXcfq/D+esDQ= +github.com/go-git/go-git/v6 v6.0.0-alpha.4/go.mod h1:4ODa/G7hPWrh4Y+7lmt59Ij3zW38IEfvRoAZxLYYBhc= +github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68 h1:KZaTBSyshWX3MP5jukJcNSuXDQTO+rNpt0J564dX/eg= +github.com/go-json-experiment/json v0.0.0-20260623181947-01eb4420fa68/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg= github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -117,8 +117,8 @@ github.com/jsimonetti/rtnetlink v1.4.2 h1:Df9w9TZ3npHTyDn0Ev9e1uzmN2odmXd0QX+J5G github.com/jsimonetti/rtnetlink v1.4.2/go.mod h1:92s6LJdE+1iOrw+F2/RO7LYI2Qd8pPpFNNUYW06gcoM= github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY= github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= -github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= -github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= +github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -128,16 +128,16 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/lucasb-eyer/go-colorful v1.4.0 h1:UtrWVfLdarDgc44HcS7pYloGHJUjHV/4FwW4TvVgFr4= github.com/lucasb-eyer/go-colorful v1.4.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= -github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4= -github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4= +github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ= +github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75 h1:P8UmIzZMYDR+NGImiFvErt6VWfIRPuGM+vyjiEdkmIw= github.com/mattn/go-localereader v0.0.2-0.20220822084749-2491eb6c1c75/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3RybWcw= -github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mdlayher/netlink v1.11.1 h1:T136gDS6Gkt+hLncaBwKdW5GpEC8Z0ykqimOebVoal0= -github.com/mdlayher/netlink v1.11.1/go.mod h1:ao4LjamyK4Uq9L8+fQzqFYpAncbeCdwbvd9Edv/pYnc= -github.com/mdlayher/socket v0.6.0 h1:ScZPaAGyO1icQnbFrhPM8mnXyMu9qukC1K4ZoM2IQKU= -github.com/mdlayher/socket v0.6.0/go.mod h1:q7vozUAnxSqnjHc12Fik5yUKIzfZ8ITCfMkhOtE9z18= +github.com/mattn/go-runewidth v0.0.24 h1:cpokDiIn0MGnhdHwuWnJBITySJ20QyNGnY2kR/ay2DU= +github.com/mattn/go-runewidth v0.0.24/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/mdlayher/netlink v1.11.2 h1:HKh2jqe+omdSWcQ88nrT7INE61B0NXfiSPFdgL4YbNI= +github.com/mdlayher/netlink v1.11.2/go.mod h1:uT2Yc/QLaZubzDpZIBi9d4GoeLwtp3x1AMeqSRrK2sA= +github.com/mdlayher/socket v0.6.1 h1:M7uj2NtuujUY4mYr1C57NmfNiRHbkKpnBxO856lsc3A= +github.com/mdlayher/socket v0.6.1/go.mod h1:+/SGtqc9V+5dAuRgQsU0fGBI+oRDiW7O2Obx10OIWfg= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= @@ -180,8 +180,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55 h1:Gzfnfk2TWrk8Jj4P4c1a3CtQyMaTVCznlkLZI++hok4= github.com/tailscale/go-winio v0.0.0-20231025203758-c4f33415bf55/go.mod h1:4k4QO+dQ3R5FofL+SanAUZe+/QfeK0+OIuwDIRu2vSg= -github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw= -github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4= +github.com/tailscale/wireguard-go v0.0.0-20260527010701-b48af7099cad h1:Ky26FR5yZ5IKEB0xtm5A8xSTb06ImY7kxBFrvgOmJSg= +github.com/tailscale/wireguard-go v0.0.0-20260527010701-b48af7099cad/go.mod h1:6SerzcvHWQchKO2BfNdmquA77CHSECZuFl+D9fp4RnI= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= @@ -193,36 +193,36 @@ github.com/yeqown/go-qrcode/writer/standard v1.3.0/go.mod h1:O4MbzsotGCvy8upYPCR github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0= github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM= github.com/yuin/goldmark v1.4.15/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE= -github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= +github.com/yuin/goldmark v1.8.4 h1:oat/nd3U6NeQqFEL3xpEJq7d7c86NI+DbSNGAs4xnjA= +github.com/yuin/goldmark v1.8.4/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc h1:+IAOyRda+RLrxa1WC7umKOZRsGq4QrFFMYApOeHzQwQ= github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc/go.mod h1:ovIvrum6DQJA4QsJSovrkC4saKHQVs7TvcaeO8AIl5I= -go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= -go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= +go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU= +go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= go4.org/mem v0.0.0-20240501181205-ae6ca9944745 h1:Tl++JLUCe4sxGu8cTpDzRLd3tN7US4hOxG5YpKCzkek= go4.org/mem v0.0.0-20240501181205-ae6ca9944745/go.mod h1:reUoABIJ9ikfM5sgtSF3Wushcza7+WeD01VB9Lirh3g= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f h1:W3F4c+6OLc6H2lb//N1q4WpJkhzJCK5J6kUi1NTVXfM= -golang.org/x/exp v0.0.0-20260410095643-746e56fc9e2f/go.mod h1:J1xhfL/vlindoeF/aINzNzt2Bket5bjo9sdOYzOsU80= -golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww= -golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= +golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597 h1:qLvzZeaANDgyVOA8pyHCOStGlXn0rseXma+GQjeuv2g= +golang.org/x/exp v0.0.0-20260709172345-9ea1abe57597/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= +golang.org/x/image v0.44.0 h1:+tDekMZED9+LrtB3G5xzRggpVh9CARjZqROla3R3R+I= +golang.org/x/image v0.44.0/go.mod h1:V8K3KE9KKKE+pLpQDOeN18w9oacNSvy1tDOirTu4xtY= +golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk= +golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40= +golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= +golang.org/x/term v0.45.0/go.mod h1:9aqxs0blBcrm/n0L9QW0aRVD+ktan8ssZromtqJC43w= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg= golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI= golang.zx2c4.com/wireguard/windows v1.0.1 h1:eOxiDVbywPC+ZQqvdCK7x+ZwWXKbYv50TtH8ysFIbw8= @@ -235,5 +235,5 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -tailscale.com v1.96.5 h1:gNkfA/KSZAl6jCH9cj8urq00HRWItDDTtGsyATI89jA= -tailscale.com v1.96.5/go.mod h1:/3lnZBYb2UEwnN0MNu2SDXUtT06AGd5k0s+OWx3WmcY= +tailscale.com v1.100.0 h1:nm/M/dEaW9RaRsGUjW2HsSDpsZ60Jwd9k4gNW9tTFiE= +tailscale.com v1.100.0/go.mod h1:DQ9YBy85DpNlSyeU2XRIWzbAu3RsGp/frv+Khg57meE= diff --git a/core/internal/errdefs/errdefs.go b/core/internal/errdefs/errdefs.go index 3b5f15984..7cf3faf81 100644 --- a/core/internal/errdefs/errdefs.go +++ b/core/internal/errdefs/errdefs.go @@ -1,9 +1,15 @@ package errdefs -type ErrorType int +import ( + dankerrdefs "github.com/AvengeMedia/dankgo/errdefs" +) + +type ErrorType = dankerrdefs.ErrorType + +type CustomError = dankerrdefs.CustomError const ( - ErrTypeNotLinux ErrorType = iota + ErrTypeNotLinux ErrorType = dankerrdefs.AppErrorBase + iota ErrTypeInvalidArchitecture ErrTypeUnsupportedDistribution ErrTypeUnsupportedVersion @@ -22,20 +28,8 @@ const ( ErrTypeGeneric ) -type CustomError struct { - Type ErrorType - Message string -} - -func (e *CustomError) Error() string { - return e.Message -} - func NewCustomError(errType ErrorType, message string) error { - return &CustomError{ - Type: errType, - Message: message, - } + return dankerrdefs.NewCustomError(errType, message) } const ( diff --git a/core/internal/geolocation/client_geoclue.go b/core/internal/geolocation/client_geoclue.go index 90d68388d..b34c547c2 100644 --- a/core/internal/geolocation/client_geoclue.go +++ b/core/internal/geolocation/client_geoclue.go @@ -5,8 +5,8 @@ import ( "sync" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/dbusutil" + "github.com/AvengeMedia/dankgo/syncmap" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/log/log.go b/core/internal/log/log.go index d116cb403..fdf465f97 100644 --- a/core/internal/log/log.go +++ b/core/internal/log/log.go @@ -1,220 +1,32 @@ package log import ( - "io" - "os" - "regexp" - "strings" - "sync" - - "github.com/charmbracelet/lipgloss" - cblog "github.com/charmbracelet/log" - "github.com/mattn/go-isatty" - "github.com/muesli/termenv" + danklog "github.com/AvengeMedia/dankgo/log" ) -// Logger embeds the Charm Logger and adds Printf/Fatalf -type Logger struct{ *cblog.Logger } +type Logger = danklog.Logger -// Printf routes goose/info-style logs through Infof. -func (l *Logger) Printf(format string, v ...any) { l.Infof(format, v...) } - -// Fatalf keeps goose’s contract of exiting the program. -func (l *Logger) Fatalf(format string, v ...any) { l.Logger.Fatalf(format, v...) } - -var ( - logger *Logger - initLogger sync.Once - - logMu sync.Mutex - logFile *os.File - logStderr io.Writer = os.Stderr - - ansiRe = regexp.MustCompile(`\x1b\[[0-9;]*[a-zA-Z]`) -) - -// ansiStripWriter strips ANSI escape sequences before forwarding to w. Used -// for the file sink so colored stderr stays colored while the file stays plain. -type ansiStripWriter struct{ w io.Writer } - -func (a *ansiStripWriter) Write(p []byte) (int, error) { - stripped := ansiRe.ReplaceAll(p, nil) - if _, err := a.w.Write(stripped); err != nil { - return 0, err - } - return len(p), nil +func init() { + danklog.SetEnvPrefix("DMS") } -func parseLogLevel(level string) cblog.Level { - switch strings.ToLower(level) { - case "debug": - return cblog.DebugLevel - case "info": - return cblog.InfoLevel - case "warn", "warning": - return cblog.WarnLevel - case "error": - return cblog.ErrorLevel - case "fatal": - return cblog.FatalLevel - default: - return cblog.InfoLevel - } -} +func GetLogger() *Logger { return danklog.GetLogger() } -func GetQtLoggingRules() string { - level := os.Getenv("DMS_LOG_LEVEL") - if level == "" { - level = "info" - } +func GetQtLoggingRules() string { return danklog.GetQtLoggingRules() } - // scene carries QML engine warnings (e.g. QQuickImage "Cannot open" cache - // probes); suppressed except at debug level - var rules []string - switch strings.ToLower(level) { - case "fatal": - rules = []string{"*.debug=false", "*.info=false", "*.warning=false", "*.critical=false"} - case "error": - rules = []string{"*.debug=false", "*.info=false", "*.warning=false"} - case "warn", "warning": - rules = []string{"*.debug=false", "*.info=false", "scene.warning=false"} - case "info": - rules = []string{"*.debug=false", "scene.warning=false"} - case "debug": - return "" - default: - rules = []string{"*.debug=false", "scene.warning=false"} - } +func SetLevel(level string) { danklog.SetLevel(level) } - return strings.Join(rules, ";") -} +func SetLogFile(path string) error { return danklog.SetLogFile(path) } -// GetLogger returns a logger instance -func GetLogger() *Logger { - initLogger.Do(func() { - styles := cblog.DefaultStyles() - // Attempt to match the colors used by qml/quickshell logs - styles.Levels[cblog.FatalLevel] = lipgloss.NewStyle(). - SetString(" FATAL"). - Foreground(lipgloss.Color("1")) - styles.Levels[cblog.ErrorLevel] = lipgloss.NewStyle(). - SetString(" ERROR"). - Foreground(lipgloss.Color("9")) - styles.Levels[cblog.WarnLevel] = lipgloss.NewStyle(). - SetString(" WARN"). - Foreground(lipgloss.Color("3")) - styles.Levels[cblog.InfoLevel] = lipgloss.NewStyle(). - SetString(" INFO"). - Foreground(lipgloss.Color("2")) - styles.Levels[cblog.DebugLevel] = lipgloss.NewStyle(). - SetString(" DEBUG"). - Foreground(lipgloss.Color("4")) +func ApplyEnvOverrides() { danklog.ApplyEnvOverrides() } - base := cblog.New(logStderr) - base.SetStyles(styles) - base.SetReportTimestamp(false) - - level := cblog.InfoLevel - if envLevel := os.Getenv("DMS_LOG_LEVEL"); envLevel != "" { - level = parseLogLevel(envLevel) - } - base.SetLevel(level) - base.SetPrefix(" go") - - logger = &Logger{base} - - if path := os.Getenv("DMS_LOG_FILE"); path != "" { - logMu.Lock() - _ = setLogFile(logger, path) - logMu.Unlock() - } - }) - return logger -} - -// SetLevel updates the active log level. Accepts the same strings as -// DMS_LOG_LEVEL. Unknown values default to info. -func SetLevel(level string) { - GetLogger().SetLevel(parseLogLevel(level)) -} - -// SetLogFile makes the logger append to path in addition to stderr. Passing an -// empty string detaches the file sink. Atomic per-line writes (≤PIPE_BUF) on -// O_APPEND keep concurrent Go and QML writers from corrupting each other. -// -// Color handling: charmbracelet/log auto-detects color support from its -// io.Writer, and io.MultiWriter doesn't pass that through, so we force the ANSI -// profile when stderr is a TTY and route the file through ansiStripWriter so -// the file stays plain while stderr keeps its colors. -func SetLogFile(path string) error { - l := GetLogger() - logMu.Lock() - defer logMu.Unlock() - return setLogFile(l, path) -} - -func setLogFile(l *Logger, path string) error { - if logFile != nil { - logFile.Close() - logFile = nil - } - - if path == "" { - l.SetOutput(logStderr) - applyColorProfile(l, logStderr) - return nil - } - - f, err := os.OpenFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o644) - if err != nil { - return err - } - logFile = f - out := io.MultiWriter(logStderr, &ansiStripWriter{w: f}) - l.SetOutput(out) - applyColorProfile(l, logStderr) - return nil -} - -// applyColorProfile forces the renderer's color profile to match what stderr -// would produce on its own, undoing the auto-downgrade triggered by wrapping -// stderr in a non-TTY writer (e.g. io.MultiWriter). -func applyColorProfile(l *Logger, stderr io.Writer) { - f, ok := stderr.(*os.File) - if !ok { - l.SetColorProfile(termenv.Ascii) - return - } - if isatty.IsTerminal(f.Fd()) { - l.SetColorProfile(termenv.ANSI) - return - } - l.SetColorProfile(termenv.Ascii) -} - -// ApplyEnvOverrides re-reads DMS_LOG_LEVEL and DMS_LOG_FILE and reconfigures -// the singleton. Safe to call after CLI flags have rewritten the environment. -func ApplyEnvOverrides() { - GetLogger() - if level := os.Getenv("DMS_LOG_LEVEL"); level != "" { - SetLevel(level) - } - if path := os.Getenv("DMS_LOG_FILE"); path != "" { - if err := SetLogFile(path); err != nil { - Warnf("Failed to open log file %q: %v", path, err) - } - } -} - -// * Convenience wrappers - -func Debug(msg any, keyvals ...any) { GetLogger().Debug(msg, keyvals...) } -func Debugf(format string, v ...any) { GetLogger().Debugf(format, v...) } -func Info(msg any, keyvals ...any) { GetLogger().Info(msg, keyvals...) } -func Infof(format string, v ...any) { GetLogger().Infof(format, v...) } -func Warn(msg any, keyvals ...any) { GetLogger().Warn(msg, keyvals...) } -func Warnf(format string, v ...any) { GetLogger().Warnf(format, v...) } -func Error(msg any, keyvals ...any) { GetLogger().Error(msg, keyvals...) } -func Errorf(format string, v ...any) { GetLogger().Errorf(format, v...) } -func Fatal(msg any, keyvals ...any) { GetLogger().Fatal(msg, keyvals...) } -func Fatalf(format string, v ...any) { GetLogger().Fatalf(format, v...) } +func Debug(msg any, keyvals ...any) { danklog.Debug(msg, keyvals...) } +func Debugf(format string, v ...any) { danklog.Debugf(format, v...) } +func Info(msg any, keyvals ...any) { danklog.Info(msg, keyvals...) } +func Infof(format string, v ...any) { danklog.Infof(format, v...) } +func Warn(msg any, keyvals ...any) { danklog.Warn(msg, keyvals...) } +func Warnf(format string, v ...any) { danklog.Warnf(format, v...) } +func Error(msg any, keyvals ...any) { danklog.Error(msg, keyvals...) } +func Errorf(format string, v ...any) { danklog.Errorf(format, v...) } +func Fatal(msg any, keyvals ...any) { danklog.Fatal(msg, keyvals...) } +func Fatalf(format string, v ...any) { danklog.Fatalf(format, v...) } diff --git a/core/internal/proto/wlr_output_management/output_management.go b/core/internal/proto/wlr_output_management/output_management.go index 23f934fc2..001c91da9 100644 --- a/core/internal/proto/wlr_output_management/output_management.go +++ b/core/internal/proto/wlr_output_management/output_management.go @@ -34,7 +34,7 @@ import ( "unsafe" "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) func registerServerProxy(ctx *client.Context, proxy client.Proxy, serverID uint32) { diff --git a/core/internal/server/apppicker/handlers.go b/core/internal/server/apppicker/handlers.go index 6fa416329..f824f80ab 100644 --- a/core/internal/server/apppicker/handlers.go +++ b/core/internal/server/apppicker/handlers.go @@ -1,14 +1,12 @@ package apppicker import ( - "net" - "github.com/AvengeMedia/DankMaterialShell/core/internal/desktop" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { switch req.Method { case "apppicker.open", "browser.open": handleOpen(conn, req, manager) @@ -17,7 +15,7 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleOpen(conn net.Conn, req models.Request, manager *Manager) { +func handleOpen(conn *models.Conn, req models.Request, manager *Manager) { log.Infof("AppPicker: Received %s request with params: %+v", req.Method, req.Params) target, ok := models.Get[string](req, "target") diff --git a/core/internal/server/apppicker/manager.go b/core/internal/server/apppicker/manager.go index bde0846a0..c3a158afe 100644 --- a/core/internal/server/apppicker/manager.go +++ b/core/internal/server/apppicker/manager.go @@ -3,7 +3,7 @@ package apppicker import ( "sync" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) type Manager struct { diff --git a/core/internal/server/bluez/handlers.go b/core/internal/server/bluez/handlers.go index be5a1c431..96f71b17d 100644 --- a/core/internal/server/bluez/handlers.go +++ b/core/internal/server/bluez/handlers.go @@ -1,12 +1,10 @@ package bluez import ( - "encoding/json" "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) type BluetoothEvent struct { @@ -14,7 +12,7 @@ type BluetoothEvent struct { Data BluetoothState `json:"data"` } -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { switch req.Method { case "bluetooth.getState": handleGetState(conn, req, manager) @@ -47,11 +45,11 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleGetState(conn net.Conn, req models.Request, manager *Manager) { +func handleGetState(conn *models.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, manager.GetState()) } -func handleStartDiscovery(conn net.Conn, req models.Request, manager *Manager) { +func handleStartDiscovery(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.StartDiscovery(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -59,7 +57,7 @@ func handleStartDiscovery(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "discovery started"}) } -func handleStopDiscovery(conn net.Conn, req models.Request, manager *Manager) { +func handleStopDiscovery(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.StopDiscovery(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -67,7 +65,7 @@ func handleStopDiscovery(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "discovery stopped"}) } -func handleSetPowered(conn net.Conn, req models.Request, manager *Manager) { +func handleSetPowered(conn *models.Conn, req models.Request, manager *Manager) { powered, err := params.Bool(req.Params, "powered") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -82,7 +80,7 @@ func handleSetPowered(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "powered state updated"}) } -func handlePairDevice(conn net.Conn, req models.Request, manager *Manager) { +func handlePairDevice(conn *models.Conn, req models.Request, manager *Manager) { devicePath, err := params.String(req.Params, "device") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -97,7 +95,7 @@ func handlePairDevice(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "pairing initiated"}) } -func handleConnectDevice(conn net.Conn, req models.Request, manager *Manager) { +func handleConnectDevice(conn *models.Conn, req models.Request, manager *Manager) { devicePath, err := params.String(req.Params, "device") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -112,7 +110,7 @@ func handleConnectDevice(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"}) } -func handleDisconnectDevice(conn net.Conn, req models.Request, manager *Manager) { +func handleDisconnectDevice(conn *models.Conn, req models.Request, manager *Manager) { devicePath, err := params.String(req.Params, "device") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -127,7 +125,7 @@ func handleDisconnectDevice(conn net.Conn, req models.Request, manager *Manager) models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "disconnected"}) } -func handleRemoveDevice(conn net.Conn, req models.Request, manager *Manager) { +func handleRemoveDevice(conn *models.Conn, req models.Request, manager *Manager) { devicePath, err := params.String(req.Params, "device") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -142,7 +140,7 @@ func handleRemoveDevice(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "device removed"}) } -func handleTrustDevice(conn net.Conn, req models.Request, manager *Manager) { +func handleTrustDevice(conn *models.Conn, req models.Request, manager *Manager) { devicePath, err := params.String(req.Params, "device") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -157,7 +155,7 @@ func handleTrustDevice(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "device trusted"}) } -func handleUntrustDevice(conn net.Conn, req models.Request, manager *Manager) { +func handleUntrustDevice(conn *models.Conn, req models.Request, manager *Manager) { devicePath, err := params.String(req.Params, "device") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -172,7 +170,7 @@ func handleUntrustDevice(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "device untrusted"}) } -func handlePairingSubmit(conn net.Conn, req models.Request, manager *Manager) { +func handlePairingSubmit(conn *models.Conn, req models.Request, manager *Manager) { token, err := params.String(req.Params, "token") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -190,7 +188,7 @@ func handlePairingSubmit(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "pairing response submitted"}) } -func handlePairingCancel(conn net.Conn, req models.Request, manager *Manager) { +func handlePairingCancel(conn *models.Conn, req models.Request, manager *Manager) { token, err := params.String(req.Params, "token") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -205,7 +203,7 @@ func handlePairingCancel(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "pairing cancelled"}) } -func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { +func handleSubscribe(conn *models.Conn, req models.Request, manager *Manager) { clientID := fmt.Sprintf("client-%p", conn) stateChan := manager.Subscribe(clientID) defer manager.Unsubscribe(clientID) @@ -216,7 +214,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { Data: initialState, } - if err := json.NewEncoder(conn).Encode(models.Response[BluetoothEvent]{ + if err := conn.WriteResponse(models.Response[BluetoothEvent]{ ID: req.ID, Result: &event, }); err != nil { @@ -228,7 +226,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { Type: "state_changed", Data: state, } - if err := json.NewEncoder(conn).Encode(models.Response[BluetoothEvent]{ + if err := conn.WriteResponse(models.Response[BluetoothEvent]{ Result: &event, }); err != nil { return diff --git a/core/internal/server/bluez/manager.go b/core/internal/server/bluez/manager.go index 4408d2541..542895c5b 100644 --- a/core/internal/server/bluez/manager.go +++ b/core/internal/server/bluez/manager.go @@ -7,7 +7,7 @@ import ( "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil" + "github.com/AvengeMedia/dankgo/dbusutil" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/server/bluez/subscription_broker.go b/core/internal/server/bluez/subscription_broker.go index ea04a190d..e0d5873d9 100644 --- a/core/internal/server/bluez/subscription_broker.go +++ b/core/internal/server/bluez/subscription_broker.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) type SubscriptionBroker struct { diff --git a/core/internal/server/bluez/types.go b/core/internal/server/bluez/types.go index 663accaee..54d402fb4 100644 --- a/core/internal/server/bluez/types.go +++ b/core/internal/server/bluez/types.go @@ -3,7 +3,7 @@ package bluez import ( "sync" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/server/brightness/handlers.go b/core/internal/server/brightness/handlers.go index c71163ee8..23fbcc6a1 100644 --- a/core/internal/server/brightness/handlers.go +++ b/core/internal/server/brightness/handlers.go @@ -1,15 +1,13 @@ package brightness import ( - "encoding/json" "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) -func HandleRequest(conn net.Conn, req models.Request, m *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, m *Manager) { switch req.Method { case "brightness.getState": handleGetState(conn, req, m) @@ -28,11 +26,11 @@ func HandleRequest(conn net.Conn, req models.Request, m *Manager) { } } -func handleGetState(conn net.Conn, req models.Request, m *Manager) { +func handleGetState(conn *models.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, m.GetState()) } -func handleSetBrightness(conn net.Conn, req models.Request, m *Manager) { +func handleSetBrightness(conn *models.Conn, req models.Request, m *Manager) { device, err := params.String(req.Params, "device") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -56,7 +54,7 @@ func handleSetBrightness(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, m.GetState()) } -func handleIncrement(conn net.Conn, req models.Request, m *Manager) { +func handleIncrement(conn *models.Conn, req models.Request, m *Manager) { device, err := params.String(req.Params, "device") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -75,7 +73,7 @@ func handleIncrement(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, m.GetState()) } -func handleDecrement(conn net.Conn, req models.Request, m *Manager) { +func handleDecrement(conn *models.Conn, req models.Request, m *Manager) { device, err := params.String(req.Params, "device") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -94,19 +92,19 @@ func handleDecrement(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, m.GetState()) } -func handleRescan(conn net.Conn, req models.Request, m *Manager) { +func handleRescan(conn *models.Conn, req models.Request, m *Manager) { m.Rescan() models.Respond(conn, req.ID, m.GetState()) } -func handleSubscribe(conn net.Conn, req models.Request, m *Manager) { +func handleSubscribe(conn *models.Conn, req models.Request, m *Manager) { clientID := fmt.Sprintf("brightness-%d", req.ID) ch := m.Subscribe(clientID) defer m.Unsubscribe(clientID) initialState := m.GetState() - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ ID: req.ID, Result: &initialState, }); err != nil { @@ -114,7 +112,7 @@ func handleSubscribe(conn net.Conn, req models.Request, m *Manager) { } for state := range ch { - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ ID: req.ID, Result: &state, }); err != nil { diff --git a/core/internal/server/brightness/types.go b/core/internal/server/brightness/types.go index 81e00cfe1..0eb2c8307 100644 --- a/core/internal/server/brightness/types.go +++ b/core/internal/server/brightness/types.go @@ -4,7 +4,7 @@ import ( "sync" "time" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) type DeviceClass string diff --git a/core/internal/server/browser/handlers.go b/core/internal/server/browser/handlers.go index 0b4159ae3..9eab4181e 100644 --- a/core/internal/server/browser/handlers.go +++ b/core/internal/server/browser/handlers.go @@ -1,12 +1,10 @@ package browser import ( - "net" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { switch req.Method { case "browser.open": url, ok := models.Get[string](req, "url") diff --git a/core/internal/server/browser/manager.go b/core/internal/server/browser/manager.go index 5306759b2..5aecfbade 100644 --- a/core/internal/server/browser/manager.go +++ b/core/internal/server/browser/manager.go @@ -3,7 +3,7 @@ package browser import ( "sync" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) type Manager struct { diff --git a/core/internal/server/clipboard/handlers.go b/core/internal/server/clipboard/handlers.go index ac993094e..cac17aa16 100644 --- a/core/internal/server/clipboard/handlers.go +++ b/core/internal/server/clipboard/handlers.go @@ -1,17 +1,15 @@ package clipboard import ( - "encoding/json" "errors" "fmt" - "net" clipboardstore "github.com/AvengeMedia/DankMaterialShell/core/internal/clipboard" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) -func HandleRequest(conn net.Conn, req models.Request, m *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, m *Manager) { switch req.Method { case "clipboard.getState": handleGetState(conn, req, m) @@ -58,11 +56,11 @@ func HandleRequest(conn net.Conn, req models.Request, m *Manager) { } } -func handleGetState(conn net.Conn, req models.Request, m *Manager) { +func handleGetState(conn *models.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, m.GetState()) } -func handleGetHistory(conn net.Conn, req models.Request, m *Manager) { +func handleGetHistory(conn *models.Conn, req models.Request, m *Manager) { history := m.GetHistory() for i := range history { history[i].Data = nil @@ -70,7 +68,7 @@ func handleGetHistory(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, history) } -func handleGetEntry(conn net.Conn, req models.Request, m *Manager) { +func handleGetEntry(conn *models.Conn, req models.Request, m *Manager) { id, err := params.Int(req.Params, "id") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -90,7 +88,7 @@ func handleGetEntry(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, entry) } -func handleDeleteEntry(conn net.Conn, req models.Request, m *Manager) { +func handleDeleteEntry(conn *models.Conn, req models.Request, m *Manager) { id, err := params.Int(req.Params, "id") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -105,12 +103,12 @@ func handleDeleteEntry(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "entry deleted"}) } -func handleClearHistory(conn net.Conn, req models.Request, m *Manager) { +func handleClearHistory(conn *models.Conn, req models.Request, m *Manager) { m.ClearHistory() models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "history cleared"}) } -func handleCopy(conn net.Conn, req models.Request, m *Manager) { +func handleCopy(conn *models.Conn, req models.Request, m *Manager) { text, err := params.String(req.Params, "text") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -125,7 +123,7 @@ func handleCopy(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "copied to clipboard"}) } -func handleCopyEntry(conn net.Conn, req models.Request, m *Manager) { +func handleCopyEntry(conn *models.Conn, req models.Request, m *Manager) { id, err := params.Int(req.Params, "id") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -173,7 +171,7 @@ func handleCopyEntry(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "copied to clipboard"}) } -func handlePaste(conn net.Conn, req models.Request, m *Manager) { +func handlePaste(conn *models.Conn, req models.Request, m *Manager) { text, err := m.PasteText() if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -183,7 +181,7 @@ func handlePaste(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, map[string]string{"text": text}) } -func handleSendPaste(conn net.Conn, req models.Request) { +func handleSendPaste(conn *models.Conn, req models.Request) { shift, _ := models.Get[bool](req, "shift") if err := clipboardstore.SendPasteKeystroke(shift); err != nil { @@ -194,14 +192,14 @@ func handleSendPaste(conn net.Conn, req models.Request) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "paste sent"}) } -func handleSubscribe(conn net.Conn, req models.Request, m *Manager) { +func handleSubscribe(conn *models.Conn, req models.Request, m *Manager) { clientID := fmt.Sprintf("clipboard-%d", req.ID) ch := m.Subscribe(clientID) defer m.Unsubscribe(clientID) initialState := m.GetState() - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ ID: req.ID, Result: &initialState, }); err != nil { @@ -209,7 +207,7 @@ func handleSubscribe(conn net.Conn, req models.Request, m *Manager) { } for state := range ch { - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ ID: req.ID, Result: &state, }); err != nil { @@ -218,7 +216,7 @@ func handleSubscribe(conn net.Conn, req models.Request, m *Manager) { } } -func handleSearch(conn net.Conn, req models.Request, m *Manager) { +func handleSearch(conn *models.Conn, req models.Request, m *Manager) { p := SearchParams{ Query: params.StringOpt(req.Params, "query", ""), MimeType: params.StringOpt(req.Params, "mimeType", ""), @@ -241,11 +239,11 @@ func handleSearch(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, m.Search(p)) } -func handleGetConfig(conn net.Conn, req models.Request, m *Manager) { +func handleGetConfig(conn *models.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, m.GetConfig()) } -func handleSetConfig(conn net.Conn, req models.Request, m *Manager) { +func handleSetConfig(conn *models.Conn, req models.Request, m *Manager) { cfg := m.GetConfig() if v, ok := models.Get[float64](req, "maxHistory"); ok { @@ -275,7 +273,7 @@ func handleSetConfig(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "config updated"}) } -func handleStore(conn net.Conn, req models.Request, m *Manager) { +func handleStore(conn *models.Conn, req models.Request, m *Manager) { data, err := params.String(req.Params, "data") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -292,7 +290,7 @@ func handleStore(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "stored"}) } -func handlePinEntry(conn net.Conn, req models.Request, m *Manager) { +func handlePinEntry(conn *models.Conn, req models.Request, m *Manager) { id, err := params.Int(req.Params, "id") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -307,7 +305,7 @@ func handlePinEntry(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "entry pinned"}) } -func handleUnpinEntry(conn net.Conn, req models.Request, m *Manager) { +func handleUnpinEntry(conn *models.Conn, req models.Request, m *Manager) { id, err := params.Int(req.Params, "id") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -322,17 +320,17 @@ func handleUnpinEntry(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "entry unpinned"}) } -func handleGetPinnedEntries(conn net.Conn, req models.Request, m *Manager) { +func handleGetPinnedEntries(conn *models.Conn, req models.Request, m *Manager) { pinned := m.GetPinnedEntries() models.Respond(conn, req.ID, pinned) } -func handleGetPinnedCount(conn net.Conn, req models.Request, m *Manager) { +func handleGetPinnedCount(conn *models.Conn, req models.Request, m *Manager) { count := m.GetPinnedCount() models.Respond(conn, req.ID, map[string]int{"count": count}) } -func handleCopyFile(conn net.Conn, req models.Request, m *Manager) { +func handleCopyFile(conn *models.Conn, req models.Request, m *Manager) { filePath, err := params.String(req.Params, "filePath") if err != nil { models.RespondError(conn, req.ID, err.Error()) diff --git a/core/internal/server/clipboard/manager_test.go b/core/internal/server/clipboard/manager_test.go index a0a3c703f..9a9f64843 100644 --- a/core/internal/server/clipboard/manager_test.go +++ b/core/internal/server/clipboard/manager_test.go @@ -311,14 +311,15 @@ func TestHandleGetEntry_ReturnsExistingEntry(t *testing.T) { history := m.GetHistory() require.Len(t, history, 1) - conn := newClipboardTestConn() + mc := newClipboardTestConn() + conn := models.NewConn(mc) handleGetEntry(conn, models.Request{ ID: 1, Params: map[string]any{"id": float64(history[0].ID)}, }, m) var resp models.Response[Entry] - require.NoError(t, json.NewDecoder(conn.writeBuf).Decode(&resp)) + require.NoError(t, json.NewDecoder(mc.writeBuf).Decode(&resp)) assert.Empty(t, resp.Error) require.NotNil(t, resp.Result) assert.Equal(t, history[0].ID, resp.Result.ID) @@ -327,7 +328,8 @@ func TestHandleGetEntry_ReturnsExistingEntry(t *testing.T) { func TestHandleGetEntry_MissingIDReturnsNullResult(t *testing.T) { m := newTestManagerWithDB(t) - conn := newClipboardTestConn() + mc := newClipboardTestConn() + conn := models.NewConn(mc) handleGetEntry(conn, models.Request{ ID: 1, @@ -335,7 +337,7 @@ func TestHandleGetEntry_MissingIDReturnsNullResult(t *testing.T) { }, m) var resp models.Response[any] - require.NoError(t, json.NewDecoder(conn.writeBuf).Decode(&resp)) + require.NoError(t, json.NewDecoder(mc.writeBuf).Decode(&resp)) assert.Empty(t, resp.Error) assert.Nil(t, resp.Result) } diff --git a/core/internal/server/cups/handlers.go b/core/internal/server/cups/handlers.go index 57b45d55f..46e446307 100644 --- a/core/internal/server/cups/handlers.go +++ b/core/internal/server/cups/handlers.go @@ -1,12 +1,10 @@ package cups import ( - "encoding/json" "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) type CUPSEvent struct { @@ -20,7 +18,7 @@ type TestPageResult struct { Message string `json:"message"` } -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { switch req.Method { case "cups.subscribe": handleSubscribe(conn, req, manager) @@ -77,7 +75,7 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleGetPrinters(conn net.Conn, req models.Request, manager *Manager) { +func handleGetPrinters(conn *models.Conn, req models.Request, manager *Manager) { printers, err := manager.GetPrinters() if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -86,7 +84,7 @@ func handleGetPrinters(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, printers) } -func handleGetJobs(conn net.Conn, req models.Request, manager *Manager) { +func handleGetJobs(conn *models.Conn, req models.Request, manager *Manager) { printerName, err := params.String(req.Params, "printerName") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -101,7 +99,7 @@ func handleGetJobs(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, jobs) } -func handlePausePrinter(conn net.Conn, req models.Request, manager *Manager) { +func handlePausePrinter(conn *models.Conn, req models.Request, manager *Manager) { printerName, err := params.String(req.Params, "printerName") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -115,7 +113,7 @@ func handlePausePrinter(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "paused"}) } -func handleResumePrinter(conn net.Conn, req models.Request, manager *Manager) { +func handleResumePrinter(conn *models.Conn, req models.Request, manager *Manager) { printerName, err := params.String(req.Params, "printerName") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -129,7 +127,7 @@ func handleResumePrinter(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "resumed"}) } -func handleCancelJob(conn net.Conn, req models.Request, manager *Manager) { +func handleCancelJob(conn *models.Conn, req models.Request, manager *Manager) { jobID, err := params.Int(req.Params, "jobID") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -143,7 +141,7 @@ func handleCancelJob(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job canceled"}) } -func handlePurgeJobs(conn net.Conn, req models.Request, manager *Manager) { +func handlePurgeJobs(conn *models.Conn, req models.Request, manager *Manager) { printerName, err := params.String(req.Params, "printerName") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -157,7 +155,7 @@ func handlePurgeJobs(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "jobs canceled"}) } -func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { +func handleSubscribe(conn *models.Conn, req models.Request, manager *Manager) { clientID := fmt.Sprintf("client-%p", conn) stateChan := manager.Subscribe(clientID) defer manager.Unsubscribe(clientID) @@ -168,7 +166,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { Data: initialState, } - if err := json.NewEncoder(conn).Encode(models.Response[CUPSEvent]{ + if err := conn.WriteResponse(models.Response[CUPSEvent]{ ID: req.ID, Result: &event, }); err != nil { @@ -180,7 +178,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { Type: "state_changed", Data: state, } - if err := json.NewEncoder(conn).Encode(models.Response[CUPSEvent]{ + if err := conn.WriteResponse(models.Response[CUPSEvent]{ Result: &event, }); err != nil { return @@ -188,7 +186,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { } } -func handleGetDevices(conn net.Conn, req models.Request, manager *Manager) { +func handleGetDevices(conn *models.Conn, req models.Request, manager *Manager) { devices, err := manager.GetDevices() if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -197,7 +195,7 @@ func handleGetDevices(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, devices) } -func handleGetPPDs(conn net.Conn, req models.Request, manager *Manager) { +func handleGetPPDs(conn *models.Conn, req models.Request, manager *Manager) { ppds, err := manager.GetPPDs() if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -206,7 +204,7 @@ func handleGetPPDs(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, ppds) } -func handleGetClasses(conn net.Conn, req models.Request, manager *Manager) { +func handleGetClasses(conn *models.Conn, req models.Request, manager *Manager) { classes, err := manager.GetClasses() if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -215,7 +213,7 @@ func handleGetClasses(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, classes) } -func handleCreatePrinter(conn net.Conn, req models.Request, manager *Manager) { +func handleCreatePrinter(conn *models.Conn, req models.Request, manager *Manager) { name, err := params.StringNonEmpty(req.Params, "name") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -246,7 +244,7 @@ func handleCreatePrinter(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer created"}) } -func handleDeletePrinter(conn net.Conn, req models.Request, manager *Manager) { +func handleDeletePrinter(conn *models.Conn, req models.Request, manager *Manager) { printerName, err := params.StringNonEmpty(req.Params, "printerName") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -260,7 +258,7 @@ func handleDeletePrinter(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer deleted"}) } -func handleAcceptJobs(conn net.Conn, req models.Request, manager *Manager) { +func handleAcceptJobs(conn *models.Conn, req models.Request, manager *Manager) { printerName, err := params.StringNonEmpty(req.Params, "printerName") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -274,7 +272,7 @@ func handleAcceptJobs(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "accepting jobs"}) } -func handleRejectJobs(conn net.Conn, req models.Request, manager *Manager) { +func handleRejectJobs(conn *models.Conn, req models.Request, manager *Manager) { printerName, err := params.StringNonEmpty(req.Params, "printerName") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -288,7 +286,7 @@ func handleRejectJobs(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "rejecting jobs"}) } -func handleSetPrinterShared(conn net.Conn, req models.Request, manager *Manager) { +func handleSetPrinterShared(conn *models.Conn, req models.Request, manager *Manager) { printerName, err := params.StringNonEmpty(req.Params, "printerName") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -308,7 +306,7 @@ func handleSetPrinterShared(conn net.Conn, req models.Request, manager *Manager) models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "sharing updated"}) } -func handleSetPrinterLocation(conn net.Conn, req models.Request, manager *Manager) { +func handleSetPrinterLocation(conn *models.Conn, req models.Request, manager *Manager) { printerName, err := params.StringNonEmpty(req.Params, "printerName") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -328,7 +326,7 @@ func handleSetPrinterLocation(conn net.Conn, req models.Request, manager *Manage models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "location updated"}) } -func handleSetPrinterInfo(conn net.Conn, req models.Request, manager *Manager) { +func handleSetPrinterInfo(conn *models.Conn, req models.Request, manager *Manager) { printerName, err := params.StringNonEmpty(req.Params, "printerName") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -348,7 +346,7 @@ func handleSetPrinterInfo(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "info updated"}) } -func handleMoveJob(conn net.Conn, req models.Request, manager *Manager) { +func handleMoveJob(conn *models.Conn, req models.Request, manager *Manager) { jobID, err := params.Int(req.Params, "jobID") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -368,7 +366,7 @@ func handleMoveJob(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job moved"}) } -func handlePrintTestPage(conn net.Conn, req models.Request, manager *Manager) { +func handlePrintTestPage(conn *models.Conn, req models.Request, manager *Manager) { printerName, err := params.StringNonEmpty(req.Params, "printerName") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -383,7 +381,7 @@ func handlePrintTestPage(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, TestPageResult{Success: true, JobID: jobID, Message: "test page queued"}) } -func handleAddPrinterToClass(conn net.Conn, req models.Request, manager *Manager) { +func handleAddPrinterToClass(conn *models.Conn, req models.Request, manager *Manager) { className, err := params.StringNonEmpty(req.Params, "className") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -403,7 +401,7 @@ func handleAddPrinterToClass(conn net.Conn, req models.Request, manager *Manager models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer added to class"}) } -func handleRemovePrinterFromClass(conn net.Conn, req models.Request, manager *Manager) { +func handleRemovePrinterFromClass(conn *models.Conn, req models.Request, manager *Manager) { className, err := params.StringNonEmpty(req.Params, "className") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -423,7 +421,7 @@ func handleRemovePrinterFromClass(conn net.Conn, req models.Request, manager *Ma models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "printer removed from class"}) } -func handleDeleteClass(conn net.Conn, req models.Request, manager *Manager) { +func handleDeleteClass(conn *models.Conn, req models.Request, manager *Manager) { className, err := params.StringNonEmpty(req.Params, "className") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -437,7 +435,7 @@ func handleDeleteClass(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "class deleted"}) } -func handleRestartJob(conn net.Conn, req models.Request, manager *Manager) { +func handleRestartJob(conn *models.Conn, req models.Request, manager *Manager) { jobID, err := params.Int(req.Params, "jobID") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -451,7 +449,7 @@ func handleRestartJob(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job restarted"}) } -func handleHoldJob(conn net.Conn, req models.Request, manager *Manager) { +func handleHoldJob(conn *models.Conn, req models.Request, manager *Manager) { jobID, err := params.Int(req.Params, "jobID") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -467,7 +465,7 @@ func handleHoldJob(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "job held"}) } -func handleTestConnection(conn net.Conn, req models.Request, manager *Manager) { +func handleTestConnection(conn *models.Conn, req models.Request, manager *Manager) { host, err := params.StringNonEmpty(req.Params, "host") if err != nil { models.RespondError(conn, req.ID, err.Error()) diff --git a/core/internal/server/cups/handlers_test.go b/core/internal/server/cups/handlers_test.go index b73931e3e..790d78476 100644 --- a/core/internal/server/cups/handlers_test.go +++ b/core/internal/server/cups/handlers_test.go @@ -41,7 +41,7 @@ func TestHandleGetPrinters(t *testing.T) { } buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -66,7 +66,7 @@ func TestHandleGetPrinters_Error(t *testing.T) { } buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -98,7 +98,7 @@ func TestHandleGetJobs(t *testing.T) { } buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -125,7 +125,7 @@ func TestHandleGetJobs_MissingParam(t *testing.T) { } buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -150,7 +150,7 @@ func TestHandlePausePrinter(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -177,7 +177,7 @@ func TestHandleResumePrinter(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -204,7 +204,7 @@ func TestHandleCancelJob(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -231,7 +231,7 @@ func TestHandlePurgeJobs(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -258,7 +258,7 @@ func TestHandleRequest_UnknownMethod(t *testing.T) { } buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -285,7 +285,7 @@ func TestHandleGetDevices(t *testing.T) { m := &Manager{client: mockClient} buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ID: 1, Method: "cups.getDevices"} handleGetDevices(conn, req, m) @@ -307,7 +307,7 @@ func TestHandleGetPPDs(t *testing.T) { m := &Manager{client: mockClient} buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ID: 1, Method: "cups.getPPDs"} handleGetPPDs(conn, req, m) @@ -330,7 +330,7 @@ func TestHandleGetClasses(t *testing.T) { m := &Manager{client: mockClient} buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ID: 1, Method: "cups.getClasses"} handleGetClasses(conn, req, m) @@ -351,7 +351,7 @@ func TestHandleCreatePrinter(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -375,7 +375,7 @@ func TestHandleCreatePrinter_MissingParams(t *testing.T) { mockClient := mocks_cups.NewMockCUPSClientInterface(t) m := &Manager{client: mockClient} buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ID: 1, Method: "cups.createPrinter", Params: map[string]any{}} handleCreatePrinter(conn, req, m) @@ -394,7 +394,7 @@ func TestHandleDeletePrinter(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -417,7 +417,7 @@ func TestHandleAcceptJobs(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -440,7 +440,7 @@ func TestHandleRejectJobs(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -463,7 +463,7 @@ func TestHandleSetPrinterShared(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -486,7 +486,7 @@ func TestHandleSetPrinterLocation(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -509,7 +509,7 @@ func TestHandleSetPrinterInfo(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -532,7 +532,7 @@ func TestHandleMoveJob(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -555,7 +555,7 @@ func TestHandlePrintTestPage(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -579,7 +579,7 @@ func TestHandleAddPrinterToClass(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -602,7 +602,7 @@ func TestHandleRemovePrinterFromClass(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -625,7 +625,7 @@ func TestHandleDeleteClass(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -648,7 +648,7 @@ func TestHandleRestartJob(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -671,7 +671,7 @@ func TestHandleHoldJob(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -694,7 +694,7 @@ func TestHandleHoldJob_WithHoldUntil(t *testing.T) { m := NewTestManager(mockClient, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, diff --git a/core/internal/server/cups/test_connection_test.go b/core/internal/server/cups/test_connection_test.go index b8350281b..4e1408931 100644 --- a/core/internal/server/cups/test_connection_test.go +++ b/core/internal/server/cups/test_connection_test.go @@ -302,7 +302,7 @@ func TestHandleTestConnection_Success(t *testing.T) { } buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -325,7 +325,7 @@ func TestHandleTestConnection_Success(t *testing.T) { func TestHandleTestConnection_MissingHost(t *testing.T) { m := NewTestManager(nil, nil) buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -351,7 +351,7 @@ func TestHandleTestConnection_CustomPortAndProtocol(t *testing.T) { } buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, @@ -379,7 +379,7 @@ func TestHandleRequest_TestConnection(t *testing.T) { } buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ ID: 1, diff --git a/core/internal/server/cups/types.go b/core/internal/server/cups/types.go index 52e9d2cda..52f0f5b9e 100644 --- a/core/internal/server/cups/types.go +++ b/core/internal/server/cups/types.go @@ -6,7 +6,7 @@ import ( "time" "github.com/AvengeMedia/DankMaterialShell/core/pkg/ipp" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) type CUPSState struct { diff --git a/core/internal/server/dbus/handlers.go b/core/internal/server/dbus/handlers.go index cab6ade12..68df1377b 100644 --- a/core/internal/server/dbus/handlers.go +++ b/core/internal/server/dbus/handlers.go @@ -2,10 +2,9 @@ package dbus import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) type objectParams struct { @@ -43,7 +42,7 @@ func extractObjectParams(p map[string]any, requirePath bool) (objectParams, erro return objectParams{bus: bus, dest: dest, path: path, iface: iface}, nil } -func HandleRequest(conn net.Conn, req models.Request, m *Manager, clientID string) { +func HandleRequest(conn *models.Conn, req models.Request, m *Manager, clientID string) { switch req.Method { case "dbus.call": handleCall(conn, req, m) @@ -66,7 +65,7 @@ func HandleRequest(conn net.Conn, req models.Request, m *Manager, clientID strin } } -func handleCall(conn net.Conn, req models.Request, m *Manager) { +func handleCall(conn *models.Conn, req models.Request, m *Manager) { op, err := extractObjectParams(req.Params, true) if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -95,7 +94,7 @@ func handleCall(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, result) } -func handleGetProperty(conn net.Conn, req models.Request, m *Manager) { +func handleGetProperty(conn *models.Conn, req models.Request, m *Manager) { op, err := extractObjectParams(req.Params, true) if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -117,7 +116,7 @@ func handleGetProperty(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, result) } -func handleSetProperty(conn net.Conn, req models.Request, m *Manager) { +func handleSetProperty(conn *models.Conn, req models.Request, m *Manager) { op, err := extractObjectParams(req.Params, true) if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -144,7 +143,7 @@ func handleSetProperty(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true}) } -func handleGetAllProperties(conn net.Conn, req models.Request, m *Manager) { +func handleGetAllProperties(conn *models.Conn, req models.Request, m *Manager) { op, err := extractObjectParams(req.Params, true) if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -160,7 +159,7 @@ func handleGetAllProperties(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, result) } -func handleIntrospect(conn net.Conn, req models.Request, m *Manager) { +func handleIntrospect(conn *models.Conn, req models.Request, m *Manager) { bus, err := params.String(req.Params, "bus") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -184,7 +183,7 @@ func handleIntrospect(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, result) } -func handleListNames(conn net.Conn, req models.Request, m *Manager) { +func handleListNames(conn *models.Conn, req models.Request, m *Manager) { bus, err := params.String(req.Params, "bus") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -200,7 +199,7 @@ func handleListNames(conn net.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, result) } -func handleSubscribe(conn net.Conn, req models.Request, m *Manager, clientID string) { +func handleSubscribe(conn *models.Conn, req models.Request, m *Manager, clientID string) { if id := params.StringOpt(req.Params, "clientId", ""); id != "" { clientID = id } @@ -225,7 +224,7 @@ func handleSubscribe(conn net.Conn, req models.Request, m *Manager, clientID str models.Respond(conn, req.ID, result) } -func handleUnsubscribe(conn net.Conn, req models.Request, m *Manager) { +func handleUnsubscribe(conn *models.Conn, req models.Request, m *Manager) { subID, err := params.String(req.Params, "subscriptionId") if err != nil { models.RespondError(conn, req.ID, err.Error()) diff --git a/core/internal/server/dbus/manager.go b/core/internal/server/dbus/manager.go index 86da6bda1..994789d9d 100644 --- a/core/internal/server/dbus/manager.go +++ b/core/internal/server/dbus/manager.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil" + "github.com/AvengeMedia/dankgo/dbusutil" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/server/dbus/types.go b/core/internal/server/dbus/types.go index 55c45bf4b..dcac8a239 100644 --- a/core/internal/server/dbus/types.go +++ b/core/internal/server/dbus/types.go @@ -1,7 +1,7 @@ package dbus import ( - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/server/evdev/handlers.go b/core/internal/server/evdev/handlers.go index 54a7bca23..0281704fa 100644 --- a/core/internal/server/evdev/handlers.go +++ b/core/internal/server/evdev/handlers.go @@ -1,12 +1,10 @@ package evdev import ( - "net" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) -func HandleRequest(conn net.Conn, req models.Request, m *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, m *Manager) { switch req.Method { case "evdev.getState": handleGetState(conn, req, m) @@ -15,6 +13,6 @@ func HandleRequest(conn net.Conn, req models.Request, m *Manager) { } } -func handleGetState(conn net.Conn, req models.Request, m *Manager) { +func handleGetState(conn *models.Conn, req models.Request, m *Manager) { models.Respond(conn, req.ID, m.GetState()) } diff --git a/core/internal/server/evdev/handlers_test.go b/core/internal/server/evdev/handlers_test.go index f9bd21db9..612777dd5 100644 --- a/core/internal/server/evdev/handlers_test.go +++ b/core/internal/server/evdev/handlers_test.go @@ -52,7 +52,8 @@ func TestHandleRequest(t *testing.T) { closeChan: make(chan struct{}), } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "evdev.getState", @@ -62,7 +63,7 @@ func TestHandleRequest(t *testing.T) { HandleRequest(conn, req, m) var resp models.Response[State] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -81,7 +82,8 @@ func TestHandleRequest(t *testing.T) { closeChan: make(chan struct{}), } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 456, Method: "evdev.unknownMethod", @@ -91,7 +93,7 @@ func TestHandleRequest(t *testing.T) { HandleRequest(conn, req, m) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 456, resp.ID) @@ -110,7 +112,8 @@ func TestHandleGetState(t *testing.T) { closeChan: make(chan struct{}), } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 789, Method: "evdev.getState", @@ -120,7 +123,7 @@ func TestHandleGetState(t *testing.T) { handleGetState(conn, req, m) var resp models.Response[State] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 789, resp.ID) diff --git a/core/internal/server/evdev/manager.go b/core/internal/server/evdev/manager.go index 324907237..af447bd22 100644 --- a/core/internal/server/evdev/manager.go +++ b/core/internal/server/evdev/manager.go @@ -9,7 +9,7 @@ import ( "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" "github.com/fsnotify/fsnotify" evdev "github.com/holoplot/go-evdev" ) diff --git a/core/internal/server/freedesktop/handlers.go b/core/internal/server/freedesktop/handlers.go index c3090fa22..cf322abed 100644 --- a/core/internal/server/freedesktop/handlers.go +++ b/core/internal/server/freedesktop/handlers.go @@ -2,13 +2,12 @@ package freedesktop import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { switch req.Method { case "freedesktop.getState": handleGetState(conn, req, manager) @@ -33,11 +32,11 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleGetState(conn net.Conn, req models.Request, manager *Manager) { +func handleGetState(conn *models.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, manager.GetState()) } -func handleSetIconFile(conn net.Conn, req models.Request, manager *Manager) { +func handleSetIconFile(conn *models.Conn, req models.Request, manager *Manager) { iconPath, err := params.String(req.Params, "path") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -52,7 +51,7 @@ func handleSetIconFile(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "icon file set"}) } -func handleSetRealName(conn net.Conn, req models.Request, manager *Manager) { +func handleSetRealName(conn *models.Conn, req models.Request, manager *Manager) { name, err := params.String(req.Params, "name") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -67,7 +66,7 @@ func handleSetRealName(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "real name set"}) } -func handleSetEmail(conn net.Conn, req models.Request, manager *Manager) { +func handleSetEmail(conn *models.Conn, req models.Request, manager *Manager) { email, err := params.String(req.Params, "email") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -82,7 +81,7 @@ func handleSetEmail(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "email set"}) } -func handleSetLanguage(conn net.Conn, req models.Request, manager *Manager) { +func handleSetLanguage(conn *models.Conn, req models.Request, manager *Manager) { language, err := params.String(req.Params, "language") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -97,7 +96,7 @@ func handleSetLanguage(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "language set"}) } -func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) { +func handleSetLocation(conn *models.Conn, req models.Request, manager *Manager) { location, err := params.String(req.Params, "location") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -112,7 +111,7 @@ func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "location set"}) } -func handleGetUserIconFile(conn net.Conn, req models.Request, manager *Manager) { +func handleGetUserIconFile(conn *models.Conn, req models.Request, manager *Manager) { username, err := params.String(req.Params, "username") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -128,7 +127,7 @@ func handleGetUserIconFile(conn net.Conn, req models.Request, manager *Manager) models.Respond(conn, req.ID, models.SuccessResult{Success: true, Value: iconFile}) } -func handleGetColorScheme(conn net.Conn, req models.Request, manager *Manager) { +func handleGetColorScheme(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.updateSettingsState(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -138,7 +137,7 @@ func handleGetColorScheme(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, map[string]uint32{"colorScheme": state.Settings.ColorScheme}) } -func handleSetIconTheme(conn net.Conn, req models.Request, manager *Manager) { +func handleSetIconTheme(conn *models.Conn, req models.Request, manager *Manager) { iconTheme, err := params.String(req.Params, "iconTheme") if err != nil { models.RespondError(conn, req.ID, err.Error()) diff --git a/core/internal/server/freedesktop/handlers_test.go b/core/internal/server/freedesktop/handlers_test.go index f2cb5a2ed..aa5bbd768 100644 --- a/core/internal/server/freedesktop/handlers_test.go +++ b/core/internal/server/freedesktop/handlers_test.go @@ -60,11 +60,12 @@ func mockGetAllAccountsProperties() *dbus.Call { } func TestRespondError_Freedesktop(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) models.RespondError(conn, 123, "test error") var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -73,12 +74,13 @@ func TestRespondError_Freedesktop(t *testing.T) { } func TestRespond_Freedesktop(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) result := models.SuccessResult{Success: true, Message: "test"} models.Respond(conn, 123, result) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -105,13 +107,14 @@ func TestHandleGetState(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "freedesktop.getState"} handleGetState(conn, req, manager) var resp models.Response[FreedeskState] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -130,7 +133,8 @@ func TestHandleSetIconFile(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.accounts.setIconFile", @@ -140,7 +144,7 @@ func TestHandleSetIconFile(t *testing.T) { handleSetIconFile(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -163,7 +167,8 @@ func TestHandleSetIconFile(t *testing.T) { accountsObj: mockAccountsObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.accounts.setIconFile", @@ -175,7 +180,7 @@ func TestHandleSetIconFile(t *testing.T) { handleSetIconFile(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -195,7 +200,8 @@ func TestHandleSetIconFile(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.accounts.setIconFile", @@ -207,7 +213,7 @@ func TestHandleSetIconFile(t *testing.T) { handleSetIconFile(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -222,7 +228,8 @@ func TestHandleSetRealName(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.accounts.setRealName", @@ -232,7 +239,7 @@ func TestHandleSetRealName(t *testing.T) { handleSetRealName(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -255,7 +262,8 @@ func TestHandleSetRealName(t *testing.T) { accountsObj: mockAccountsObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.accounts.setRealName", @@ -267,7 +275,7 @@ func TestHandleSetRealName(t *testing.T) { handleSetRealName(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -285,7 +293,8 @@ func TestHandleSetEmail(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.accounts.setEmail", @@ -295,7 +304,7 @@ func TestHandleSetEmail(t *testing.T) { handleSetEmail(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -318,7 +327,8 @@ func TestHandleSetEmail(t *testing.T) { accountsObj: mockAccountsObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.accounts.setEmail", @@ -330,7 +340,7 @@ func TestHandleSetEmail(t *testing.T) { handleSetEmail(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -348,7 +358,8 @@ func TestHandleSetLanguage(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.accounts.setLanguage", @@ -358,7 +369,7 @@ func TestHandleSetLanguage(t *testing.T) { handleSetLanguage(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -373,7 +384,8 @@ func TestHandleSetLocation(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.accounts.setLocation", @@ -383,7 +395,7 @@ func TestHandleSetLocation(t *testing.T) { handleSetLocation(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -398,7 +410,8 @@ func TestHandleGetUserIconFile(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.accounts.getUserIconFile", @@ -408,7 +421,7 @@ func TestHandleGetUserIconFile(t *testing.T) { handleGetUserIconFile(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -425,7 +438,8 @@ func TestHandleGetUserIconFile(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.accounts.getUserIconFile", @@ -437,7 +451,7 @@ func TestHandleGetUserIconFile(t *testing.T) { handleGetUserIconFile(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -456,13 +470,14 @@ func TestHandleGetColorScheme(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "freedesktop.settings.getColorScheme"} handleGetColorScheme(conn, req, manager) var resp models.Response[map[string]uint32] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -487,13 +502,14 @@ func TestHandleGetColorScheme(t *testing.T) { settingsObj: mockSettingsObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "freedesktop.settings.getColorScheme"} handleGetColorScheme(conn, req, manager) var resp models.Response[map[string]uint32] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -515,7 +531,8 @@ func TestHandleRequest(t *testing.T) { } t.Run("unknown method", func(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.unknown", @@ -524,7 +541,7 @@ func TestHandleRequest(t *testing.T) { HandleRequest(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -532,7 +549,8 @@ func TestHandleRequest(t *testing.T) { }) t.Run("valid method - getState", func(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "freedesktop.getState", @@ -541,7 +559,7 @@ func TestHandleRequest(t *testing.T) { HandleRequest(conn, req, manager) var resp models.Response[FreedeskState] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -560,7 +578,8 @@ func TestHandleRequest(t *testing.T) { } for _, method := range tests { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: method, @@ -570,7 +589,7 @@ func TestHandleRequest(t *testing.T) { HandleRequest(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) diff --git a/core/internal/server/freedesktop/manager.go b/core/internal/server/freedesktop/manager.go index 510c78cb2..e8e281505 100644 --- a/core/internal/server/freedesktop/manager.go +++ b/core/internal/server/freedesktop/manager.go @@ -8,7 +8,7 @@ import ( "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil" + "github.com/AvengeMedia/dankgo/dbusutil" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/server/freedesktop/types.go b/core/internal/server/freedesktop/types.go index c68ff92a5..47a350424 100644 --- a/core/internal/server/freedesktop/types.go +++ b/core/internal/server/freedesktop/types.go @@ -4,7 +4,7 @@ import ( "sync" "time" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/server/location/handlers.go b/core/internal/server/location/handlers.go index c9d556c42..984d6d41f 100644 --- a/core/internal/server/location/handlers.go +++ b/core/internal/server/location/handlers.go @@ -1,9 +1,7 @@ package location import ( - "encoding/json" "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) @@ -13,7 +11,7 @@ type LocationEvent struct { Data State `json:"data"` } -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { switch req.Method { case "location.getState": handleGetState(conn, req, manager) @@ -25,11 +23,11 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleGetState(conn net.Conn, req models.Request, manager *Manager) { +func handleGetState(conn *models.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, manager.GetState()) } -func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { +func handleSubscribe(conn *models.Conn, req models.Request, manager *Manager) { clientID := fmt.Sprintf("client-%p", conn) stateChan := manager.Subscribe(clientID) defer manager.Unsubscribe(clientID) @@ -40,7 +38,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { Data: initialState, } - if err := json.NewEncoder(conn).Encode(models.Response[LocationEvent]{ + if err := conn.WriteResponse(models.Response[LocationEvent]{ ID: req.ID, Result: &event, }); err != nil { @@ -52,7 +50,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { Type: "state_changed", Data: state, } - if err := json.NewEncoder(conn).Encode(models.Response[LocationEvent]{ + if err := conn.WriteResponse(models.Response[LocationEvent]{ Result: &event, }); err != nil { return diff --git a/core/internal/server/location/types.go b/core/internal/server/location/types.go index 1f3d5180b..f18198738 100644 --- a/core/internal/server/location/types.go +++ b/core/internal/server/location/types.go @@ -4,7 +4,7 @@ import ( "sync" "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) type State struct { diff --git a/core/internal/server/loginctl/handlers.go b/core/internal/server/loginctl/handlers.go index ded5ed311..3d8db154c 100644 --- a/core/internal/server/loginctl/handlers.go +++ b/core/internal/server/loginctl/handlers.go @@ -1,15 +1,13 @@ package loginctl import ( - "encoding/json" "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { switch req.Method { case "loginctl.getState": handleGetState(conn, req, manager) @@ -38,11 +36,11 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleGetState(conn net.Conn, req models.Request, manager *Manager) { +func handleGetState(conn *models.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, manager.GetState()) } -func handleLock(conn net.Conn, req models.Request, manager *Manager) { +func handleLock(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.Lock(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -50,7 +48,7 @@ func handleLock(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "locked"}) } -func handleUnlock(conn net.Conn, req models.Request, manager *Manager) { +func handleUnlock(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.Unlock(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -58,7 +56,7 @@ func handleUnlock(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "unlocked"}) } -func handleActivate(conn net.Conn, req models.Request, manager *Manager) { +func handleActivate(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.Activate(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -66,7 +64,7 @@ func handleActivate(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "activated"}) } -func handleSetIdleHint(conn net.Conn, req models.Request, manager *Manager) { +func handleSetIdleHint(conn *models.Conn, req models.Request, manager *Manager) { idle, err := params.Bool(req.Params, "idle") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -80,7 +78,7 @@ func handleSetIdleHint(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "idle hint set"}) } -func handleSetLockedHint(conn net.Conn, req models.Request, manager *Manager) { +func handleSetLockedHint(conn *models.Conn, req models.Request, manager *Manager) { locked, err := params.Bool(req.Params, "locked") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -94,7 +92,7 @@ func handleSetLockedHint(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "locked hint set"}) } -func handleSetLockBeforeSuspend(conn net.Conn, req models.Request, manager *Manager) { +func handleSetLockBeforeSuspend(conn *models.Conn, req models.Request, manager *Manager) { enabled, err := params.Bool(req.Params, "enabled") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -105,7 +103,7 @@ func handleSetLockBeforeSuspend(conn net.Conn, req models.Request, manager *Mana models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "lock before suspend set"}) } -func handleSetSleepInhibitorEnabled(conn net.Conn, req models.Request, manager *Manager) { +func handleSetSleepInhibitorEnabled(conn *models.Conn, req models.Request, manager *Manager) { enabled, err := params.Bool(req.Params, "enabled") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -116,7 +114,7 @@ func handleSetSleepInhibitorEnabled(conn net.Conn, req models.Request, manager * models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "sleep inhibitor setting updated"}) } -func handleLockerReady(conn net.Conn, req models.Request, manager *Manager) { +func handleLockerReady(conn *models.Conn, req models.Request, manager *Manager) { manager.lockTimerMu.Lock() if manager.lockTimer != nil { manager.lockTimer.Stop() @@ -133,7 +131,7 @@ func handleLockerReady(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "ok"}) } -func handleTerminate(conn net.Conn, req models.Request, manager *Manager) { +func handleTerminate(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.Terminate(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -141,7 +139,7 @@ func handleTerminate(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "terminated"}) } -func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { +func handleSubscribe(conn *models.Conn, req models.Request, manager *Manager) { clientID := fmt.Sprintf("client-%p", conn) stateChan := manager.Subscribe(clientID) defer manager.Unsubscribe(clientID) @@ -151,7 +149,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { Type: EventStateChanged, Data: initialState, } - if err := json.NewEncoder(conn).Encode(models.Response[SessionEvent]{ + if err := conn.WriteResponse(models.Response[SessionEvent]{ ID: req.ID, Result: &event, }); err != nil { @@ -163,7 +161,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { Type: EventStateChanged, Data: state, } - if err := json.NewEncoder(conn).Encode(models.Response[SessionEvent]{ + if err := conn.WriteResponse(models.Response[SessionEvent]{ Result: &event, }); err != nil { return diff --git a/core/internal/server/loginctl/handlers_test.go b/core/internal/server/loginctl/handlers_test.go index d3169e48b..dfcb69329 100644 --- a/core/internal/server/loginctl/handlers_test.go +++ b/core/internal/server/loginctl/handlers_test.go @@ -44,11 +44,12 @@ func (m *mockNetConn) Close() error { } func TestRespondError_Loginctl(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) models.RespondError(conn, 123, "test error") var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -57,12 +58,13 @@ func TestRespondError_Loginctl(t *testing.T) { } func TestRespond_Loginctl(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) result := models.SuccessResult{Success: true, Message: "test"} models.Respond(conn, 123, result) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -85,13 +87,14 @@ func TestHandleGetState(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "loginctl.getState"} handleGetState(conn, req, manager) var resp models.Response[SessionState] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -114,12 +117,13 @@ func TestHandleLock(t *testing.T) { sessionObj: mockSessionObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "loginctl.lock"} handleLock(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -140,12 +144,13 @@ func TestHandleLock(t *testing.T) { sessionObj: mockSessionObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "loginctl.lock"} handleLock(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -165,12 +170,13 @@ func TestHandleUnlock(t *testing.T) { sessionObj: mockSessionObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "loginctl.unlock"} handleUnlock(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -191,12 +197,13 @@ func TestHandleUnlock(t *testing.T) { sessionObj: mockSessionObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "loginctl.unlock"} handleUnlock(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -216,12 +223,13 @@ func TestHandleActivate(t *testing.T) { sessionObj: mockSessionObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "loginctl.activate"} handleActivate(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -242,12 +250,13 @@ func TestHandleActivate(t *testing.T) { sessionObj: mockSessionObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "loginctl.activate"} handleActivate(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -262,7 +271,8 @@ func TestHandleSetIdleHint(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "loginctl.setIdleHint", @@ -272,7 +282,7 @@ func TestHandleSetIdleHint(t *testing.T) { handleSetIdleHint(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -290,7 +300,8 @@ func TestHandleSetIdleHint(t *testing.T) { sessionObj: mockSessionObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "loginctl.setIdleHint", @@ -302,7 +313,7 @@ func TestHandleSetIdleHint(t *testing.T) { handleSetIdleHint(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -323,7 +334,8 @@ func TestHandleSetIdleHint(t *testing.T) { sessionObj: mockSessionObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "loginctl.setIdleHint", @@ -335,7 +347,7 @@ func TestHandleSetIdleHint(t *testing.T) { handleSetIdleHint(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -355,12 +367,13 @@ func TestHandleTerminate(t *testing.T) { sessionObj: mockSessionObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "loginctl.terminate"} handleTerminate(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -381,12 +394,13 @@ func TestHandleTerminate(t *testing.T) { sessionObj: mockSessionObj, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "loginctl.terminate"} handleTerminate(conn, req, manager) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -404,7 +418,8 @@ func TestHandleRequest(t *testing.T) { } t.Run("unknown method", func(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "loginctl.unknown", @@ -413,7 +428,7 @@ func TestHandleRequest(t *testing.T) { HandleRequest(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -421,7 +436,8 @@ func TestHandleRequest(t *testing.T) { }) t.Run("valid method - getState", func(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "loginctl.getState", @@ -430,7 +446,7 @@ func TestHandleRequest(t *testing.T) { HandleRequest(conn, req, manager) var resp models.Response[SessionState] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -444,7 +460,8 @@ func TestHandleRequest(t *testing.T) { manager.sessionObj = mockSessionObj - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "loginctl.lock", @@ -453,7 +470,7 @@ func TestHandleRequest(t *testing.T) { HandleRequest(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -469,7 +486,8 @@ func TestHandleSubscribe(t *testing.T) { stateMutex: sync.RWMutex{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "loginctl.subscribe"} done := make(chan bool) @@ -480,11 +498,11 @@ func TestHandleSubscribe(t *testing.T) { time.Sleep(50 * time.Millisecond) - conn.Close() + mc.Close() - if conn.writeBuf.Len() > 0 { + if mc.writeBuf.Len() > 0 { var resp models.Response[SessionEvent] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) if err == nil { assert.Equal(t, 123, resp.ID) require.NotNil(t, resp.Result) diff --git a/core/internal/server/loginctl/manager.go b/core/internal/server/loginctl/manager.go index 0c51e6457..3ccd4ac40 100644 --- a/core/internal/server/loginctl/manager.go +++ b/core/internal/server/loginctl/manager.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil" + "github.com/AvengeMedia/dankgo/dbusutil" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/server/loginctl/monitor.go b/core/internal/server/loginctl/monitor.go index 4c0605a0b..e10263903 100644 --- a/core/internal/server/loginctl/monitor.go +++ b/core/internal/server/loginctl/monitor.go @@ -3,7 +3,7 @@ package loginctl import ( "time" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/dbusutil" + "github.com/AvengeMedia/dankgo/dbusutil" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/server/loginctl/types.go b/core/internal/server/loginctl/types.go index c3df1920b..eb5e36fba 100644 --- a/core/internal/server/loginctl/types.go +++ b/core/internal/server/loginctl/types.go @@ -6,7 +6,7 @@ import ( "sync/atomic" "time" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/server/matugen_handler.go b/core/internal/server/matugen_handler.go index 73c6ad610..0c84cd491 100644 --- a/core/internal/server/matugen_handler.go +++ b/core/internal/server/matugen_handler.go @@ -2,7 +2,6 @@ package server import ( "context" - "net" "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/matugen" @@ -14,7 +13,7 @@ type MatugenQueueResult struct { Message string `json:"message,omitempty"` } -func handleMatugenQueue(conn net.Conn, req models.Request) { +func handleMatugenQueue(conn *models.Conn, req models.Request) { opts := matugen.Options{ StateDir: models.GetOr(req, "stateDir", ""), ShellDir: models.GetOr(req, "shellDir", ""), @@ -70,7 +69,7 @@ func handleMatugenQueue(conn net.Conn, req models.Request) { } } -func handleMatugenStatus(conn net.Conn, req models.Request) { +func handleMatugenStatus(conn *models.Conn, req models.Request) { queue := matugen.GetQueue() models.Respond(conn, req.ID, map[string]bool{ "running": queue.IsRunning(), diff --git a/core/internal/server/mime/handlers.go b/core/internal/server/mime/handlers.go index de4d6296c..4c58c51ed 100644 --- a/core/internal/server/mime/handlers.go +++ b/core/internal/server/mime/handlers.go @@ -2,11 +2,10 @@ package mime import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/desktop" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) type defaultResult struct { @@ -23,7 +22,7 @@ type queryResult struct { Defaults map[string]string `json:"defaults"` } -func HandleRequest(conn net.Conn, req models.Request) { +func HandleRequest(conn *models.Conn, req models.Request) { switch req.Method { case "mime.getDefault": handleGetDefault(conn, req) @@ -43,7 +42,7 @@ func HandleRequest(conn net.Conn, req models.Request) { } } -func handleGetDefault(conn net.Conn, req models.Request) { +func handleGetDefault(conn *models.Conn, req models.Request) { mimeType, err := mimeParam(req.Params, "mimeType") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -55,7 +54,7 @@ func handleGetDefault(conn net.Conn, req models.Request) { }) } -func handleSetDefault(conn net.Conn, req models.Request) { +func handleSetDefault(conn *models.Conn, req models.Request) { mimeType, err := mimeParam(req.Params, "mimeType") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -73,7 +72,7 @@ func handleSetDefault(conn net.Conn, req models.Request) { models.Respond(conn, req.ID, models.SuccessResult{Success: true}) } -func handleSetDefaults(conn net.Conn, req models.Request) { +func handleSetDefaults(conn *models.Conn, req models.Request) { desktopID, err := params.StringNonEmpty(req.Params, "desktopId") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -91,7 +90,7 @@ func handleSetDefaults(conn net.Conn, req models.Request) { models.Respond(conn, req.ID, models.SuccessResult{Success: true}) } -func handleAppsForMime(conn net.Conn, req models.Request) { +func handleAppsForMime(conn *models.Conn, req models.Request) { mimeType, err := mimeParam(req.Params, "mimeType") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -107,7 +106,7 @@ func handleAppsForMime(conn net.Conn, req models.Request) { }) } -func handleQueryDefaults(conn net.Conn, req models.Request) { +func handleQueryDefaults(conn *models.Conn, req models.Request) { mimeTypes, err := mimeListParam(req, "mimeTypes") if err != nil { models.RespondError(conn, req.ID, err.Error()) diff --git a/core/internal/server/models/types.go b/core/internal/server/models/types.go index 2933fb517..8449f4715 100644 --- a/core/internal/server/models/types.go +++ b/core/internal/server/models/types.go @@ -1,13 +1,17 @@ package models import ( - "encoding/json" "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc" + "github.com/AvengeMedia/dankgo/ipc/params" ) +type Conn = ipc.ConnWriter + +func NewConn(c net.Conn) *Conn { return ipc.NewConnWriter(c) } + type Request struct { ID int `json:"id,omitempty"` Method string `json:"method"` @@ -29,15 +33,13 @@ type Response[T any] struct { Error string `json:"error,omitempty"` } -func RespondError(conn net.Conn, id int, errMsg string) { +func RespondError(conn *Conn, id int, errMsg string) { log.Errorf("DMS API Error: id=%d error=%s", id, errMsg) - resp := Response[any]{ID: id, Error: errMsg} - json.NewEncoder(conn).Encode(resp) + _ = conn.WriteResponse(Response[any]{ID: id, Error: errMsg}) } -func Respond[T any](conn net.Conn, id int, result T) { - resp := Response[T]{ID: id, Result: &result} - json.NewEncoder(conn).Encode(resp) +func Respond[T any](conn *Conn, id int, result T) { + _ = conn.WriteResponse(Response[T]{ID: id, Result: &result}) } type SuccessResult struct { diff --git a/core/internal/server/network/handlers.go b/core/internal/server/network/handlers.go index 2c023d63e..d0b132061 100644 --- a/core/internal/server/network/handlers.go +++ b/core/internal/server/network/handlers.go @@ -1,17 +1,15 @@ package network import ( - "encoding/json" "fmt" - "net" "os" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { switch req.Method { case "network.getState": handleGetState(conn, req, manager) @@ -86,7 +84,7 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleCredentialsSubmit(conn net.Conn, req models.Request, manager *Manager) { +func handleCredentialsSubmit(conn *models.Conn, req models.Request, manager *Manager) { token, err := params.String(req.Params, "token") if err != nil { log.Warnf("handleCredentialsSubmit: missing or invalid token parameter") @@ -113,7 +111,7 @@ func handleCredentialsSubmit(conn net.Conn, req models.Request, manager *Manager models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "credentials submitted"}) } -func handleCredentialsCancel(conn net.Conn, req models.Request, manager *Manager) { +func handleCredentialsCancel(conn *models.Conn, req models.Request, manager *Manager) { token, err := params.String(req.Params, "token") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -128,11 +126,11 @@ func handleCredentialsCancel(conn net.Conn, req models.Request, manager *Manager models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "credentials cancelled"}) } -func handleGetState(conn net.Conn, req models.Request, manager *Manager) { +func handleGetState(conn *models.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, manager.GetState()) } -func handleScanWiFi(conn net.Conn, req models.Request, manager *Manager) { +func handleScanWiFi(conn *models.Conn, req models.Request, manager *Manager) { device := params.StringOpt(req.Params, "device", "") var err error if device != "" { @@ -147,11 +145,11 @@ func handleScanWiFi(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "scanning"}) } -func handleGetWiFiNetworks(conn net.Conn, req models.Request, manager *Manager) { +func handleGetWiFiNetworks(conn *models.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, manager.GetWiFiNetworks()) } -func handleConnectWiFi(conn net.Conn, req models.Request, manager *Manager) { +func handleConnectWiFi(conn *models.Conn, req models.Request, manager *Manager) { ssid, err := params.String(req.Params, "ssid") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -204,7 +202,7 @@ func handleConnectWiFi(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"}) } -func handleDisconnectWiFi(conn net.Conn, req models.Request, manager *Manager) { +func handleDisconnectWiFi(conn *models.Conn, req models.Request, manager *Manager) { device := params.StringOpt(req.Params, "device", "") var err error if device != "" { @@ -219,7 +217,7 @@ func handleDisconnectWiFi(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "disconnected"}) } -func handleForgetWiFi(conn net.Conn, req models.Request, manager *Manager) { +func handleForgetWiFi(conn *models.Conn, req models.Request, manager *Manager) { ssid, err := params.String(req.Params, "ssid") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -234,7 +232,7 @@ func handleForgetWiFi(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "forgotten"}) } -func handleToggleWiFi(conn net.Conn, req models.Request, manager *Manager) { +func handleToggleWiFi(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.ToggleWiFi(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -244,7 +242,7 @@ func handleToggleWiFi(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, map[string]bool{"enabled": state.WiFiEnabled}) } -func handleEnableWiFi(conn net.Conn, req models.Request, manager *Manager) { +func handleEnableWiFi(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.EnableWiFi(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -252,7 +250,7 @@ func handleEnableWiFi(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, map[string]bool{"enabled": true}) } -func handleDisableWiFi(conn net.Conn, req models.Request, manager *Manager) { +func handleDisableWiFi(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.DisableWiFi(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -260,7 +258,7 @@ func handleDisableWiFi(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, map[string]bool{"enabled": false}) } -func handleConnectEthernetSpecificConfig(conn net.Conn, req models.Request, manager *Manager) { +func handleConnectEthernetSpecificConfig(conn *models.Conn, req models.Request, manager *Manager) { uuid, err := params.String(req.Params, "uuid") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -273,7 +271,7 @@ func handleConnectEthernetSpecificConfig(conn net.Conn, req models.Request, mana models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"}) } -func handleConnectEthernet(conn net.Conn, req models.Request, manager *Manager) { +func handleConnectEthernet(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.ConnectEthernet(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -281,7 +279,7 @@ func handleConnectEthernet(conn net.Conn, req models.Request, manager *Manager) models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connecting"}) } -func handleDisconnectEthernet(conn net.Conn, req models.Request, manager *Manager) { +func handleDisconnectEthernet(conn *models.Conn, req models.Request, manager *Manager) { device := params.StringOpt(req.Params, "device", "") var err error if device != "" { @@ -296,7 +294,7 @@ func handleDisconnectEthernet(conn net.Conn, req models.Request, manager *Manage models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "disconnected"}) } -func handleSetPreference(conn net.Conn, req models.Request, manager *Manager) { +func handleSetPreference(conn *models.Conn, req models.Request, manager *Manager) { preference, err := params.String(req.Params, "preference") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -311,7 +309,7 @@ func handleSetPreference(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, map[string]string{"preference": preference}) } -func handleGetNetworkInfo(conn net.Conn, req models.Request, manager *Manager) { +func handleGetNetworkInfo(conn *models.Conn, req models.Request, manager *Manager) { ssid, err := params.String(req.Params, "ssid") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -327,7 +325,7 @@ func handleGetNetworkInfo(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, network) } -func handleGetNetworkQRCode(conn net.Conn, req models.Request, manager *Manager) { +func handleGetNetworkQRCode(conn *models.Conn, req models.Request, manager *Manager) { ssid, err := params.String(req.Params, "ssid") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -343,7 +341,7 @@ func handleGetNetworkQRCode(conn net.Conn, req models.Request, manager *Manager) models.Respond(conn, req.ID, content) } -func handleGetNetworkQRCodeContent(conn net.Conn, req models.Request, manager *Manager) { +func handleGetNetworkQRCodeContent(conn *models.Conn, req models.Request, manager *Manager) { ssid, err := params.String(req.Params, "ssid") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -359,7 +357,7 @@ func handleGetNetworkQRCodeContent(conn net.Conn, req models.Request, manager *M models.Respond(conn, req.ID, content) } -func handleDeleteQRCode(conn net.Conn, req models.Request, _ *Manager) { +func handleDeleteQRCode(conn *models.Conn, req models.Request, _ *Manager) { path, err := params.String(req.Params, "path") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -379,7 +377,7 @@ func handleDeleteQRCode(conn net.Conn, req models.Request, _ *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "QR code file deleted"}) } -func handleGetWiredNetworkInfo(conn net.Conn, req models.Request, manager *Manager) { +func handleGetWiredNetworkInfo(conn *models.Conn, req models.Request, manager *Manager) { uuid, err := params.String(req.Params, "uuid") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -395,7 +393,7 @@ func handleGetWiredNetworkInfo(conn net.Conn, req models.Request, manager *Manag models.Respond(conn, req.ID, network) } -func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { +func handleSubscribe(conn *models.Conn, req models.Request, manager *Manager) { clientID := fmt.Sprintf("client-%p", conn) stateChan := manager.Subscribe(clientID) defer manager.Unsubscribe(clientID) @@ -405,7 +403,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { Type: EventStateChanged, Data: initialState, } - if err := json.NewEncoder(conn).Encode(models.Response[NetworkEvent]{ + if err := conn.WriteResponse(models.Response[NetworkEvent]{ ID: req.ID, Result: &event, }); err != nil { @@ -417,7 +415,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { Type: EventStateChanged, Data: state, } - if err := json.NewEncoder(conn).Encode(models.Response[NetworkEvent]{ + if err := conn.WriteResponse(models.Response[NetworkEvent]{ Result: &event, }); err != nil { return @@ -425,7 +423,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { } } -func handleListVPNProfiles(conn net.Conn, req models.Request, manager *Manager) { +func handleListVPNProfiles(conn *models.Conn, req models.Request, manager *Manager) { profiles, err := manager.ListVPNProfiles() if err != nil { log.Warnf("handleListVPNProfiles: failed to list profiles: %v", err) @@ -436,7 +434,7 @@ func handleListVPNProfiles(conn net.Conn, req models.Request, manager *Manager) models.Respond(conn, req.ID, profiles) } -func handleListActiveVPN(conn net.Conn, req models.Request, manager *Manager) { +func handleListActiveVPN(conn *models.Conn, req models.Request, manager *Manager) { active, err := manager.ListActiveVPN() if err != nil { log.Warnf("handleListActiveVPN: failed to list active VPNs: %v", err) @@ -447,7 +445,7 @@ func handleListActiveVPN(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, active) } -func handleConnectVPN(conn net.Conn, req models.Request, manager *Manager) { +func handleConnectVPN(conn *models.Conn, req models.Request, manager *Manager) { uuidOrName, ok := params.StringAlt(req.Params, "uuidOrName", "name", "uuid") if !ok { log.Warnf("handleConnectVPN: missing uuidOrName/name/uuid parameter") @@ -466,7 +464,7 @@ func handleConnectVPN(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN connection initiated"}) } -func handleDisconnectVPN(conn net.Conn, req models.Request, manager *Manager) { +func handleDisconnectVPN(conn *models.Conn, req models.Request, manager *Manager) { uuidOrName, ok := params.StringAlt(req.Params, "uuidOrName", "name", "uuid") if !ok { log.Warnf("handleDisconnectVPN: missing uuidOrName/name/uuid parameter") @@ -483,7 +481,7 @@ func handleDisconnectVPN(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN disconnected"}) } -func handleDisconnectAllVPN(conn net.Conn, req models.Request, manager *Manager) { +func handleDisconnectAllVPN(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.DisconnectAllVPN(); err != nil { log.Warnf("handleDisconnectAllVPN: failed: %v", err) models.RespondError(conn, req.ID, fmt.Sprintf("failed to disconnect all VPNs: %v", err)) @@ -493,7 +491,7 @@ func handleDisconnectAllVPN(conn net.Conn, req models.Request, manager *Manager) models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "All VPNs disconnected"}) } -func handleClearVPNCredentials(conn net.Conn, req models.Request, manager *Manager) { +func handleClearVPNCredentials(conn *models.Conn, req models.Request, manager *Manager) { uuidOrName, ok := params.StringAlt(req.Params, "uuid", "name", "uuidOrName") if !ok { log.Warnf("handleClearVPNCredentials: missing uuidOrName/name/uuid parameter") @@ -510,7 +508,7 @@ func handleClearVPNCredentials(conn net.Conn, req models.Request, manager *Manag models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN credentials cleared"}) } -func handleSetWiFiAutoconnect(conn net.Conn, req models.Request, manager *Manager) { +func handleSetWiFiAutoconnect(conn *models.Conn, req models.Request, manager *Manager) { ssid, err := params.String(req.Params, "ssid") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -531,7 +529,7 @@ func handleSetWiFiAutoconnect(conn net.Conn, req models.Request, manager *Manage models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "autoconnect updated"}) } -func handleListVPNPlugins(conn net.Conn, req models.Request, manager *Manager) { +func handleListVPNPlugins(conn *models.Conn, req models.Request, manager *Manager) { plugins, err := manager.ListVPNPlugins() if err != nil { log.Warnf("handleListVPNPlugins: failed to list plugins: %v", err) @@ -542,7 +540,7 @@ func handleListVPNPlugins(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, plugins) } -func handleImportVPN(conn net.Conn, req models.Request, manager *Manager) { +func handleImportVPN(conn *models.Conn, req models.Request, manager *Manager) { filePath, ok := params.StringAlt(req.Params, "file", "path") if !ok { models.RespondError(conn, req.ID, "missing 'file' or 'path' parameter") @@ -561,7 +559,7 @@ func handleImportVPN(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, result) } -func handleGetVPNConfig(conn net.Conn, req models.Request, manager *Manager) { +func handleGetVPNConfig(conn *models.Conn, req models.Request, manager *Manager) { uuidOrName, ok := params.StringAlt(req.Params, "uuid", "name", "uuidOrName") if !ok { models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter") @@ -578,7 +576,7 @@ func handleGetVPNConfig(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, config) } -func handleUpdateVPNConfig(conn net.Conn, req models.Request, manager *Manager) { +func handleUpdateVPNConfig(conn *models.Conn, req models.Request, manager *Manager) { connUUID, err := params.String(req.Params, "uuid") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -611,7 +609,7 @@ func handleUpdateVPNConfig(conn net.Conn, req models.Request, manager *Manager) models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN config updated"}) } -func handleDeleteVPN(conn net.Conn, req models.Request, manager *Manager) { +func handleDeleteVPN(conn *models.Conn, req models.Request, manager *Manager) { uuidOrName, ok := params.StringAlt(req.Params, "uuid", "name", "uuidOrName") if !ok { models.RespondError(conn, req.ID, "missing 'uuid', 'name', or 'uuidOrName' parameter") @@ -627,7 +625,7 @@ func handleDeleteVPN(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "VPN deleted"}) } -func handleSetVPNCredentials(conn net.Conn, req models.Request, manager *Manager) { +func handleSetVPNCredentials(conn *models.Conn, req models.Request, manager *Manager) { connUUID, err := params.String(req.Params, "uuid") if err != nil { models.RespondError(conn, req.ID, err.Error()) diff --git a/core/internal/server/network/handlers_test.go b/core/internal/server/network/handlers_test.go index ea8ae3335..092ccf9cf 100644 --- a/core/internal/server/network/handlers_test.go +++ b/core/internal/server/network/handlers_test.go @@ -39,11 +39,12 @@ func (m *mockNetConn) Close() error { } func TestRespondError_Network(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) models.RespondError(conn, 123, "test error") var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -52,12 +53,13 @@ func TestRespondError_Network(t *testing.T) { } func TestRespond_Network(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) result := models.SuccessResult{Success: true, Message: "test"} models.Respond(conn, 123, result) var resp models.Response[models.SuccessResult] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -76,13 +78,14 @@ func TestHandleGetState(t *testing.T) { }, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "network.getState"} handleGetState(conn, req, manager) var resp models.Response[NetworkState] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -102,13 +105,14 @@ func TestHandleGetWiFiNetworks(t *testing.T) { }, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ID: 123, Method: "network.wifi.networks"} handleGetWiFiNetworks(conn, req, manager) var resp models.Response[[]WiFiNetwork] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -124,7 +128,8 @@ func TestHandleConnectWiFi(t *testing.T) { state: &NetworkState{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "network.wifi.connect", @@ -134,7 +139,7 @@ func TestHandleConnectWiFi(t *testing.T) { handleConnectWiFi(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -148,7 +153,8 @@ func TestHandleSetPreference(t *testing.T) { state: &NetworkState{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "network.preference.set", @@ -158,7 +164,7 @@ func TestHandleSetPreference(t *testing.T) { handleSetPreference(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -172,7 +178,8 @@ func TestHandleGetNetworkInfo(t *testing.T) { state: &NetworkState{}, } - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "network.info", @@ -182,7 +189,7 @@ func TestHandleGetNetworkInfo(t *testing.T) { handleGetNetworkInfo(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -198,7 +205,8 @@ func TestHandleRequest(t *testing.T) { } t.Run("unknown method", func(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "network.unknown", @@ -207,7 +215,7 @@ func TestHandleRequest(t *testing.T) { HandleRequest(conn, req, manager) var resp models.Response[any] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -215,7 +223,8 @@ func TestHandleRequest(t *testing.T) { }) t.Run("valid method - getState", func(t *testing.T) { - conn := newMockNetConn() + mc := newMockNetConn() + conn := models.NewConn(mc) req := models.Request{ ID: 123, Method: "network.getState", @@ -224,7 +233,7 @@ func TestHandleRequest(t *testing.T) { HandleRequest(conn, req, manager) var resp models.Response[NetworkState] - err := json.NewDecoder(conn.writeBuf).Decode(&resp) + err := json.NewDecoder(mc.writeBuf).Decode(&resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) diff --git a/core/internal/server/network/subscription_broker.go b/core/internal/server/network/subscription_broker.go index 1b150a260..5985a815d 100644 --- a/core/internal/server/network/subscription_broker.go +++ b/core/internal/server/network/subscription_broker.go @@ -6,7 +6,7 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) type SubscriptionBroker struct { diff --git a/core/internal/server/network/types.go b/core/internal/server/network/types.go index 6f7191c4c..ba982bcf1 100644 --- a/core/internal/server/network/types.go +++ b/core/internal/server/network/types.go @@ -3,7 +3,7 @@ package network import ( "sync" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/server/params/params.go b/core/internal/server/params/params.go deleted file mode 100644 index 1f592c61f..000000000 --- a/core/internal/server/params/params.go +++ /dev/null @@ -1,113 +0,0 @@ -package params - -import "fmt" - -func Get[T any](params map[string]any, key string) (T, error) { - val, ok := params[key].(T) - if !ok { - var zero T - return zero, fmt.Errorf("missing or invalid '%s' parameter", key) - } - return val, nil -} - -func GetOpt[T any](params map[string]any, key string, def T) T { - if val, ok := params[key].(T); ok { - return val - } - return def -} - -func String(params map[string]any, key string) (string, error) { - return Get[string](params, key) -} - -func StringNonEmpty(params map[string]any, key string) (string, error) { - val, err := Get[string](params, key) - if err != nil || val == "" { - return "", fmt.Errorf("missing or invalid '%s' parameter", key) - } - return val, nil -} - -func StringOpt(params map[string]any, key string, def string) string { - return GetOpt(params, key, def) -} - -func Int(params map[string]any, key string) (int, error) { - val, err := Get[float64](params, key) - if err != nil { - return 0, err - } - return int(val), nil -} - -func IntOpt(params map[string]any, key string, def int) int { - if val, ok := params[key].(float64); ok { - return int(val) - } - return def -} - -func Float(params map[string]any, key string) (float64, error) { - return Get[float64](params, key) -} - -func FloatOpt(params map[string]any, key string, def float64) float64 { - return GetOpt(params, key, def) -} - -func Bool(params map[string]any, key string) (bool, error) { - return Get[bool](params, key) -} - -func BoolOpt(params map[string]any, key string, def bool) bool { - return GetOpt(params, key, def) -} - -func StringMap(params map[string]any, key string) (map[string]string, error) { - rawMap, err := Get[map[string]any](params, key) - if err != nil { - return nil, err - } - result := make(map[string]string) - for k, v := range rawMap { - if str, ok := v.(string); ok { - result[k] = str - } - } - return result, nil -} - -func StringMapOpt(params map[string]any, key string) map[string]string { - rawMap, ok := params[key].(map[string]any) - if !ok { - return nil - } - result := make(map[string]string) - for k, v := range rawMap { - if str, ok := v.(string); ok { - result[k] = str - } - } - return result -} - -func Any(params map[string]any, key string) (any, bool) { - val, ok := params[key] - return val, ok -} - -func AnyMap(params map[string]any, key string) (map[string]any, bool) { - val, ok := params[key].(map[string]any) - return val, ok -} - -func StringAlt(params map[string]any, keys ...string) (string, bool) { - for _, key := range keys { - if val, ok := params[key].(string); ok { - return val, true - } - } - return "", false -} diff --git a/core/internal/server/params/params_test.go b/core/internal/server/params/params_test.go deleted file mode 100644 index 99847ff1c..000000000 --- a/core/internal/server/params/params_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package params - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestGet(t *testing.T) { - p := map[string]any{"key": "value"} - val, err := Get[string](p, "key") - assert.NoError(t, err) - assert.Equal(t, "value", val) - - _, err = Get[string](p, "missing") - assert.Error(t, err) - - _, err = Get[int](p, "key") - assert.Error(t, err) -} - -func TestGetOpt(t *testing.T) { - p := map[string]any{"key": "value"} - assert.Equal(t, "value", GetOpt(p, "key", "default")) - assert.Equal(t, "default", GetOpt(p, "missing", "default")) -} - -func TestString(t *testing.T) { - p := map[string]any{"s": "hello", "n": 123} - val, err := String(p, "s") - assert.NoError(t, err) - assert.Equal(t, "hello", val) - - _, err = String(p, "n") - assert.Error(t, err) -} - -func TestStringNonEmpty(t *testing.T) { - p := map[string]any{"s": "hello", "empty": ""} - val, err := StringNonEmpty(p, "s") - assert.NoError(t, err) - assert.Equal(t, "hello", val) - - _, err = StringNonEmpty(p, "empty") - assert.Error(t, err) - - _, err = StringNonEmpty(p, "missing") - assert.Error(t, err) -} - -func TestStringOpt(t *testing.T) { - p := map[string]any{"s": "hello"} - assert.Equal(t, "hello", StringOpt(p, "s", "default")) - assert.Equal(t, "default", StringOpt(p, "missing", "default")) -} - -func TestInt(t *testing.T) { - p := map[string]any{"n": float64(42), "s": "str"} - val, err := Int(p, "n") - assert.NoError(t, err) - assert.Equal(t, 42, val) - - _, err = Int(p, "s") - assert.Error(t, err) -} - -func TestIntOpt(t *testing.T) { - p := map[string]any{"n": float64(42)} - assert.Equal(t, 42, IntOpt(p, "n", 0)) - assert.Equal(t, 99, IntOpt(p, "missing", 99)) -} - -func TestFloat(t *testing.T) { - p := map[string]any{"f": 3.14, "s": "str"} - val, err := Float(p, "f") - assert.NoError(t, err) - assert.Equal(t, 3.14, val) - - _, err = Float(p, "s") - assert.Error(t, err) -} - -func TestFloatOpt(t *testing.T) { - p := map[string]any{"f": 3.14} - assert.Equal(t, 3.14, FloatOpt(p, "f", 0)) - assert.Equal(t, 1.0, FloatOpt(p, "missing", 1.0)) -} - -func TestBool(t *testing.T) { - p := map[string]any{"b": true, "s": "str"} - val, err := Bool(p, "b") - assert.NoError(t, err) - assert.True(t, val) - - _, err = Bool(p, "s") - assert.Error(t, err) -} - -func TestBoolOpt(t *testing.T) { - p := map[string]any{"b": true} - assert.True(t, BoolOpt(p, "b", false)) - assert.True(t, BoolOpt(p, "missing", true)) -} - -func TestStringMap(t *testing.T) { - p := map[string]any{ - "m": map[string]any{"a": "1", "b": "2", "c": 3}, - } - val, err := StringMap(p, "m") - assert.NoError(t, err) - assert.Equal(t, map[string]string{"a": "1", "b": "2"}, val) - - _, err = StringMap(p, "missing") - assert.Error(t, err) -} - -func TestStringMapOpt(t *testing.T) { - p := map[string]any{ - "m": map[string]any{"a": "1"}, - } - assert.Equal(t, map[string]string{"a": "1"}, StringMapOpt(p, "m")) - assert.Nil(t, StringMapOpt(p, "missing")) -} - -func TestAny(t *testing.T) { - p := map[string]any{"k": 123} - val, ok := Any(p, "k") - assert.True(t, ok) - assert.Equal(t, 123, val) - - _, ok = Any(p, "missing") - assert.False(t, ok) -} - -func TestAnyMap(t *testing.T) { - inner := map[string]any{"nested": true} - p := map[string]any{"m": inner} - val, ok := AnyMap(p, "m") - assert.True(t, ok) - assert.Equal(t, inner, val) - - _, ok = AnyMap(p, "missing") - assert.False(t, ok) -} - -func TestStringAlt(t *testing.T) { - p := map[string]any{"b": "found"} - val, ok := StringAlt(p, "a", "b", "c") - assert.True(t, ok) - assert.Equal(t, "found", val) - - _, ok = StringAlt(p, "x", "y") - assert.False(t, ok) -} diff --git a/core/internal/server/plugins/handlers.go b/core/internal/server/plugins/handlers.go index 33f656483..761f1f2fe 100644 --- a/core/internal/server/plugins/handlers.go +++ b/core/internal/server/plugins/handlers.go @@ -2,12 +2,11 @@ package plugins import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) -func HandleRequest(conn net.Conn, req models.Request) { +func HandleRequest(conn *models.Conn, req models.Request) { switch req.Method { case "plugins.list": HandleList(conn, req) diff --git a/core/internal/server/plugins/handlers_test.go b/core/internal/server/plugins/handlers_test.go index 28978abf0..76cfbfac0 100644 --- a/core/internal/server/plugins/handlers_test.go +++ b/core/internal/server/plugins/handlers_test.go @@ -12,8 +12,9 @@ import ( ) func TestHandleList(t *testing.T) { - conn := net.NewMockConn(t) - conn.EXPECT().Write(mock.Anything).Return(0, nil).Maybe() + mc := net.NewMockConn(t) + conn := models.NewConn(mc) + mc.EXPECT().Write(mock.Anything).Return(0, nil).Maybe() req := models.Request{ ID: 123, @@ -25,8 +26,9 @@ func TestHandleList(t *testing.T) { } func TestHandleListInstalled(t *testing.T) { - conn := net.NewMockConn(t) - conn.EXPECT().Write(mock.Anything).Return(0, nil).Maybe() + mc := net.NewMockConn(t) + conn := models.NewConn(mc) + mc.EXPECT().Write(mock.Anything).Return(0, nil).Maybe() req := models.Request{ ID: 123, @@ -38,9 +40,10 @@ func TestHandleListInstalled(t *testing.T) { } func TestHandleInstallMissingName(t *testing.T) { - conn := net.NewMockConn(t) + mc := net.NewMockConn(t) + conn := models.NewConn(mc) var written []byte - conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { + mc.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { written = b return len(b), nil }).Maybe() @@ -61,9 +64,10 @@ func TestHandleInstallMissingName(t *testing.T) { } func TestHandleInstallInvalidName(t *testing.T) { - conn := net.NewMockConn(t) + mc := net.NewMockConn(t) + conn := models.NewConn(mc) var written []byte - conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { + mc.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { written = b return len(b), nil }).Maybe() @@ -85,9 +89,10 @@ func TestHandleInstallInvalidName(t *testing.T) { } func TestHandleUninstallMissingName(t *testing.T) { - conn := net.NewMockConn(t) + mc := net.NewMockConn(t) + conn := models.NewConn(mc) var written []byte - conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { + mc.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { written = b return len(b), nil }).Maybe() @@ -107,9 +112,10 @@ func TestHandleUninstallMissingName(t *testing.T) { } func TestHandleUpdateMissingName(t *testing.T) { - conn := net.NewMockConn(t) + mc := net.NewMockConn(t) + conn := models.NewConn(mc) var written []byte - conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { + mc.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { written = b return len(b), nil }).Maybe() @@ -129,9 +135,10 @@ func TestHandleUpdateMissingName(t *testing.T) { } func TestHandleSearchMissingQuery(t *testing.T) { - conn := net.NewMockConn(t) + mc := net.NewMockConn(t) + conn := models.NewConn(mc) var written []byte - conn.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { + mc.EXPECT().Write(mock.Anything).RunAndReturn(func(b []byte) (int, error) { written = b return len(b), nil }).Maybe() diff --git a/core/internal/server/plugins/install.go b/core/internal/server/plugins/install.go index 2c13a0e17..388495839 100644 --- a/core/internal/server/plugins/install.go +++ b/core/internal/server/plugins/install.go @@ -2,13 +2,12 @@ package plugins import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) -func HandleInstall(conn net.Conn, req models.Request) { +func HandleInstall(conn *models.Conn, req models.Request) { idOrName, ok := models.Get[string](req, "name") if !ok { models.RespondError(conn, req.ID, "missing or invalid 'name' parameter") diff --git a/core/internal/server/plugins/list.go b/core/internal/server/plugins/list.go index 476589ac1..d6b22a4dc 100644 --- a/core/internal/server/plugins/list.go +++ b/core/internal/server/plugins/list.go @@ -2,13 +2,12 @@ package plugins import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) -func HandleList(conn net.Conn, req models.Request) { +func HandleList(conn *models.Conn, req models.Request) { registry, err := plugins.NewRegistry() if err != nil { models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err)) diff --git a/core/internal/server/plugins/list_installed.go b/core/internal/server/plugins/list_installed.go index 7eeadeaa7..72485b12e 100644 --- a/core/internal/server/plugins/list_installed.go +++ b/core/internal/server/plugins/list_installed.go @@ -2,13 +2,12 @@ package plugins import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) -func HandleListInstalled(conn net.Conn, req models.Request) { +func HandleListInstalled(conn *models.Conn, req models.Request) { manager, err := plugins.NewManager() if err != nil { models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err)) diff --git a/core/internal/server/plugins/search.go b/core/internal/server/plugins/search.go index 11ec973f1..1221c4e5c 100644 --- a/core/internal/server/plugins/search.go +++ b/core/internal/server/plugins/search.go @@ -2,13 +2,12 @@ package plugins import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) -func HandleSearch(conn net.Conn, req models.Request) { +func HandleSearch(conn *models.Conn, req models.Request) { query, ok := models.Get[string](req, "query") if !ok { models.RespondError(conn, req.ID, "missing or invalid 'query' parameter") diff --git a/core/internal/server/plugins/uninstall.go b/core/internal/server/plugins/uninstall.go index 7a3d69848..8aadec788 100644 --- a/core/internal/server/plugins/uninstall.go +++ b/core/internal/server/plugins/uninstall.go @@ -2,13 +2,12 @@ package plugins import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) -func HandleUninstall(conn net.Conn, req models.Request) { +func HandleUninstall(conn *models.Conn, req models.Request) { name, ok := models.Get[string](req, "name") if !ok { models.RespondError(conn, req.ID, "missing or invalid 'name' parameter") diff --git a/core/internal/server/plugins/update.go b/core/internal/server/plugins/update.go index 4ea762a1e..98725a69b 100644 --- a/core/internal/server/plugins/update.go +++ b/core/internal/server/plugins/update.go @@ -2,13 +2,12 @@ package plugins import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/plugins" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) -func HandleUpdate(conn net.Conn, req models.Request) { +func HandleUpdate(conn *models.Conn, req models.Request) { name, ok := models.Get[string](req, "name") if !ok { models.RespondError(conn, req.ID, "missing or invalid 'name' parameter") diff --git a/core/internal/server/router.go b/core/internal/server/router.go index d07485678..acd08716d 100644 --- a/core/internal/server/router.go +++ b/core/internal/server/router.go @@ -2,7 +2,6 @@ package server import ( "fmt" - "net" "strings" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/apppicker" @@ -28,7 +27,7 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput" ) -func RouteRequest(conn net.Conn, req models.Request) { +func RouteRequest(conn *models.Conn, req models.Request) { if strings.HasPrefix(req.Method, "network.") { if networkManager == nil { models.RespondError(conn, req.ID, "network manager not initialized") @@ -223,7 +222,7 @@ func RouteRequest(conn net.Conn, req models.Request) { } } -func handleClipboardSetConfig(conn net.Conn, req models.Request) { +func handleClipboardSetConfig(conn *models.Conn, req models.Request) { cfg := clipboard.LoadConfig() if v, ok := models.Get[float64](req, "maxHistory"); ok { diff --git a/core/internal/server/server.go b/core/internal/server/server.go index 02df7d073..b88e120d8 100644 --- a/core/internal/server/server.go +++ b/core/internal/server/server.go @@ -1,19 +1,12 @@ package server import ( - "bufio" - "encoding/json" + "context" "errors" "fmt" - "net" - "os" - "path/filepath" "runtime/debug" - "strconv" - "strings" "sync" "sync/atomic" - "syscall" "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation" @@ -39,7 +32,9 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlcontext" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wlroutput" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/ipc" + "github.com/AvengeMedia/dankgo/paths" + "github.com/AvengeMedia/dankgo/syncmap" ) const APIVersion = 28 @@ -88,74 +83,10 @@ var capabilitySubscribers syncmap.Map[string, chan ServerInfo] var cupsSubscribers syncmap.Map[string, bool] var cupsSubscriberCount atomic.Int32 -func getSocketDir() string { - if runtime := os.Getenv("XDG_RUNTIME_DIR"); runtime != "" { - return runtime - } - - if os.Getuid() == 0 { - if _, err := os.Stat("/run"); err == nil { - return "/run/dankdots" - } - return "/var/run/dankdots" - } - - return os.TempDir() -} +var appPaths = paths.New("danklinux") func GetSocketPath() string { - return filepath.Join(getSocketDir(), fmt.Sprintf("danklinux-%d.sock", os.Getpid())) -} - -func FindSocket() (string, error) { - dir := getSocketDir() - entries, err := os.ReadDir(dir) - if err != nil { - return "", err - } - - for _, entry := range entries { - if strings.HasPrefix(entry.Name(), "danklinux-") && strings.HasSuffix(entry.Name(), ".sock") { - return filepath.Join(dir, entry.Name()), nil - } - } - return "", fmt.Errorf("no dms socket found") -} - -func cleanupStaleSockets() { - dir := getSocketDir() - entries, err := os.ReadDir(dir) - if err != nil { - return - } - - for _, entry := range entries { - if !strings.HasPrefix(entry.Name(), "danklinux-") || !strings.HasSuffix(entry.Name(), ".sock") { - continue - } - - pidStr := strings.TrimPrefix(entry.Name(), "danklinux-") - pidStr = strings.TrimSuffix(pidStr, ".sock") - pid, err := strconv.Atoi(pidStr) - if err != nil { - continue - } - - process, err := os.FindProcess(pid) - if err != nil { - socketPath := filepath.Join(dir, entry.Name()) - os.Remove(socketPath) - log.Debugf("Removed stale socket: %s", socketPath) - continue - } - - err = process.Signal(syscall.Signal(0)) - if err != nil { - socketPath := filepath.Join(dir, entry.Name()) - os.Remove(socketPath) - log.Debugf("Removed stale socket: %s", socketPath) - } - } + return appPaths.SocketPath() } func InitializeNetworkManager() error { @@ -397,36 +328,21 @@ func InitializeSysUpdateManager() error { return nil } -func handleConnection(conn net.Conn) { - defer conn.Close() - defer func() { - if r := recover(); r != nil { - log.Errorf("handleConnection panic recovered: panic=%v\n%s", r, debug.Stack()) - } - }() +func routeHandler(_ context.Context, conn *models.Conn, req ipc.Request, _ *ipc.Subscriber) { + routeRequestRecovered(conn, models.Request(req)) +} - caps := getCapabilities() - capsData, _ := json.Marshal(caps) - conn.Write(capsData) - conn.Write([]byte("\n")) - scanner := bufio.NewScanner(conn) - scanner.Buffer(make([]byte, bufio.MaxScanTokenSize), 64*1024*1024) // grow up to 64 MB for large clipboard payloads - for scanner.Scan() { - line := scanner.Bytes() - - var req models.Request - if err := json.Unmarshal(line, &req); err != nil { - log.Warnf("handleConnection: Failed to unmarshal JSON: %v, line: %s", err, string(line)) - models.RespondError(conn, 0, "invalid json") - continue - } - - go routeRequestRecovered(conn, req) +func subscribeHandler(_ context.Context, conn *models.Conn, req ipc.Request, _ *ipc.Subscriber) { + switch req.Method { + case "subscribe": + routeRequestRecovered(conn, models.Request(req)) + default: + models.RespondError(conn, req.ID, fmt.Sprintf("unknown method: %s", req.Method)) } } // routeRequestRecovered keeps a panicking handler from taking down the whole daemon -func routeRequestRecovered(conn net.Conn, req models.Request) { +func routeRequestRecovered(conn *models.Conn, req models.Request) { defer func() { if r := recover(); r != nil { log.Errorf("RouteRequest panic recovered: method=%s panic=%v\n%s", req.Method, r, debug.Stack()) @@ -595,7 +511,7 @@ func notifyCapabilityChange() { }) } -func handleSubscribe(conn net.Conn, req models.Request) { +func handleSubscribe(conn *models.Conn, req models.Request) { clientID := fmt.Sprintf("meta-client-%p", conn) dbusClient := dbusClientID @@ -1300,7 +1216,7 @@ func handleSubscribe(conn net.Conn, req models.Request) { }() info := getServerInfo() - if err := json.NewEncoder(conn).Encode(models.Response[ServiceEvent]{ + if err := conn.WriteResponse(models.Response[ServiceEvent]{ ID: req.ID, Result: &ServiceEvent{Service: "server", Data: info}, }); err != nil { @@ -1309,7 +1225,7 @@ func handleSubscribe(conn net.Conn, req models.Request) { } for event := range eventChan { - if err := json.NewEncoder(conn).Encode(models.Response[ServiceEvent]{ + if err := conn.WriteResponse(models.Response[ServiceEvent]{ ID: req.ID, Result: &event, }); err != nil { @@ -1382,8 +1298,39 @@ func cleanupManagers() { } } +type Server struct { + ipc *ipc.Server +} + +func New() *Server { + return &Server{ipc: ipc.NewServer(ipc.Config{ + AppName: appPaths.Name, + APIVersion: APIVersion, + CapabilitiesFunc: func() []string { return getCapabilities().Capabilities }, + MaxLineSize: 64 * 1024 * 1024, // large clipboard payloads + SubscribeHandler: subscribeHandler, + }, routeHandler)} +} + +func (s *Server) Listen() error { return s.ipc.Listen() } + +func (s *Server) SocketPath() string { return s.ipc.SocketPath() } + +func (s *Server) Close() { + s.ipc.Close() +} + func Start(printDocs bool) error { - cleanupStaleSockets() + s := New() + if err := s.Listen(); err != nil { + return err + } + return s.Serve(printDocs) +} + +func (s *Server) Serve(printDocs bool) error { + defer s.ipc.Close() + defer cleanupManagers() // Tailscale manager always starts — reconnects internally via WatchIPNBus. // The capability is only advertised once tailscaled is reachable; the @@ -1393,17 +1340,7 @@ func Start(printDocs bool) error { notifyCapabilityChange() }) - socketPath := GetSocketPath() - os.Remove(socketPath) - - listener, err := net.Listen("unix", socketPath) - if err != nil { - return err - } - defer listener.Close() - defer cleanupManagers() - - log.Infof("DMS API Server listening on: %s", socketPath) + log.Infof("DMS API Server listening on: %s", s.ipc.SocketPath()) log.Infof("API Version: %d", APIVersion) log.Info("Protocol: JSON over Unix socket") log.Info("Request format: {\"id\": , \"method\": \"...\", \"params\": {...}}") @@ -1768,21 +1705,16 @@ func Start(printDocs bool) error { log.Info("") log.Infof("Ready! Capabilities: %v", getCapabilities().Capabilities) - listenerErrChan := make(chan error, 1) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + serveErrChan := make(chan error, 1) go func() { - for { - conn, err := listener.Accept() - if err != nil { - listenerErrChan <- err - return - } - go handleConnection(conn) - } + serveErrChan <- s.ipc.Serve(ctx) }() select { - case err := <-listenerErrChan: + case err := <-serveErrChan: return err case err := <-fatalErrChan: return err diff --git a/core/internal/server/server_test.go b/core/internal/server/server_test.go index 404f62438..e92ad5cf8 100644 --- a/core/internal/server/server_test.go +++ b/core/internal/server/server_test.go @@ -5,7 +5,6 @@ import ( "fmt" "net" "os" - "path/filepath" "testing" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" @@ -14,32 +13,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetSocketDir(t *testing.T) { - tests := []struct { - name string - xdgRuntimeDir string - uid int - expectedSubstr string - }{ - { - name: "uses XDG_RUNTIME_DIR when set", - xdgRuntimeDir: "/run/user/1000", - expectedSubstr: "/run/user/1000", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.xdgRuntimeDir != "" { - t.Setenv("XDG_RUNTIME_DIR", tt.xdgRuntimeDir) - } - - result := getSocketDir() - assert.Contains(t, result, tt.expectedSubstr) - }) - } -} - func TestGetSocketPath(t *testing.T) { path := GetSocketPath() assert.Contains(t, path, "danklinux-") @@ -81,11 +54,11 @@ func (m *mockConn) Close() error { } func TestRespondError(t *testing.T) { - conn := &mockConn{} - models.RespondError(conn, 123, "test error") + mc := &mockConn{} + models.RespondError(models.NewConn(mc), 123, "test error") var resp models.Response[any] - err := json.Unmarshal(conn.written, &resp) + err := json.Unmarshal(mc.written, &resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -94,12 +67,12 @@ func TestRespondError(t *testing.T) { } func TestRespond(t *testing.T) { - conn := &mockConn{} + mc := &mockConn{} result := map[string]string{"foo": "bar"} - models.Respond(conn, 123, result) + models.Respond(models.NewConn(mc), 123, result) var resp models.Response[map[string]string] - err := json.Unmarshal(conn.written, &resp) + err := json.Unmarshal(mc.written, &resp) require.NoError(t, err) assert.Equal(t, 123, resp.ID) @@ -157,24 +130,3 @@ func TestResponse_JSON(t *testing.T) { assert.Nil(t, decoded.Result) }) } - -func TestCleanupStaleSockets(t *testing.T) { - tempDir := t.TempDir() - t.Setenv("XDG_RUNTIME_DIR", tempDir) - - staleSocket := filepath.Join(tempDir, "danklinux-4194305.sock") - err := os.WriteFile(staleSocket, []byte{}, 0o600) - require.NoError(t, err) - - activeSocket := filepath.Join(tempDir, fmt.Sprintf("danklinux-%d.sock", os.Getpid())) - err = os.WriteFile(activeSocket, []byte{}, 0o600) - require.NoError(t, err) - - cleanupStaleSockets() - - _, err = os.Stat(staleSocket) - assert.True(t, os.IsNotExist(err)) - - _, err = os.Stat(activeSocket) - assert.NoError(t, err) -} diff --git a/core/internal/server/sysupdate/handlers.go b/core/internal/server/sysupdate/handlers.go index 42dd0db05..af140c73f 100644 --- a/core/internal/server/sysupdate/handlers.go +++ b/core/internal/server/sysupdate/handlers.go @@ -1,13 +1,11 @@ package sysupdate import ( - "net" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) -func HandleRequest(conn net.Conn, req models.Request, m *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, m *Manager) { switch req.Method { case "sysupdate.getState": models.Respond(conn, req.ID, m.GetState()) @@ -39,7 +37,7 @@ func HandleRequest(conn net.Conn, req models.Request, m *Manager) { } } -func handleUpgrade(conn net.Conn, req models.Request, m *Manager) { +func handleUpgrade(conn *models.Conn, req models.Request, m *Manager) { opts := UpgradeOptions{ IncludeFlatpak: params.BoolOpt(req.Params, "includeFlatpak", true), IncludeAUR: params.BoolOpt(req.Params, "includeAUR", true), diff --git a/core/internal/server/sysupdate/manager.go b/core/internal/server/sysupdate/manager.go index 811168ebf..f5c157ff7 100644 --- a/core/internal/server/sysupdate/manager.go +++ b/core/internal/server/sysupdate/manager.go @@ -12,7 +12,7 @@ import ( "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) const ( diff --git a/core/internal/server/tailscale/handlers.go b/core/internal/server/tailscale/handlers.go index d87a20c7f..533b14d02 100644 --- a/core/internal/server/tailscale/handlers.go +++ b/core/internal/server/tailscale/handlers.go @@ -2,13 +2,12 @@ package tailscale import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) // HandleRequest routes an IPC request to the appropriate handler. -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { switch req.Method { case "tailscale.getStatus": handleGetStatus(conn, req, manager) @@ -27,17 +26,17 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleGetStatus(conn net.Conn, req models.Request, manager *Manager) { +func handleGetStatus(conn *models.Conn, req models.Request, manager *Manager) { state := manager.GetState() models.Respond(conn, req.ID, state) } -func handleRefresh(conn net.Conn, req models.Request, manager *Manager) { +func handleRefresh(conn *models.Conn, req models.Request, manager *Manager) { manager.RefreshState() models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "refreshed"}) } -func handleConnect(conn net.Conn, req models.Request, manager *Manager) { +func handleConnect(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.Connect(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -45,7 +44,7 @@ func handleConnect(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "connected"}) } -func handleDisconnect(conn net.Conn, req models.Request, manager *Manager) { +func handleDisconnect(conn *models.Conn, req models.Request, manager *Manager) { if err := manager.Disconnect(); err != nil { models.RespondError(conn, req.ID, err.Error()) return @@ -53,7 +52,7 @@ func handleDisconnect(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "disconnected"}) } -func handleSetExitNode(conn net.Conn, req models.Request, manager *Manager) { +func handleSetExitNode(conn *models.Conn, req models.Request, manager *Manager) { id := models.GetOr(req, "id", "") if err := manager.SetExitNode(id); err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -62,7 +61,7 @@ func handleSetExitNode(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "exit node updated"}) } -func handleSetAllowLanAccess(conn net.Conn, req models.Request, manager *Manager) { +func handleSetAllowLanAccess(conn *models.Conn, req models.Request, manager *Manager) { enabled := models.GetOr(req, "enabled", false) if err := manager.SetAllowLANAccess(enabled); err != nil { models.RespondError(conn, req.ID, err.Error()) diff --git a/core/internal/server/tailscale/handlers_test.go b/core/internal/server/tailscale/handlers_test.go index ed1a32658..3a42e4f18 100644 --- a/core/internal/server/tailscale/handlers_test.go +++ b/core/internal/server/tailscale/handlers_test.go @@ -47,7 +47,7 @@ func TestHandleGetStatus(t *testing.T) { defer m.Close() buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ID: 1, Method: "tailscale.getStatus"} handleGetStatus(conn, req, m) @@ -66,7 +66,7 @@ func TestHandleRefresh(t *testing.T) { defer m.Close() buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ID: 1, Method: "tailscale.refresh"} handleRefresh(conn, req, m) @@ -98,7 +98,7 @@ func TestHandleActions(t *testing.T) { defer m.Close() buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ID: 1, Method: tc.method, Params: tc.params} HandleRequest(conn, req, m) @@ -125,7 +125,7 @@ func TestHandleAction_BackendError(t *testing.T) { defer m.Close() buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ID: 1, Method: "tailscale.connect"} HandleRequest(conn, req, m) @@ -141,7 +141,7 @@ func TestHandleRequest_UnknownMethod(t *testing.T) { defer m.Close() buf := &bytes.Buffer{} - conn := &mockConn{Buffer: buf} + conn := models.NewConn(&mockConn{Buffer: buf}) req := models.Request{ID: 1, Method: "tailscale.unknownMethod"} HandleRequest(conn, req, m) diff --git a/core/internal/server/tailscale/manager.go b/core/internal/server/tailscale/manager.go index e622df9ab..d6e97a888 100644 --- a/core/internal/server/tailscale/manager.go +++ b/core/internal/server/tailscale/manager.go @@ -7,7 +7,7 @@ import ( "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" "tailscale.com/client/local" "tailscale.com/ipn" "tailscale.com/ipn/ipnstate" @@ -133,7 +133,7 @@ func (m *Manager) watchLoop(ctx context.Context) { break } - if notify.State == nil && notify.NetMap == nil { + if notify.State == nil && notify.NetMap == nil { //nolint:staticcheck // NetMap is deprecated upstream but still the only activity signal on some platforms continue } select { diff --git a/core/internal/server/thememode/handlers.go b/core/internal/server/thememode/handlers.go index 689187565..eb8ee3c4c 100644 --- a/core/internal/server/thememode/handlers.go +++ b/core/internal/server/thememode/handlers.go @@ -1,15 +1,13 @@ package thememode import ( - "encoding/json" "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { if manager == nil { models.RespondError(conn, req.ID, "theme mode manager not initialized") return @@ -37,11 +35,11 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleGetState(conn net.Conn, req models.Request, manager *Manager) { +func handleGetState(conn *models.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, manager.GetState()) } -func handleSetEnabled(conn net.Conn, req models.Request, manager *Manager) { +func handleSetEnabled(conn *models.Conn, req models.Request, manager *Manager) { enabled, err := params.Bool(req.Params, "enabled") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -52,7 +50,7 @@ func handleSetEnabled(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto enabled set"}) } -func handleSetMode(conn net.Conn, req models.Request, manager *Manager) { +func handleSetMode(conn *models.Conn, req models.Request, manager *Manager) { mode, err := params.String(req.Params, "mode") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -68,7 +66,7 @@ func handleSetMode(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto mode set"}) } -func handleSetSchedule(conn net.Conn, req models.Request, manager *Manager) { +func handleSetSchedule(conn *models.Conn, req models.Request, manager *Manager) { startHour, err := params.Int(req.Params, "startHour") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -99,7 +97,7 @@ func handleSetSchedule(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, manager.GetState()) } -func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) { +func handleSetLocation(conn *models.Conn, req models.Request, manager *Manager) { lat, err := params.Float(req.Params, "latitude") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -115,7 +113,7 @@ func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto location set"}) } -func handleSetUseIPLocation(conn net.Conn, req models.Request, manager *Manager) { +func handleSetUseIPLocation(conn *models.Conn, req models.Request, manager *Manager) { use, err := params.Bool(req.Params, "use") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -126,18 +124,18 @@ func handleSetUseIPLocation(conn net.Conn, req models.Request, manager *Manager) models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto IP location set"}) } -func handleTrigger(conn net.Conn, req models.Request, manager *Manager) { +func handleTrigger(conn *models.Conn, req models.Request, manager *Manager) { manager.TriggerUpdate() models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "theme auto update triggered"}) } -func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { +func handleSubscribe(conn *models.Conn, req models.Request, manager *Manager) { clientID := fmt.Sprintf("client-%p", conn) stateChan := manager.Subscribe(clientID) defer manager.Unsubscribe(clientID) initialState := manager.GetState() - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ ID: req.ID, Result: &initialState, }); err != nil { @@ -145,7 +143,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { } for state := range stateChan { - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ Result: &state, }); err != nil { return diff --git a/core/internal/server/thememode/manager.go b/core/internal/server/thememode/manager.go index f7644ad26..855c87e38 100644 --- a/core/internal/server/thememode/manager.go +++ b/core/internal/server/thememode/manager.go @@ -8,7 +8,7 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/wayland" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) const ( diff --git a/core/internal/server/themes/handlers.go b/core/internal/server/themes/handlers.go index b3b308a8e..6f3993844 100644 --- a/core/internal/server/themes/handlers.go +++ b/core/internal/server/themes/handlers.go @@ -2,12 +2,11 @@ package themes import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" ) -func HandleRequest(conn net.Conn, req models.Request) { +func HandleRequest(conn *models.Conn, req models.Request) { switch req.Method { case "themes.list": HandleList(conn, req) diff --git a/core/internal/server/themes/install.go b/core/internal/server/themes/install.go index 26d22bab2..861541d0d 100644 --- a/core/internal/server/themes/install.go +++ b/core/internal/server/themes/install.go @@ -2,13 +2,12 @@ package themes import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/AvengeMedia/DankMaterialShell/core/internal/themes" ) -func HandleInstall(conn net.Conn, req models.Request) { +func HandleInstall(conn *models.Conn, req models.Request) { idOrName, ok := models.Get[string](req, "name") if !ok { models.RespondError(conn, req.ID, "missing or invalid 'name' parameter") diff --git a/core/internal/server/themes/list.go b/core/internal/server/themes/list.go index 9564be1eb..73f3c1174 100644 --- a/core/internal/server/themes/list.go +++ b/core/internal/server/themes/list.go @@ -2,14 +2,13 @@ package themes import ( "fmt" - "net" "strings" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/AvengeMedia/DankMaterialShell/core/internal/themes" ) -func HandleList(conn net.Conn, req models.Request) { +func HandleList(conn *models.Conn, req models.Request) { registry, err := themes.NewRegistry() if err != nil { models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err)) diff --git a/core/internal/server/themes/list_installed.go b/core/internal/server/themes/list_installed.go index 49be81b47..0ad6092f5 100644 --- a/core/internal/server/themes/list_installed.go +++ b/core/internal/server/themes/list_installed.go @@ -2,7 +2,6 @@ package themes import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/AvengeMedia/DankMaterialShell/core/internal/themes" @@ -79,7 +78,7 @@ func addVariantsInfo(info *ThemeInfo, variants *themes.ThemeVariants) { } } -func HandleListInstalled(conn net.Conn, req models.Request) { +func HandleListInstalled(conn *models.Conn, req models.Request) { manager, err := themes.NewManager() if err != nil { models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err)) diff --git a/core/internal/server/themes/search.go b/core/internal/server/themes/search.go index 2823fe0dc..f46618f59 100644 --- a/core/internal/server/themes/search.go +++ b/core/internal/server/themes/search.go @@ -2,13 +2,12 @@ package themes import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/AvengeMedia/DankMaterialShell/core/internal/themes" ) -func HandleSearch(conn net.Conn, req models.Request) { +func HandleSearch(conn *models.Conn, req models.Request) { query, ok := models.Get[string](req, "query") if !ok { models.RespondError(conn, req.ID, "missing or invalid 'query' parameter") diff --git a/core/internal/server/themes/uninstall.go b/core/internal/server/themes/uninstall.go index 9b453e494..72be014ee 100644 --- a/core/internal/server/themes/uninstall.go +++ b/core/internal/server/themes/uninstall.go @@ -2,13 +2,12 @@ package themes import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/AvengeMedia/DankMaterialShell/core/internal/themes" ) -func HandleUninstall(conn net.Conn, req models.Request) { +func HandleUninstall(conn *models.Conn, req models.Request) { idOrName, ok := models.Get[string](req, "name") if !ok { models.RespondError(conn, req.ID, "missing or invalid 'name' parameter") diff --git a/core/internal/server/themes/update.go b/core/internal/server/themes/update.go index fb9f81f65..335cab3dd 100644 --- a/core/internal/server/themes/update.go +++ b/core/internal/server/themes/update.go @@ -2,13 +2,12 @@ package themes import ( "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" "github.com/AvengeMedia/DankMaterialShell/core/internal/themes" ) -func HandleUpdate(conn net.Conn, req models.Request) { +func HandleUpdate(conn *models.Conn, req models.Request) { idOrName, ok := models.Get[string](req, "name") if !ok { models.RespondError(conn, req.ID, "missing or invalid 'name' parameter") diff --git a/core/internal/server/wallpaper/handlers.go b/core/internal/server/wallpaper/handlers.go index f7f4dffaa..226349c5d 100644 --- a/core/internal/server/wallpaper/handlers.go +++ b/core/internal/server/wallpaper/handlers.go @@ -3,13 +3,12 @@ package wallpaper import ( "encoding/json" "fmt" - "net" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { if manager == nil { models.RespondError(conn, req.ID, "wallpaper manager not initialized") return @@ -29,11 +28,11 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleGetState(conn net.Conn, req models.Request, manager *Manager) { +func handleGetState(conn *models.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, manager.GetState()) } -func handleSetConfig(conn net.Conn, req models.Request, manager *Manager) { +func handleSetConfig(conn *models.Conn, req models.Request, manager *Manager) { raw, ok := params.Any(req.Params, "config") if !ok { models.RespondError(conn, req.ID, "missing or invalid 'config' parameter") @@ -56,18 +55,18 @@ func handleSetConfig(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "wallpaper schedule set"}) } -func handleTrigger(conn net.Conn, req models.Request, manager *Manager) { +func handleTrigger(conn *models.Conn, req models.Request, manager *Manager) { manager.ResetSchedule(params.StringOpt(req.Params, "target", "")) models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "wallpaper schedule reset"}) } -func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { +func handleSubscribe(conn *models.Conn, req models.Request, manager *Manager) { clientID := fmt.Sprintf("client-%p", conn) stateChan := manager.Subscribe(clientID) defer manager.Unsubscribe(clientID) initialState := manager.GetState() - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ ID: req.ID, Result: &initialState, }); err != nil { @@ -75,7 +74,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { } for state := range stateChan { - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ Result: &state, }); err != nil { return diff --git a/core/internal/server/wallpaper/manager.go b/core/internal/server/wallpaper/manager.go index d3c2edbe3..e6e17407c 100644 --- a/core/internal/server/wallpaper/manager.go +++ b/core/internal/server/wallpaper/manager.go @@ -8,7 +8,7 @@ import ( "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/loginctl" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) type activeSchedule struct { diff --git a/core/internal/server/wayland/handlers.go b/core/internal/server/wayland/handlers.go index 76b08b5b4..4c7deb12b 100644 --- a/core/internal/server/wayland/handlers.go +++ b/core/internal/server/wayland/handlers.go @@ -1,16 +1,14 @@ package wayland import ( - "encoding/json" "fmt" - "net" "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/server/models" - "github.com/AvengeMedia/DankMaterialShell/core/internal/server/params" + "github.com/AvengeMedia/dankgo/ipc/params" ) -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { if manager == nil { models.RespondError(conn, req.ID, "wayland manager not initialized") return @@ -38,11 +36,11 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleGetState(conn net.Conn, req models.Request, manager *Manager) { +func handleGetState(conn *models.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, manager.GetState()) } -func handleSetTemperature(conn net.Conn, req models.Request, manager *Manager) { +func handleSetTemperature(conn *models.Conn, req models.Request, manager *Manager) { var lowTemp, highTemp int if temp, ok := models.Get[float64](req, "temp"); ok { @@ -71,7 +69,7 @@ func handleSetTemperature(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "temperature set"}) } -func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) { +func handleSetLocation(conn *models.Conn, req models.Request, manager *Manager) { lat, err := params.Float(req.Params, "latitude") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -92,7 +90,7 @@ func handleSetLocation(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "location set"}) } -func handleSetManualTimes(conn net.Conn, req models.Request, manager *Manager) { +func handleSetManualTimes(conn *models.Conn, req models.Request, manager *Manager) { sunriseStr, sunriseOK := models.Get[string](req, "sunrise") sunsetStr, sunsetOK := models.Get[string](req, "sunset") @@ -122,7 +120,7 @@ func handleSetManualTimes(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "manual times set"}) } -func handleSetUseIPLocation(conn net.Conn, req models.Request, manager *Manager) { +func handleSetUseIPLocation(conn *models.Conn, req models.Request, manager *Manager) { use, err := params.Bool(req.Params, "use") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -133,7 +131,7 @@ func handleSetUseIPLocation(conn net.Conn, req models.Request, manager *Manager) models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "IP location preference set"}) } -func handleSetGamma(conn net.Conn, req models.Request, manager *Manager) { +func handleSetGamma(conn *models.Conn, req models.Request, manager *Manager) { gamma, err := params.Float(req.Params, "gamma") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -148,7 +146,7 @@ func handleSetGamma(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "gamma set"}) } -func handleSetEnabled(conn net.Conn, req models.Request, manager *Manager) { +func handleSetEnabled(conn *models.Conn, req models.Request, manager *Manager) { enabled, err := params.Bool(req.Params, "enabled") if err != nil { models.RespondError(conn, req.ID, err.Error()) @@ -159,13 +157,13 @@ func handleSetEnabled(conn net.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: "enabled state set"}) } -func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { +func handleSubscribe(conn *models.Conn, req models.Request, manager *Manager) { clientID := fmt.Sprintf("client-%p", conn) stateChan := manager.Subscribe(clientID) defer manager.Unsubscribe(clientID) initialState := manager.GetState() - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ ID: req.ID, Result: &initialState, }); err != nil { @@ -173,7 +171,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { } for state := range stateChan { - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ Result: &state, }); err != nil { return diff --git a/core/internal/server/wayland/types.go b/core/internal/server/wayland/types.go index 863eeb8c3..5bae66b9f 100644 --- a/core/internal/server/wayland/types.go +++ b/core/internal/server/wayland/types.go @@ -9,7 +9,7 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs" "github.com/AvengeMedia/DankMaterialShell/core/internal/geolocation" wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" "github.com/godbus/dbus/v5" ) diff --git a/core/internal/server/wlroutput/handlers.go b/core/internal/server/wlroutput/handlers.go index c885e1375..1b5bd2b72 100644 --- a/core/internal/server/wlroutput/handlers.go +++ b/core/internal/server/wlroutput/handlers.go @@ -3,7 +3,6 @@ package wlroutput import ( "encoding/json" "fmt" - "net" "time" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" @@ -31,7 +30,7 @@ type ConfigurationRequest struct { Test bool `json:"test"` } -func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { +func HandleRequest(conn *models.Conn, req models.Request, manager *Manager) { if manager == nil { models.RespondError(conn, req.ID, "wlroutput manager not initialized") return @@ -51,11 +50,11 @@ func HandleRequest(conn net.Conn, req models.Request, manager *Manager) { } } -func handleGetState(conn net.Conn, req models.Request, manager *Manager) { +func handleGetState(conn *models.Conn, req models.Request, manager *Manager) { models.Respond(conn, req.ID, manager.GetState()) } -func handleApplyConfiguration(conn net.Conn, req models.Request, manager *Manager, test bool) { +func handleApplyConfiguration(conn *models.Conn, req models.Request, manager *Manager, test bool) { headsParam, ok := models.Get[any](req, "heads") if !ok { models.RespondError(conn, req.ID, "missing 'heads' parameter") @@ -86,13 +85,13 @@ func handleApplyConfiguration(conn net.Conn, req models.Request, manager *Manage models.Respond(conn, req.ID, models.SuccessResult{Success: true, Message: msg}) } -func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { +func handleSubscribe(conn *models.Conn, req models.Request, manager *Manager) { clientID := fmt.Sprintf("client-%p", conn) stateChan := manager.Subscribe(clientID) defer manager.Unsubscribe(clientID) initialState := manager.GetState() - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ ID: req.ID, Result: &initialState, }); err != nil { @@ -100,7 +99,7 @@ func handleSubscribe(conn net.Conn, req models.Request, manager *Manager) { } for state := range stateChan { - if err := json.NewEncoder(conn).Encode(models.Response[State]{ + if err := conn.WriteResponse(models.Response[State]{ Result: &state, }); err != nil { return diff --git a/core/internal/server/wlroutput/types.go b/core/internal/server/wlroutput/types.go index 9c79b1a8e..0ee030547 100644 --- a/core/internal/server/wlroutput/types.go +++ b/core/internal/server/wlroutput/types.go @@ -5,7 +5,7 @@ import ( "github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_output_management" wlclient "github.com/AvengeMedia/DankMaterialShell/core/pkg/go-wayland/wayland/client" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) type OutputMode struct { diff --git a/core/internal/shellembed/embed_stub.go b/core/internal/shellembed/embed_stub.go new file mode 100644 index 000000000..f8dd0402c --- /dev/null +++ b/core/internal/shellembed/embed_stub.go @@ -0,0 +1,9 @@ +//go:build !withshell + +package shellembed + +import "embed" + +// Untagged builds (tests, vet, plain `go build`) carry no embedded UI; +// config resolution then requires an explicit shell dir. +var distFS embed.FS diff --git a/core/internal/shellembed/embed_withshell.go b/core/internal/shellembed/embed_withshell.go new file mode 100644 index 000000000..17c8381dc --- /dev/null +++ b/core/internal/shellembed/embed_withshell.go @@ -0,0 +1,12 @@ +//go:build withshell + +package shellembed + +import "embed" + +// dist is populated from the repo's quickshell/ tree by `make sync-shell` +// before any tagged build; it is never committed. all: keeps the .dankrev +// revision key, which go:embed would otherwise skip as a dotfile. +// +//go:embed all:dist +var distFS embed.FS diff --git a/core/internal/shellembed/shellembed.go b/core/internal/shellembed/shellembed.go new file mode 100644 index 000000000..f4a10796b --- /dev/null +++ b/core/internal/shellembed/shellembed.go @@ -0,0 +1,36 @@ +// Package shellembed carries the quickshell UI inside the dms binary and +// materializes it at runtime via dankgo/shellapp/shellfs, since quickshell +// needs a real filesystem path. Customization goes through -c / +// DMS_SHELL_DIR instead of editing the extraction. +package shellembed + +import ( + "io/fs" + "path" + + "github.com/AvengeMedia/dankgo/shellapp/shellfs" +) + +const ( + distRoot = "dist" + shellEntry = "shell.qml" +) + +// Available reports whether this binary was built with the embedded UI +// (the withshell build tag). +func Available() bool { + info, err := fs.Stat(distFS, path.Join(distRoot, shellEntry)) + return err == nil && !info.IsDir() +} + +func Extract(baseDir string) (string, error) { + sub, err := fs.Sub(distFS, distRoot) + if err != nil { + return "", err + } + return shellfs.Extract(sub, baseDir) +} + +func Prune(baseDir, keep string) { + shellfs.Prune(baseDir, keep) +} diff --git a/core/internal/shellembed/shellembed_bench_test.go b/core/internal/shellembed/shellembed_bench_test.go new file mode 100644 index 000000000..39e8d2f04 --- /dev/null +++ b/core/internal/shellembed/shellembed_bench_test.go @@ -0,0 +1,52 @@ +package shellembed + +import ( + "os" + "path/filepath" + "testing" +) + +func benchDir(b *testing.B) string { + b.Helper() + dir, err := os.MkdirTemp("", "shellembed-bench-") + if err != nil { + b.Fatal(err) + } + b.Cleanup(func() { + filepath.WalkDir(dir, func(p string, d os.DirEntry, err error) error { + if err == nil && d.IsDir() { + os.Chmod(p, 0o755) + } + return nil + }) + os.RemoveAll(dir) + }) + return dir +} + +func BenchmarkExtractWarm(b *testing.B) { + if !Available() { + b.Skip("no embedded UI in this build") + } + base := benchDir(b) + if _, err := Extract(base); err != nil { + b.Fatal(err) + } + b.ResetTimer() + for b.Loop() { + if _, err := Extract(base); err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkExtractCold(b *testing.B) { + if !Available() { + b.Skip("no embedded UI in this build") + } + for b.Loop() { + if _, err := Extract(benchDir(b)); err != nil { + b.Fatal(err) + } + } +} diff --git a/core/internal/shellembed/shellembed_test.go b/core/internal/shellembed/shellembed_test.go new file mode 100644 index 000000000..f0d78dfe8 --- /dev/null +++ b/core/internal/shellembed/shellembed_test.go @@ -0,0 +1,9 @@ +package shellembed + +import "testing" + +func TestAvailableFalseWithoutTag(t *testing.T) { + if Available() { + t.Fatal("Available() should be false in untagged test builds") + } +} diff --git a/core/internal/utils/paths.go b/core/internal/utils/paths.go index 31662d33d..05cf0fa0d 100644 --- a/core/internal/utils/paths.go +++ b/core/internal/utils/paths.go @@ -4,39 +4,19 @@ import ( "os" "path/filepath" "strings" + + "github.com/AvengeMedia/dankgo/paths" ) -func XDGStateHome() string { - if dir := os.Getenv("XDG_STATE_HOME"); dir != "" { - return dir - } - home, _ := os.UserHomeDir() - return filepath.Join(home, ".local", "state") -} +func XDGStateHome() string { return paths.XDGStateHome() } -func XDGDataHome() string { - if dir := os.Getenv("XDG_DATA_HOME"); dir != "" { - return dir - } - home, _ := os.UserHomeDir() - return filepath.Join(home, ".local", "share") -} +func XDGDataHome() string { return paths.XDGDataHome() } -func XDGCacheHome() string { - if dir, err := os.UserCacheDir(); err == nil { - return dir - } - home, _ := os.UserHomeDir() - return filepath.Join(home, ".cache") -} +func XDGCacheHome() string { return paths.XDGCacheHome() } -func XDGConfigHome() string { - if dir, err := os.UserConfigDir(); err == nil { - return dir - } - home, _ := os.UserHomeDir() - return filepath.Join(home, ".config") -} +func XDGConfigHome() string { return paths.XDGConfigHome() } + +func ExpandPath(path string) (string, error) { return paths.ExpandPath(path) } func XDGPicturesDir() string { if dir := os.Getenv("XDG_PICTURES_DIR"); dir != "" { @@ -83,18 +63,3 @@ func EmacsConfigDir() string { return "" } - -func ExpandPath(path string) (string, error) { - expanded := os.ExpandEnv(path) - expanded = filepath.Clean(expanded) - - if strings.HasPrefix(expanded, "~") { - home, err := os.UserHomeDir() - if err != nil { - return "", err - } - expanded = filepath.Join(home, expanded[1:]) - } - - return expanded, nil -} diff --git a/core/pkg/dbusutil/variant.go b/core/pkg/dbusutil/variant.go deleted file mode 100644 index 4080b6641..000000000 --- a/core/pkg/dbusutil/variant.go +++ /dev/null @@ -1,95 +0,0 @@ -package dbusutil - -import "github.com/godbus/dbus/v5" - -func As[T any](v dbus.Variant) (T, bool) { - val, ok := v.Value().(T) - return val, ok -} - -func AsOr[T any](v dbus.Variant, def T) T { - if val, ok := v.Value().(T); ok { - return val - } - return def -} - -func Get[T any](m map[string]dbus.Variant, key string) (T, bool) { - v, ok := m[key] - if !ok { - var zero T - return zero, false - } - return As[T](v) -} - -func GetOr[T any](m map[string]dbus.Variant, key string, def T) T { - v, ok := m[key] - if !ok { - return def - } - return AsOr(v, def) -} - -func Normalize(v any) any { - switch val := v.(type) { - case dbus.Variant: - return Normalize(val.Value()) - case dbus.ObjectPath: - return string(val) - case []dbus.ObjectPath: - result := make([]string, len(val)) - for i, p := range val { - result[i] = string(p) - } - return result - case map[string]dbus.Variant: - result := make(map[string]any) - for k, vv := range val { - result[k] = Normalize(vv.Value()) - } - return result - case map[string]any: - result := make(map[string]any) - for k, vv := range val { - result[k] = Normalize(vv) - } - return result - case map[dbus.ObjectPath]map[string]map[string]dbus.Variant: - result := make(map[string]any) - for path, ifaces := range val { - ifaceMap := make(map[string]any) - for ifaceName, props := range ifaces { - propMap := make(map[string]any) - for propName, propVal := range props { - propMap[propName] = Normalize(propVal.Value()) - } - ifaceMap[ifaceName] = propMap - } - result[string(path)] = ifaceMap - } - return result - case []any: - result := make([]any, len(val)) - for i, item := range val { - result[i] = Normalize(item) - } - return result - case []dbus.Variant: - result := make([]any, len(val)) - for i, item := range val { - result[i] = Normalize(item.Value()) - } - return result - default: - return v - } -} - -func NormalizeSlice(values []any) []any { - result := make([]any, len(values)) - for i, v := range values { - result[i] = Normalize(v) - } - return result -} diff --git a/core/pkg/dbusutil/variant_test.go b/core/pkg/dbusutil/variant_test.go deleted file mode 100644 index a927e54be..000000000 --- a/core/pkg/dbusutil/variant_test.go +++ /dev/null @@ -1,155 +0,0 @@ -package dbusutil - -import ( - "testing" - - "github.com/godbus/dbus/v5" - "github.com/stretchr/testify/assert" -) - -func TestAs(t *testing.T) { - t.Run("string", func(t *testing.T) { - v := dbus.MakeVariant("hello") - val, ok := As[string](v) - assert.True(t, ok) - assert.Equal(t, "hello", val) - }) - - t.Run("bool", func(t *testing.T) { - v := dbus.MakeVariant(true) - val, ok := As[bool](v) - assert.True(t, ok) - assert.True(t, val) - }) - - t.Run("int32", func(t *testing.T) { - v := dbus.MakeVariant(int32(42)) - val, ok := As[int32](v) - assert.True(t, ok) - assert.Equal(t, int32(42), val) - }) - - t.Run("wrong type", func(t *testing.T) { - v := dbus.MakeVariant("hello") - _, ok := As[int](v) - assert.False(t, ok) - }) -} - -func TestAsOr(t *testing.T) { - t.Run("exists", func(t *testing.T) { - v := dbus.MakeVariant("hello") - val := AsOr(v, "default") - assert.Equal(t, "hello", val) - }) - - t.Run("wrong type uses default", func(t *testing.T) { - v := dbus.MakeVariant(123) - val := AsOr(v, "default") - assert.Equal(t, "default", val) - }) -} - -func TestGet(t *testing.T) { - m := map[string]dbus.Variant{ - "name": dbus.MakeVariant("test"), - "enabled": dbus.MakeVariant(true), - "count": dbus.MakeVariant(int32(5)), - } - - t.Run("exists", func(t *testing.T) { - val, ok := Get[string](m, "name") - assert.True(t, ok) - assert.Equal(t, "test", val) - }) - - t.Run("missing key", func(t *testing.T) { - _, ok := Get[string](m, "missing") - assert.False(t, ok) - }) - - t.Run("wrong type", func(t *testing.T) { - _, ok := Get[int](m, "name") - assert.False(t, ok) - }) -} - -func TestGetOr(t *testing.T) { - m := map[string]dbus.Variant{ - "name": dbus.MakeVariant("test"), - } - - t.Run("exists", func(t *testing.T) { - val := GetOr(m, "name", "default") - assert.Equal(t, "test", val) - }) - - t.Run("missing uses default", func(t *testing.T) { - val := GetOr(m, "missing", "default") - assert.Equal(t, "default", val) - }) - - t.Run("wrong type uses default", func(t *testing.T) { - val := GetOr(m, "name", 42) - assert.Equal(t, 42, val) - }) -} - -func TestNormalize(t *testing.T) { - t.Run("variant unwrap", func(t *testing.T) { - v := dbus.MakeVariant("hello") - result := Normalize(v) - assert.Equal(t, "hello", result) - }) - - t.Run("nested variant", func(t *testing.T) { - v := dbus.MakeVariant(dbus.MakeVariant("nested")) - result := Normalize(v) - assert.Equal(t, "nested", result) - }) - - t.Run("object path", func(t *testing.T) { - v := dbus.ObjectPath("/org/test") - result := Normalize(v) - assert.Equal(t, "/org/test", result) - }) - - t.Run("object path slice", func(t *testing.T) { - v := []dbus.ObjectPath{"/org/a", "/org/b"} - result := Normalize(v) - assert.Equal(t, []string{"/org/a", "/org/b"}, result) - }) - - t.Run("variant map", func(t *testing.T) { - v := map[string]dbus.Variant{ - "key": dbus.MakeVariant("value"), - } - result := Normalize(v) - expected := map[string]any{"key": "value"} - assert.Equal(t, expected, result) - }) - - t.Run("any slice", func(t *testing.T) { - v := []any{dbus.MakeVariant("a"), dbus.ObjectPath("/b")} - result := Normalize(v) - expected := []any{"a", "/b"} - assert.Equal(t, expected, result) - }) - - t.Run("passthrough primitives", func(t *testing.T) { - assert.Equal(t, "hello", Normalize("hello")) - assert.Equal(t, 42, Normalize(42)) - assert.Equal(t, true, Normalize(true)) - }) -} - -func TestNormalizeSlice(t *testing.T) { - input := []any{ - dbus.MakeVariant("a"), - dbus.ObjectPath("/b"), - "c", - } - result := NormalizeSlice(input) - expected := []any{"a", "/b", "c"} - assert.Equal(t, expected, result) -} diff --git a/core/pkg/go-wayland/wayland/client/context.go b/core/pkg/go-wayland/wayland/client/context.go index 7e48f6aaa..220f5aebf 100644 --- a/core/pkg/go-wayland/wayland/client/context.go +++ b/core/pkg/go-wayland/wayland/client/context.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/AvengeMedia/DankMaterialShell/core/pkg/syncmap" + "github.com/AvengeMedia/dankgo/syncmap" ) type Context struct { diff --git a/core/pkg/syncmap/LICENSE b/core/pkg/syncmap/LICENSE deleted file mode 100644 index edba27558..000000000 --- a/core/pkg/syncmap/LICENSE +++ /dev/null @@ -1,28 +0,0 @@ -Copyright 2009 The Go Authors. -Copyright 2024 Zachary Olstein. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google LLC nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/core/pkg/syncmap/syncmap.go b/core/pkg/syncmap/syncmap.go deleted file mode 100644 index 27d9458ad..000000000 --- a/core/pkg/syncmap/syncmap.go +++ /dev/null @@ -1,537 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package syncmap - -import ( - "sync" - "sync/atomic" - "unsafe" -) - -// Map is like a Go map[K]V but is safe for concurrent use -// by multiple goroutines without additional locking or coordination. -// Loads, stores, and deletes run in amortized constant time. -// -// The Map type is specialized. Most code should use a plain Go map instead, -// with separate locking or coordination, for better type safety and to make it -// easier to maintain other invariants along with the map content. -// -// The Map type is optimized for two common use cases: (1) when the entry for a given -// key is only ever written once but read many times, as in caches that only grow, -// or (2) when multiple goroutines read, write, and overwrite entries for disjoint -// sets of keys. In these two cases, use of a Map may significantly reduce lock -// contention compared to a Go map paired with a separate [Mutex] or [RWMutex]. -// -// The zero Map is empty and ready for use. A Map must not be copied after first use. -// -// In the terminology of [the Go memory model], Map arranges that a write operation -// “synchronizes before” any read operation that observes the effect of the write, where -// read and write operations are defined as follows. -// [Map.Load], [Map.LoadAndDelete], [Map.LoadOrStore], and [Map.Swap] are read operations; -// [Map.Delete], [Map.LoadAndDelete], [Map.Store], and [Map.Swap] are write operations; -// [Map.LoadOrStore] is a write operation when it returns loaded set to false. -// -// [the Go memory model]: https://go.dev/ref/mem -type Map[K comparable, V any] struct { - mu sync.Mutex - - // read contains the portion of the map's contents that are safe for - // concurrent access (with or without mu held). - // - // The read field itself is always safe to load, but must only be stored with - // mu held. - // - // Entries stored in read may be updated concurrently without mu, but updating - // a previously-expunged entry requires that the entry be copied to the dirty - // map and unexpunged with mu held. - read atomic.Pointer[readOnly[K, V]] - - // dirty contains the portion of the map's contents that require mu to be - // held. To ensure that the dirty map can be promoted to the read map quickly, - // it also includes all of the non-expunged entries in the read map. - // - // Expunged entries are not stored in the dirty map. An expunged entry in the - // clean map must be unexpunged and added to the dirty map before a new value - // can be stored to it. - // - // If the dirty map is nil, the next write to the map will initialize it by - // making a shallow copy of the clean map, omitting stale entries. - dirty map[K]*entry[V] - - // misses counts the number of loads since the read map was last updated that - // needed to lock mu to determine whether the key was present. - // - // Once enough misses have occurred to cover the cost of copying the dirty - // map, the dirty map will be promoted to the read map (in the unamended - // state) and the next store to the map will make a new dirty copy. - misses int -} - -// readOnly is an immutable struct stored atomically in the Map.read field. -type readOnly[K comparable, V any] struct { - m map[K]*entry[V] - amended bool // true if the dirty map contains some key not in m. -} - -// expunged is an arbitrary pointer that marks entries which have been deleted -// from the dirty map. -// Because the same expunged pointer is used regardless of the Map's value type, -// value pointers read from the map must be compared against expunged BEFORE -// casting the pointer to *V. -var expunged = unsafe.Pointer(new(int)) - -// An entry is a slot in the map corresponding to a particular key. -type entry[V any] struct { - // p points to the value stored for the entry. - // - // If p == nil, the entry has been deleted, and either m.dirty == nil or - // m.dirty[key] is e. - // - // If p == expunged, the entry has been deleted, m.dirty != nil, and the entry - // is missing from m.dirty. - // - // Otherwise, the entry is valid and recorded in m.read.m[key] and, if m.dirty - // != nil, in m.dirty[key]. - // - // If p != expunged, it is always safe to cast it to (*V). - // - // An entry can be deleted by atomic replacement with nil: when m.dirty is - // next created, it will atomically replace nil with expunged and leave - // m.dirty[key] unset. - // - // An entry's associated value can be updated by atomic replacement, provided - // p != expunged. If p == expunged, an entry's associated value can be updated - // only after first setting m.dirty[key] = e so that lookups using the dirty - // map find the entry. - p unsafe.Pointer -} - -func newEntry[V any](i V) *entry[V] { - e := &entry[V]{} - atomic.StorePointer(&e.p, unsafe.Pointer(&i)) - return e -} - -func (m *Map[K, V]) loadReadOnly() readOnly[K, V] { - if p := m.read.Load(); p != nil { - return *p - } - return readOnly[K, V]{} -} - -// Load returns the value stored in the map for a key, or nil if no -// value is present. -// The ok result indicates whether value was found in the map. -func (m *Map[K, V]) Load(key K) (value V, ok bool) { - read := m.loadReadOnly() - e, ok := read.m[key] - if !ok && read.amended { - m.mu.Lock() - // Avoid reporting a spurious miss if m.dirty got promoted while we were - // blocked on m.mu. (If further loads of the same key will not miss, it's - // not worth copying the dirty map for this key.) - read = m.loadReadOnly() - e, ok = read.m[key] - if !ok && read.amended { - e, ok = m.dirty[key] - // Regardless of whether the entry was present, record a miss: this key - // will take the slow path until the dirty map is promoted to the read - // map. - m.missLocked() - } - m.mu.Unlock() - } - if !ok { - return value, false - } - return e.load() -} - -func (e *entry[V]) load() (value V, ok bool) { - p := atomic.LoadPointer(&e.p) - if p == nil || p == expunged { - return value, false - } - return *(*V)(p), true -} - -// Store sets the value for a key. -func (m *Map[K, V]) Store(key K, value V) { - _, _ = m.Swap(key, value) -} - -// unexpungeLocked ensures that the entry is not marked as expunged. -// -// If the entry was previously expunged, it must be added to the dirty map -// before m.mu is unlocked. -func (e *entry[V]) unexpungeLocked() (wasExpunged bool) { - return atomic.CompareAndSwapPointer(&e.p, expunged, nil) -} - -// swapLocked unconditionally swaps a value into the entry. -// -// The entry must be known not to be expunged. -func (e *entry[V]) swapLocked(i *V) *V { - return (*V)(atomic.SwapPointer(&e.p, unsafe.Pointer(i))) -} - -// LoadOrStore returns the existing value for the key if present. -// Otherwise, it stores and returns the given value. -// The loaded result is true if the value was loaded, false if stored. -func (m *Map[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) { - // Avoid locking if it's a clean hit. - read := m.loadReadOnly() - if e, ok := read.m[key]; ok { - actual, loaded, ok := e.tryLoadOrStore(value) - if ok { - return actual, loaded - } - } - - m.mu.Lock() - read = m.loadReadOnly() - if e, ok := read.m[key]; ok { - if e.unexpungeLocked() { - m.dirty[key] = e - } - actual, loaded, _ = e.tryLoadOrStore(value) - } else if e, ok := m.dirty[key]; ok { - actual, loaded, _ = e.tryLoadOrStore(value) - m.missLocked() - } else { - if !read.amended { - // We're adding the first new key to the dirty map. - // Make sure it is allocated and mark the read-only map as incomplete. - m.dirtyLocked() - m.read.Store(&readOnly[K, V]{m: read.m, amended: true}) - } - m.dirty[key] = newEntry(value) - actual, loaded = value, false - } - m.mu.Unlock() - - return actual, loaded -} - -// tryLoadOrStore atomically loads or stores a value if the entry is not -// expunged. -// -// If the entry is expunged, tryLoadOrStore leaves the entry unchanged and -// returns with ok==false. -func (e *entry[V]) tryLoadOrStore(i V) (actual V, loaded, ok bool) { - ptr := atomic.LoadPointer(&e.p) - if ptr == expunged { - return actual, false, false - } - p := (*V)(ptr) - if p != nil { - return *p, true, true - } - - // Copy the interface after the first load to make this method more amenable - // to escape analysis: if we hit the "load" path or the entry is expunged, we - // shouldn't bother heap-allocating. - ic := i - for { - if atomic.CompareAndSwapPointer(&e.p, nil, unsafe.Pointer(&ic)) { - return i, false, true - } - ptr = atomic.LoadPointer(&e.p) - if ptr == expunged { - return actual, false, false - } - p = (*V)(ptr) - if p != nil { - return *p, true, true - } - } -} - -// LoadAndDelete deletes the value for a key, returning the previous value if any. -// The loaded result reports whether the key was present. -func (m *Map[K, V]) LoadAndDelete(key K) (value V, loaded bool) { - read := m.loadReadOnly() - e, ok := read.m[key] - if !ok && read.amended { - m.mu.Lock() - read = m.loadReadOnly() - e, ok = read.m[key] - if !ok && read.amended { - e, ok = m.dirty[key] - delete(m.dirty, key) - // Regardless of whether the entry was present, record a miss: this key - // will take the slow path until the dirty map is promoted to the read - // map. - m.missLocked() - } - m.mu.Unlock() - } - if ok { - return e.delete() - } - return value, false -} - -// Delete deletes the value for a key. -func (m *Map[K, V]) Delete(key K) { - m.LoadAndDelete(key) -} - -func (e *entry[V]) delete() (value V, ok bool) { - for { - p := atomic.LoadPointer(&e.p) - if p == nil || p == expunged { - return value, false - } - if atomic.CompareAndSwapPointer(&e.p, p, nil) { - return *(*V)(p), true - } - } -} - -// trySwap swaps a value if the entry has not been expunged. -// -// If the entry is expunged, trySwap returns false and leaves the entry -// unchanged. -func (e *entry[V]) trySwap(i *V) (*V, bool) { - for { - p := atomic.LoadPointer(&e.p) - if p == expunged { - return nil, false - } - if atomic.CompareAndSwapPointer(&e.p, p, unsafe.Pointer(i)) { - return (*V)(p), true - } - } -} - -// Swap swaps the value for a key and returns the previous value if any. -// The loaded result reports whether the key was present. -func (m *Map[K, V]) Swap(key K, value V) (previous V, loaded bool) { - read := m.loadReadOnly() - if e, ok := read.m[key]; ok { - if v, ok := e.trySwap(&value); ok { - if v == nil { - return previous, false - } - return *v, true - } - } - - m.mu.Lock() - read = m.loadReadOnly() - if e, ok := read.m[key]; ok { - if e.unexpungeLocked() { - // The entry was previously expunged, which implies that there is a - // non-nil dirty map and this entry is not in it. - m.dirty[key] = e - } - if v := e.swapLocked(&value); v != nil { - loaded = true - previous = *v - } - } else if e, ok := m.dirty[key]; ok { - if v := e.swapLocked(&value); v != nil { - loaded = true - previous = *v - } - } else { - if !read.amended { - // We're adding the first new key to the dirty map. - // Make sure it is allocated and mark the read-only map as incomplete. - m.dirtyLocked() - m.read.Store(&readOnly[K, V]{m: read.m, amended: true}) - } - m.dirty[key] = newEntry(value) - } - m.mu.Unlock() - return previous, loaded -} - -// Range calls f sequentially for each key and value present in the map. -// If f returns false, range stops the iteration. -// -// Range does not necessarily correspond to any consistent snapshot of the Map's -// contents: no key will be visited more than once, but if the value for any key -// is stored or deleted concurrently (including by f), Range may reflect any -// mapping for that key from any point during the Range call. Range does not -// block other methods on the receiver; even f itself may call any method on m. -// -// Range may be O(N) with the number of elements in the map even if f returns -// false after a constant number of calls. -func (m *Map[K, V]) Range(f func(key K, value V) bool) { - // We need to be able to iterate over all of the keys that were already - // present at the start of the call to Range. - // If read.amended is false, then read.m satisfies that property without - // requiring us to hold m.mu for a long time. - read := m.loadReadOnly() - if read.amended { - // m.dirty contains keys not in read.m. Fortunately, Range is already O(N) - // (assuming the caller does not break out early), so a call to Range - // amortizes an entire copy of the map: we can promote the dirty copy - // immediately! - m.mu.Lock() - read = m.loadReadOnly() - if read.amended { - read = readOnly[K, V]{m: m.dirty} - copyRead := read - m.read.Store(©Read) - m.dirty = nil - m.misses = 0 - } - m.mu.Unlock() - } - - for k, e := range read.m { - v, ok := e.load() - if !ok { - continue - } - if !f(k, v) { - break - } - } -} - -// CompareAndSwap swaps the old and new values for key -// if the value stored in the map is equal to old. -// The old value must be of a comparable type. -func CompareAndSwap[K comparable, V comparable](m *Map[K, V], key K, old, new V) (swapped bool) { - read := m.loadReadOnly() - if e, ok := read.m[key]; ok { - return tryCompareAndSwap(e, old, new) - } else if !read.amended { - return false // No existing value for key. - } - - m.mu.Lock() - defer m.mu.Unlock() - read = m.loadReadOnly() - swapped = false - if e, ok := read.m[key]; ok { - swapped = tryCompareAndSwap(e, old, new) - } else if e, ok := m.dirty[key]; ok { - swapped = tryCompareAndSwap(e, old, new) - // We needed to lock mu in order to load the entry for key, - // and the operation didn't change the set of keys in the map - // (so it would be made more efficient by promoting the dirty - // map to read-only). - // Count it as a miss so that we will eventually switch to the - // more efficient steady state. - m.missLocked() - } - return swapped -} - -// CompareAndDelete deletes the entry for key if its value is equal to old. -// The old value must be of a comparable type. -// -// If there is no current value for key in the map, CompareAndDelete -// returns false (even if the old value is the zero value of V). -func CompareAndDelete[K comparable, V comparable](m *Map[K, V], key K, old V) (deleted bool) { - read := m.loadReadOnly() - e, ok := read.m[key] - if !ok && read.amended { - m.mu.Lock() - read = m.loadReadOnly() - e, ok = read.m[key] - if !ok && read.amended { - e, ok = m.dirty[key] - // Don't delete key from m.dirty: we still need to do the “compare” part - // of the operation. The entry will eventually be expunged when the - // dirty map is promoted to the read map. - // - // Regardless of whether the entry was present, record a miss: this key - // will take the slow path until the dirty map is promoted to the read - // map. - m.missLocked() - } - m.mu.Unlock() - } - for ok { - ptr := atomic.LoadPointer(&e.p) - if ptr == nil || ptr == expunged { - return false - } - p := (*V)(ptr) - if *p != old { - return false - } - if atomic.CompareAndSwapPointer(&e.p, ptr, nil) { - return true - } - } - return false -} - -// tryCompareAndSwap compare the entry with the given old value and swaps -// it with a new value if the entry is equal to the old value, and the entry -// has not been expunged. -// -// If the entry is expunged, tryCompareAndSwap returns false and leaves -// the entry unchanged. -func tryCompareAndSwap[V comparable](e *entry[V], old, new V) bool { - ptr := atomic.LoadPointer(&e.p) - if ptr == nil || ptr == expunged { - return false - } - p := (*V)(ptr) - if *p != old { - return false - } - - // Copy the interface after the first load to make this method more amenable - // to escape analysis: if the comparison fails from the start, we shouldn't - // bother heap-allocating an interface value to store. - nc := new - for { - if atomic.CompareAndSwapPointer(&e.p, ptr, unsafe.Pointer(&nc)) { - return true - } - ptr = atomic.LoadPointer(&e.p) - if ptr == nil || ptr == expunged { - return false - } - p = (*V)(ptr) - if *p != old { - return false - } - } -} - -func (m *Map[K, V]) missLocked() { - m.misses++ - if m.misses < len(m.dirty) { - return - } - m.read.Store(&readOnly[K, V]{m: m.dirty}) - m.dirty = nil - m.misses = 0 -} - -func (m *Map[K, V]) dirtyLocked() { - if m.dirty != nil { - return - } - - read := m.loadReadOnly() - m.dirty = make(map[K]*entry[V], len(read.m)) - for k, e := range read.m { - if !e.tryExpungeLocked() { - m.dirty[k] = e - } - } -} - -func (e *entry[V]) tryExpungeLocked() (isExpunged bool) { - p := atomic.LoadPointer(&e.p) - for p == nil { - if atomic.CompareAndSwapPointer(&e.p, nil, expunged) { - return true - } - p = atomic.LoadPointer(&e.p) - } - return p == expunged -} diff --git a/distro/debian/dms-git/debian/rules b/distro/debian/dms-git/debian/rules index 6a2b12c51..b3f6f5f94 100755 --- a/distro/debian/dms-git/debian/rules +++ b/distro/debian/dms-git/debian/rules @@ -68,24 +68,11 @@ override_dh_auto_build: override_dh_auto_install: install -Dm755 dms debian/dms-git/usr/bin/dms - mkdir -p debian/dms-git/usr/share/quickshell/dms debian/dms-git/usr/lib/systemd/user - if [ -d quickshell ]; then \ - cp -rL quickshell/* debian/dms-git/usr/share/quickshell/dms/; \ - install -Dm644 assets/systemd/dms.service debian/dms-git/usr/lib/systemd/user/dms.service; \ - install -Dm644 assets/dms-open.desktop debian/dms-git/usr/share/applications/dms-open.desktop; \ - install -Dm644 assets/com.danklinux.dms.desktop debian/dms-git/usr/share/applications/com.danklinux.dms.desktop; \ - install -Dm644 assets/com.danklinux.dms.notepad.desktop debian/dms-git/usr/share/applications/com.danklinux.dms.notepad.desktop; \ - install -Dm644 assets/danklogo.svg debian/dms-git/usr/share/icons/hicolor/scalable/apps/danklogo.svg; \ - else \ - echo "ERROR: quickshell directory not found!" && \ - echo "Contents of current directory:" && ls -la && \ - exit 1; \ - fi - - rm -rf debian/dms-git/usr/share/quickshell/dms/core \ - debian/dms-git/usr/share/quickshell/dms/distro - - echo "$(UPSTREAM_VERSION)" > debian/dms-git/usr/share/quickshell/dms/VERSION + install -Dm644 assets/systemd/dms.service debian/dms-git/usr/lib/systemd/user/dms.service + install -Dm644 assets/dms-open.desktop debian/dms-git/usr/share/applications/dms-open.desktop + install -Dm644 assets/com.danklinux.dms.desktop debian/dms-git/usr/share/applications/com.danklinux.dms.desktop + install -Dm644 assets/com.danklinux.dms.notepad.desktop debian/dms-git/usr/share/applications/com.danklinux.dms.notepad.desktop + install -Dm644 assets/danklogo.svg debian/dms-git/usr/share/icons/hicolor/scalable/apps/danklogo.svg override_dh_auto_clean: # Clean up build artifacts diff --git a/distro/fedora/dms-git.spec b/distro/fedora/dms-git.spec index 32a514cf6..837a16189 100644 --- a/distro/fedora/dms-git.spec +++ b/distro/fedora/dms-git.spec @@ -3,7 +3,7 @@ %global debug_package %{nil} %global version {{{ git_repo_version }}} %global pkg_summary DankMaterialShell - Material 3 inspired shell for Wayland compositors -%global go_toolchain_version 1.26.1 +%global go_toolchain_version 1.26.4 Name: dms Epoch: 2 @@ -130,16 +130,6 @@ install -Dm644 assets/com.danklinux.dms.desktop %{buildroot}%{_datadir}/applicat install -Dm644 assets/com.danklinux.dms.notepad.desktop %{buildroot}%{_datadir}/applications/com.danklinux.dms.notepad.desktop install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg -# Install shell files to shared data location -install -dm755 %{buildroot}%{_datadir}/quickshell/dms -cp -rL quickshell/* %{buildroot}%{_datadir}/quickshell/dms/ - -# Remove build files -rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git* -rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore -rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github -rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro - %posttrans # Signal running DMS instances to reload pkill -USR1 -x dms >/dev/null 2>&1 || : @@ -148,7 +138,6 @@ pkill -USR1 -x dms >/dev/null 2>&1 || : %license LICENSE %doc CONTRIBUTING.md %doc quickshell/README.md -%{_datadir}/quickshell/dms/ %{_userunitdir}/dms.service %{_datadir}/applications/dms-open.desktop %{_datadir}/applications/com.danklinux.dms.desktop diff --git a/distro/opensuse/dms-git.spec b/distro/opensuse/dms-git.spec index fd5766800..4907784c8 100644 --- a/distro/opensuse/dms-git.spec +++ b/distro/opensuse/dms-git.spec @@ -1,5 +1,5 @@ %global debug_package %{nil} -%global go_toolchain_version 1.26.1 +%global go_toolchain_version 1.26.4 Name: dms-git Version: 1.4.0+git2528.d336866f @@ -38,7 +38,7 @@ DankMaterialShell (DMS) is a modern Wayland desktop shell built with Quickshell and optimized for niri, Hyprland, Sway, and other wlroots compositors. This git version tracks the master branch and includes the latest features -and fixes. Includes pre-built dms CLI binary and QML shell files. +and fixes. The Quickshell UI is embedded in the dms binary. %prep %setup -q -n dms-git-source @@ -120,15 +120,6 @@ install -Dm644 assets/com.danklinux.dms.desktop %{buildroot}%{_datadir}/applicat install -Dm644 assets/com.danklinux.dms.notepad.desktop %{buildroot}%{_datadir}/applications/com.danklinux.dms.notepad.desktop install -Dm644 assets/danklogo.svg %{buildroot}%{_datadir}/icons/hicolor/scalable/apps/danklogo.svg -install -dm755 %{buildroot}%{_datadir}/quickshell/dms -cp -r quickshell/* %{buildroot}%{_datadir}/quickshell/dms/ - -rm -rf %{buildroot}%{_datadir}/quickshell/dms/.git* -rm -f %{buildroot}%{_datadir}/quickshell/dms/.gitignore -rm -rf %{buildroot}%{_datadir}/quickshell/dms/.github -rm -rf %{buildroot}%{_datadir}/quickshell/dms/distro -rm -rf %{buildroot}%{_datadir}/quickshell/dms/core - %posttrans if [ -d "%{_sysconfdir}/xdg/quickshell/dms" ]; then rmdir "%{_sysconfdir}/xdg/quickshell/dms" 2>/dev/null || true @@ -149,8 +140,6 @@ pkill -USR1 -x dms >/dev/null 2>&1 || : %dir %{_datadir}/zsh/site-functions %{_datadir}/zsh/site-functions/_dms %{_datadir}/bash-completion/completions/dms -%dir %{_datadir}/quickshell -%{_datadir}/quickshell/dms/ %{_userunitdir}/dms.service %{_datadir}/applications/dms-open.desktop %{_datadir}/applications/com.danklinux.dms.desktop diff --git a/distro/ubuntu/dms-git/debian/rules b/distro/ubuntu/dms-git/debian/rules index 737a0beb5..8f858a3e9 100755 --- a/distro/ubuntu/dms-git/debian/rules +++ b/distro/ubuntu/dms-git/debian/rules @@ -58,14 +58,6 @@ override_dh_auto_install: # Install binary install -Dm755 dms debian/dms-git/usr/bin/dms - # Install QML files from git clone - mkdir -p debian/dms-git/usr/share/quickshell/dms - cp -rL dms-git-repo/* debian/dms-git/usr/share/quickshell/dms/ - - # Remove unnecessary directories - rm -rf debian/dms-git/usr/share/quickshell/dms/core - rm -rf debian/dms-git/usr/share/quickshell/dms/distro - # Install systemd user service install -Dm644 dms-git-repo/assets/systemd/dms.service \ debian/dms-git/usr/lib/systemd/user/dms.service @@ -80,14 +72,6 @@ override_dh_auto_install: install -Dm644 dms-git-repo/assets/danklogo.svg \ debian/dms-git/usr/share/icons/hicolor/scalable/apps/danklogo.svg - # Create DMS Version file - @if [ -f dms-git-repo/.dms-version ]; then \ - . dms-git-repo/.dms-version; \ - echo "$$VERSION" > debian/dms-git/usr/share/quickshell/dms/VERSION; \ - else \ - echo "dev" > debian/dms-git/usr/share/quickshell/dms/VERSION; \ - fi - override_dh_auto_clean: # Don't delete dms-git-repo directory - it's part of the source package (native format) # Clean up build artifacts diff --git a/distro/void/srcpkgs/dms-git/template b/distro/void/srcpkgs/dms-git/template index f2a82362b..dc5656604 100644 --- a/distro/void/srcpkgs/dms-git/template +++ b/distro/void/srcpkgs/dms-git/template @@ -8,16 +8,18 @@ # without first providing the source archive. # # The binary is built with the `distro_binary` build tag (drops the in-app -# self-update command). +# self-update command) and `withshell` (embeds the Quickshell UI; the shell +# no longer discovers an installed tree, so an unembedded binary has no UI). pkgname=dms-git version=1.4.6.git revision=1 create_wrksrc=yes build_style=go build_wrksrc="core" +hostmakedepends="make" go_import_path="github.com/AvengeMedia/DankMaterialShell/core" go_package="${go_import_path}/cmd/dms" -go_build_tags="distro_binary" +go_build_tags="distro_binary withshell" go_ldflags="-X main.Version=${version}" short_desc="DankMaterialShell — Material 3 desktop shell for Wayland (git variant)" maintainer="AvengeMedia " @@ -34,11 +36,11 @@ provides="dms-${version}_${revision}" # Optional feature deps are listed in distro/void/README.md. depends="quickshell accountsservice dgop matugen dbus elogind" -post_install() { - # QML shell tree (build_style=go already installed the dms binary) - vmkdir usr/share/quickshell/dms - vcopy "${wrksrc}/quickshell/*" usr/share/quickshell/dms +pre_build() { + make sync-shell +} +post_install() { # Desktop entry + icon vinstall "${wrksrc}/assets/dms-open.desktop" 644 usr/share/applications vinstall "${wrksrc}/assets/com.danklinux.dms.desktop" 644 usr/share/applications diff --git a/flake.nix b/flake.nix index ef26355b3..5ef1fe6b1 100644 --- a/flake.nix +++ b/flake.nix @@ -111,7 +111,7 @@ inherit version; pname = "dms-shell"; src = ./core; - vendorHash = "sha256-nvxFHQhOfBGl3h51fgYDb39K0NCj+H8mAEyKr1qOwJQ="; + vendorHash = "sha256-//ya3Yb25/kEBeT6M4bO8Egi+Tb7iiUwevXlzTyIgqA="; subPackages = [ "cmd/dms" ];