mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 21:42:51 -05:00
core: add slices, paths, exec utils
This commit is contained in:
@@ -211,45 +211,13 @@ func runBrightnessSet(cmd *cobra.Command, args []string) {
|
|||||||
exponential, _ := cmd.Flags().GetBool("exponential")
|
exponential, _ := cmd.Flags().GetBool("exponential")
|
||||||
exponent, _ := cmd.Flags().GetFloat64("exponent")
|
exponent, _ := cmd.Flags().GetFloat64("exponent")
|
||||||
|
|
||||||
// For backlight/leds devices, try logind backend first (requires D-Bus connection)
|
|
||||||
parts := strings.SplitN(deviceID, ":", 2)
|
parts := strings.SplitN(deviceID, ":", 2)
|
||||||
if len(parts) == 2 && (parts[0] == "backlight" || parts[0] == "leds") {
|
if len(parts) == 2 && (parts[0] == "backlight" || parts[0] == "leds") {
|
||||||
subsystem := parts[0]
|
if ok := tryLogindBrightness(parts[0], parts[1], deviceID, percent, exponential, exponent); ok {
|
||||||
name := parts[1]
|
|
||||||
|
|
||||||
// Initialize backends needed for logind approach
|
|
||||||
sysfs, err := brightness.NewSysfsBackend()
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("NewSysfsBackend failed: %v", err)
|
|
||||||
} else {
|
|
||||||
logind, err := brightness.NewLogindBackend()
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("NewLogindBackend failed: %v", err)
|
|
||||||
} else {
|
|
||||||
defer logind.Close()
|
|
||||||
|
|
||||||
// Get device info to convert percent to value
|
|
||||||
dev, err := sysfs.GetDevice(deviceID)
|
|
||||||
if err == nil {
|
|
||||||
// Calculate hardware value using the same logic as Manager.setViaSysfsWithLogind
|
|
||||||
value := sysfs.PercentToValueWithExponent(percent, dev, exponential, exponent)
|
|
||||||
|
|
||||||
// Call logind with hardware value
|
|
||||||
if err := logind.SetBrightness(subsystem, name, uint32(value)); err == nil {
|
|
||||||
log.Debugf("set %s to %d%% (%d) via logind", deviceID, percent, value)
|
|
||||||
fmt.Printf("Set %s to %d%%\n", deviceID, percent)
|
|
||||||
return
|
return
|
||||||
} else {
|
|
||||||
log.Debugf("logind.SetBrightness failed: %v", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
log.Debugf("sysfs.GetDeviceByID failed: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback to direct sysfs (requires write permissions)
|
|
||||||
sysfs, err := brightness.NewSysfsBackend()
|
sysfs, err := brightness.NewSysfsBackend()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err := sysfs.SetBrightnessWithExponent(deviceID, percent, exponential, exponent); err == nil {
|
if err := sysfs.SetBrightnessWithExponent(deviceID, percent, exponential, exponent); err == nil {
|
||||||
@@ -280,6 +248,37 @@ func runBrightnessSet(cmd *cobra.Command, args []string) {
|
|||||||
log.Fatalf("Failed to set brightness for device: %s", deviceID)
|
log.Fatalf("Failed to set brightness for device: %s", deviceID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func tryLogindBrightness(subsystem, name, deviceID string, percent int, exponential bool, exponent float64) bool {
|
||||||
|
sysfs, err := brightness.NewSysfsBackend()
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("NewSysfsBackend failed: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
logind, err := brightness.NewLogindBackend()
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("NewLogindBackend failed: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer logind.Close()
|
||||||
|
|
||||||
|
dev, err := sysfs.GetDevice(deviceID)
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("sysfs.GetDeviceByID failed: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
value := sysfs.PercentToValueWithExponent(percent, dev, exponential, exponent)
|
||||||
|
if err := logind.SetBrightness(subsystem, name, uint32(value)); err != nil {
|
||||||
|
log.Debugf("logind.SetBrightness failed: %v", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("set %s to %d%% (%d) via logind", deviceID, percent, value)
|
||||||
|
fmt.Printf("Set %s to %d%%\n", deviceID, percent)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func getBrightnessDevices(includeDDC bool) []string {
|
func getBrightnessDevices(includeDDC bool) []string {
|
||||||
allDevices := getAllBrightnessDevices(includeDDC)
|
allDevices := getAllBrightnessDevices(includeDDC)
|
||||||
|
|
||||||
|
|||||||
@@ -152,6 +152,24 @@ var pluginsUninstallCmd = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pluginsUpdateCmd = &cobra.Command{
|
||||||
|
Use: "update <plugin-id>",
|
||||||
|
Short: "Update a plugin by ID",
|
||||||
|
Long: "Update an installed DMS plugin using its ID (e.g., 'myPlugin'). Plugin names are also supported.",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
if len(args) != 0 {
|
||||||
|
return nil, cobra.ShellCompDirectiveNoFileComp
|
||||||
|
}
|
||||||
|
return getInstalledPluginIDs(), cobra.ShellCompDirectiveNoFileComp
|
||||||
|
},
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if err := updatePluginCLI(args[0]); err != nil {
|
||||||
|
log.Fatalf("Error updating plugin: %v", err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func runVersion(cmd *cobra.Command, args []string) {
|
func runVersion(cmd *cobra.Command, args []string) {
|
||||||
printASCII()
|
printASCII()
|
||||||
fmt.Printf("%s\n", formatVersion(Version))
|
fmt.Printf("%s\n", formatVersion(Version))
|
||||||
@@ -408,39 +426,14 @@ func uninstallPluginCLI(idOrName string) error {
|
|||||||
return fmt.Errorf("failed to create registry: %w", err)
|
return fmt.Errorf("failed to create registry: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginList, err := registry.List()
|
pluginList, _ := registry.List()
|
||||||
if err != nil {
|
plugin := plugins.FindByIDOrName(idOrName, pluginList)
|
||||||
return fmt.Errorf("failed to list plugins: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// First, try to find by ID (preferred method)
|
|
||||||
var plugin *plugins.Plugin
|
|
||||||
for _, p := range pluginList {
|
|
||||||
if p.ID == idOrName {
|
|
||||||
plugin = &p
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback to name for backward compatibility
|
|
||||||
if plugin == nil {
|
|
||||||
for _, p := range pluginList {
|
|
||||||
if p.Name == idOrName {
|
|
||||||
plugin = &p
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if plugin == nil {
|
|
||||||
return fmt.Errorf("plugin not found: %s", idOrName)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if plugin != nil {
|
||||||
installed, err := manager.IsInstalled(*plugin)
|
installed, err := manager.IsInstalled(*plugin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to check install status: %w", err)
|
return fmt.Errorf("failed to check install status: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !installed {
|
if !installed {
|
||||||
return fmt.Errorf("plugin not installed: %s", plugin.Name)
|
return fmt.Errorf("plugin not installed: %s", plugin.Name)
|
||||||
}
|
}
|
||||||
@@ -449,11 +442,57 @@ func uninstallPluginCLI(idOrName string) error {
|
|||||||
if err := manager.Uninstall(*plugin); err != nil {
|
if err := manager.Uninstall(*plugin); err != nil {
|
||||||
return fmt.Errorf("failed to uninstall plugin: %w", err)
|
return fmt.Errorf("failed to uninstall plugin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Plugin uninstalled successfully: %s\n", plugin.Name)
|
fmt.Printf("Plugin uninstalled successfully: %s\n", plugin.Name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Uninstalling plugin: %s\n", idOrName)
|
||||||
|
if err := manager.UninstallByIDOrName(idOrName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("Plugin uninstalled successfully: %s\n", idOrName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func updatePluginCLI(idOrName string) error {
|
||||||
|
manager, err := plugins.NewManager()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create manager: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
registry, err := plugins.NewRegistry()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create registry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginList, _ := registry.List()
|
||||||
|
plugin := plugins.FindByIDOrName(idOrName, pluginList)
|
||||||
|
|
||||||
|
if plugin != nil {
|
||||||
|
installed, err := manager.IsInstalled(*plugin)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to check install status: %w", err)
|
||||||
|
}
|
||||||
|
if !installed {
|
||||||
|
return fmt.Errorf("plugin not installed: %s", plugin.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Updating plugin: %s (ID: %s)\n", plugin.Name, plugin.ID)
|
||||||
|
if err := manager.Update(*plugin); err != nil {
|
||||||
|
return fmt.Errorf("failed to update plugin: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Plugin updated successfully: %s\n", plugin.Name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("Updating plugin: %s\n", idOrName)
|
||||||
|
if err := manager.UpdateByIDOrName(idOrName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("Plugin updated successfully: %s\n", idOrName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func getCommonCommands() []*cobra.Command {
|
func getCommonCommands() []*cobra.Command {
|
||||||
return []*cobra.Command{
|
return []*cobra.Command{
|
||||||
versionCmd,
|
versionCmd,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -121,10 +122,10 @@ func updateArchLinux() error {
|
|||||||
var helper string
|
var helper string
|
||||||
var updateCmd *exec.Cmd
|
var updateCmd *exec.Cmd
|
||||||
|
|
||||||
if commandExists("yay") {
|
if utils.CommandExists("yay") {
|
||||||
helper = "yay"
|
helper = "yay"
|
||||||
updateCmd = exec.Command("yay", "-S", packageName)
|
updateCmd = exec.Command("yay", "-S", packageName)
|
||||||
} else if commandExists("paru") {
|
} else if utils.CommandExists("paru") {
|
||||||
helper = "paru"
|
helper = "paru"
|
||||||
updateCmd = exec.Command("paru", "-S", packageName)
|
updateCmd = exec.Command("paru", "-S", packageName)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
@@ -448,7 +449,7 @@ func enableGreeter() error {
|
|||||||
fmt.Println("Detecting installed compositors...")
|
fmt.Println("Detecting installed compositors...")
|
||||||
compositors := greeter.DetectCompositors()
|
compositors := greeter.DetectCompositors()
|
||||||
|
|
||||||
if commandExists("sway") {
|
if utils.CommandExists("sway") {
|
||||||
compositors = append(compositors, "sway")
|
compositors = append(compositors, "sway")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func init() {
|
|||||||
updateCmd.AddCommand(updateCheckCmd)
|
updateCmd.AddCommand(updateCheckCmd)
|
||||||
|
|
||||||
// Add subcommands to plugins
|
// Add subcommands to plugins
|
||||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd)
|
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
||||||
|
|
||||||
// Add common commands to root
|
// Add common commands to root
|
||||||
rootCmd.AddCommand(getCommonCommands()...)
|
rootCmd.AddCommand(getCommonCommands()...)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func init() {
|
|||||||
greeterCmd.AddCommand(greeterSyncCmd, greeterEnableCmd, greeterStatusCmd)
|
greeterCmd.AddCommand(greeterSyncCmd, greeterEnableCmd, greeterStatusCmd)
|
||||||
|
|
||||||
// Add subcommands to plugins
|
// Add subcommands to plugins
|
||||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd)
|
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
||||||
|
|
||||||
// Add common commands to root
|
// Add common commands to root
|
||||||
rootCmd.AddCommand(getCommonCommands()...)
|
rootCmd.AddCommand(getCommonCommands()...)
|
||||||
|
|||||||
@@ -104,7 +104,6 @@ func getAllDMSPIDs() []int {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the child process is still alive
|
|
||||||
proc, err := os.FindProcess(childPID)
|
proc, err := os.FindProcess(childPID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.Remove(pidFile)
|
os.Remove(pidFile)
|
||||||
@@ -112,18 +111,15 @@ func getAllDMSPIDs() []int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := proc.Signal(syscall.Signal(0)); err != nil {
|
if err := proc.Signal(syscall.Signal(0)); err != nil {
|
||||||
// Process is dead, remove stale PID file
|
|
||||||
os.Remove(pidFile)
|
os.Remove(pidFile)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
pids = append(pids, childPID)
|
pids = append(pids, childPID)
|
||||||
|
|
||||||
// Also get the parent PID from the filename
|
|
||||||
parentPIDStr := strings.TrimPrefix(entry.Name(), "danklinux-")
|
parentPIDStr := strings.TrimPrefix(entry.Name(), "danklinux-")
|
||||||
parentPIDStr = strings.TrimSuffix(parentPIDStr, ".pid")
|
parentPIDStr = strings.TrimSuffix(parentPIDStr, ".pid")
|
||||||
if parentPID, err := strconv.Atoi(parentPIDStr); err == nil {
|
if parentPID, err := strconv.Atoi(parentPIDStr); err == nil {
|
||||||
// Check if parent is still alive
|
|
||||||
if parentProc, err := os.FindProcess(parentPID); err == nil {
|
if parentProc, err := os.FindProcess(parentPID); err == nil {
|
||||||
if err := parentProc.Signal(syscall.Signal(0)); err == nil {
|
if err := parentProc.Signal(syscall.Signal(0)); err == nil {
|
||||||
pids = append(pids, parentPID)
|
pids = append(pids, parentPID)
|
||||||
@@ -225,7 +221,6 @@ func runShellInteractive(session bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// All other signals: clean shutdown
|
|
||||||
log.Infof("\nReceived signal %v, shutting down...", sig)
|
log.Infof("\nReceived signal %v, shutting down...", sig)
|
||||||
cancel()
|
cancel()
|
||||||
cmd.Process.Signal(syscall.SIGTERM)
|
cmd.Process.Signal(syscall.SIGTERM)
|
||||||
@@ -282,7 +277,6 @@ func restartShell() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func killShell() {
|
func killShell() {
|
||||||
// Get all tracked DMS PIDs from PID files
|
|
||||||
pids := getAllDMSPIDs()
|
pids := getAllDMSPIDs()
|
||||||
|
|
||||||
if len(pids) == 0 {
|
if len(pids) == 0 {
|
||||||
@@ -293,14 +287,12 @@ func killShell() {
|
|||||||
currentPid := os.Getpid()
|
currentPid := os.Getpid()
|
||||||
uniquePids := make(map[int]bool)
|
uniquePids := make(map[int]bool)
|
||||||
|
|
||||||
// Deduplicate and filter out current process
|
|
||||||
for _, pid := range pids {
|
for _, pid := range pids {
|
||||||
if pid != currentPid {
|
if pid != currentPid {
|
||||||
uniquePids[pid] = true
|
uniquePids[pid] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill all tracked processes
|
|
||||||
for pid := range uniquePids {
|
for pid := range uniquePids {
|
||||||
proc, err := os.FindProcess(pid)
|
proc, err := os.FindProcess(pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -308,7 +300,6 @@ func killShell() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if process is still alive before killing
|
|
||||||
if err := proc.Signal(syscall.Signal(0)); err != nil {
|
if err := proc.Signal(syscall.Signal(0)); err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -320,7 +311,6 @@ func killShell() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up any remaining PID files
|
|
||||||
dir := getRuntimeDir()
|
dir := getRuntimeDir()
|
||||||
entries, err := os.ReadDir(dir)
|
entries, err := os.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -337,7 +327,6 @@ func killShell() {
|
|||||||
|
|
||||||
func runShellDaemon(session bool) {
|
func runShellDaemon(session bool) {
|
||||||
isSessionManaged = session
|
isSessionManaged = session
|
||||||
// Check if this is the daemon child process by looking for the hidden flag
|
|
||||||
isDaemonChild := false
|
isDaemonChild := false
|
||||||
for _, arg := range os.Args {
|
for _, arg := range os.Args {
|
||||||
if arg == "--daemon-child" {
|
if arg == "--daemon-child" {
|
||||||
|
|||||||
@@ -6,12 +6,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func commandExists(cmd string) bool {
|
|
||||||
_, err := exec.LookPath(cmd)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// findCommandPath returns the absolute path to a command in PATH
|
|
||||||
func findCommandPath(cmd string) (string, error) {
|
func findCommandPath(cmd string) (string, error) {
|
||||||
path, err := exec.LookPath(cmd)
|
path, err := exec.LookPath(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,19 +5,14 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// LocateDMSConfig searches for DMS installation following XDG Base Directory specification
|
|
||||||
func LocateDMSConfig() (string, error) {
|
func LocateDMSConfig() (string, error) {
|
||||||
var primaryPaths []string
|
var primaryPaths []string
|
||||||
|
|
||||||
configHome := os.Getenv("XDG_CONFIG_HOME")
|
configHome := utils.XDGConfigHome()
|
||||||
if configHome == "" {
|
|
||||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
|
||||||
configHome = filepath.Join(homeDir, ".config")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if configHome != "" {
|
if configHome != "" {
|
||||||
primaryPaths = append(primaryPaths, filepath.Join(configHome, "quickshell", "dms"))
|
primaryPaths = append(primaryPaths, filepath.Join(configHome, "quickshell", "dms"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,13 +113,14 @@ func RGBToHSV(rgb RGB) HSV {
|
|||||||
delta := max - min
|
delta := max - min
|
||||||
|
|
||||||
var h float64
|
var h float64
|
||||||
if delta == 0 {
|
switch {
|
||||||
|
case delta == 0:
|
||||||
h = 0
|
h = 0
|
||||||
} else if max == rgb.R {
|
case max == rgb.R:
|
||||||
h = math.Mod((rgb.G-rgb.B)/delta, 6.0) / 6.0
|
h = math.Mod((rgb.G-rgb.B)/delta, 6.0) / 6.0
|
||||||
} else if max == rgb.G {
|
case max == rgb.G:
|
||||||
h = ((rgb.B-rgb.R)/delta + 2.0) / 6.0
|
h = ((rgb.B-rgb.R)/delta + 2.0) / 6.0
|
||||||
} else {
|
default:
|
||||||
h = ((rgb.R-rgb.G)/delta + 4.0) / 6.0
|
h = ((rgb.R-rgb.G)/delta + 4.0) / 6.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -112,31 +112,11 @@ func (a *ArchDistribution) DetectDependenciesWithTerminal(ctx context.Context, w
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArchDistribution) detectXDGPortal() deps.Dependency {
|
func (a *ArchDistribution) detectXDGPortal() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return a.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", a.packageInstalled("xdg-desktop-portal-gtk"))
|
||||||
if a.packageInstalled("xdg-desktop-portal-gtk") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "xdg-desktop-portal-gtk",
|
|
||||||
Status: status,
|
|
||||||
Description: "Desktop integration portal for GTK",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArchDistribution) detectAccountsService() deps.Dependency {
|
func (a *ArchDistribution) detectAccountsService() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return a.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", a.packageInstalled("accountsservice"))
|
||||||
if a.packageInstalled("accountsservice") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "accountsservice",
|
|
||||||
Status: status,
|
|
||||||
Description: "D-Bus interface for user account query and manipulation",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *ArchDistribution) packageInstalled(pkg string) bool {
|
func (a *ArchDistribution) packageInstalled(pkg string) bool {
|
||||||
|
|||||||
@@ -76,47 +76,42 @@ func ExecSudoCommand(ctx context.Context, sudoPassword string, command string) *
|
|||||||
return exec.CommandContext(ctx, "bash", "-c", cmdStr)
|
return exec.CommandContext(ctx, "bash", "-c", cmdStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common dependency detection methods
|
func (b *BaseDistribution) detectCommand(name, description string) deps.Dependency {
|
||||||
func (b *BaseDistribution) detectGit() deps.Dependency {
|
|
||||||
status := deps.StatusMissing
|
status := deps.StatusMissing
|
||||||
if b.commandExists("git") {
|
if b.commandExists(name) {
|
||||||
status = deps.StatusInstalled
|
status = deps.StatusInstalled
|
||||||
}
|
}
|
||||||
|
|
||||||
return deps.Dependency{
|
return deps.Dependency{
|
||||||
Name: "git",
|
Name: name,
|
||||||
Status: status,
|
Status: status,
|
||||||
Description: "Version control system",
|
Description: description,
|
||||||
Required: true,
|
Required: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *BaseDistribution) detectPackage(name, description string, installed bool) deps.Dependency {
|
||||||
|
status := deps.StatusMissing
|
||||||
|
if installed {
|
||||||
|
status = deps.StatusInstalled
|
||||||
|
}
|
||||||
|
return deps.Dependency{
|
||||||
|
Name: name,
|
||||||
|
Status: status,
|
||||||
|
Description: description,
|
||||||
|
Required: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BaseDistribution) detectGit() deps.Dependency {
|
||||||
|
return b.detectCommand("git", "Version control system")
|
||||||
|
}
|
||||||
|
|
||||||
func (b *BaseDistribution) detectMatugen() deps.Dependency {
|
func (b *BaseDistribution) detectMatugen() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return b.detectCommand("matugen", "Material Design color generation tool")
|
||||||
if b.commandExists("matugen") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "matugen",
|
|
||||||
Status: status,
|
|
||||||
Description: "Material Design color generation tool",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BaseDistribution) detectDgop() deps.Dependency {
|
func (b *BaseDistribution) detectDgop() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return b.detectCommand("dgop", "Desktop portal management tool")
|
||||||
if b.commandExists("dgop") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "dgop",
|
|
||||||
Status: status,
|
|
||||||
Description: "Desktop portal management tool",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BaseDistribution) detectDMS() deps.Dependency {
|
func (b *BaseDistribution) detectDMS() deps.Dependency {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBaseDistribution_detectDMS_NotInstalled(t *testing.T) {
|
func TestBaseDistribution_detectDMS_NotInstalled(t *testing.T) {
|
||||||
@@ -36,7 +37,7 @@ func TestBaseDistribution_detectDMS_NotInstalled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBaseDistribution_detectDMS_Installed(t *testing.T) {
|
func TestBaseDistribution_detectDMS_Installed(t *testing.T) {
|
||||||
if !commandExists("git") {
|
if !utils.CommandExists("git") {
|
||||||
t.Skip("git not available")
|
t.Skip("git not available")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -80,7 +81,7 @@ func TestBaseDistribution_detectDMS_Installed(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestBaseDistribution_detectDMS_NeedsUpdate(t *testing.T) {
|
func TestBaseDistribution_detectDMS_NeedsUpdate(t *testing.T) {
|
||||||
if !commandExists("git") {
|
if !utils.CommandExists("git") {
|
||||||
t.Skip("git not available")
|
t.Skip("git not available")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,11 +165,6 @@ func TestBaseDistribution_NewBaseDistribution(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandExists(cmd string) bool {
|
|
||||||
_, err := exec.LookPath(cmd)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBaseDistribution_versionCompare(t *testing.T) {
|
func TestBaseDistribution_versionCompare(t *testing.T) {
|
||||||
logChan := make(chan string, 10)
|
logChan := make(chan string, 10)
|
||||||
defer close(logChan)
|
defer close(logChan)
|
||||||
|
|||||||
@@ -75,45 +75,15 @@ func (d *DebianDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DebianDistribution) detectXDGPortal() deps.Dependency {
|
func (d *DebianDistribution) detectXDGPortal() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return d.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", d.packageInstalled("xdg-desktop-portal-gtk"))
|
||||||
if d.packageInstalled("xdg-desktop-portal-gtk") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "xdg-desktop-portal-gtk",
|
|
||||||
Status: status,
|
|
||||||
Description: "Desktop integration portal for GTK",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DebianDistribution) detectXwaylandSatellite() deps.Dependency {
|
func (d *DebianDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return d.detectCommand("xwayland-satellite", "Xwayland support")
|
||||||
if d.commandExists("xwayland-satellite") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "xwayland-satellite",
|
|
||||||
Status: status,
|
|
||||||
Description: "Xwayland support",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DebianDistribution) detectAccountsService() deps.Dependency {
|
func (d *DebianDistribution) detectAccountsService() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return d.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", d.packageInstalled("accountsservice"))
|
||||||
if d.packageInstalled("accountsservice") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "accountsservice",
|
|
||||||
Status: status,
|
|
||||||
Description: "D-Bus interface for user account query and manipulation",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DebianDistribution) packageInstalled(pkg string) bool {
|
func (d *DebianDistribution) packageInstalled(pkg string) bool {
|
||||||
|
|||||||
@@ -97,17 +97,7 @@ func (f *FedoraDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FedoraDistribution) detectXDGPortal() deps.Dependency {
|
func (f *FedoraDistribution) detectXDGPortal() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return f.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", f.packageInstalled("xdg-desktop-portal-gtk"))
|
||||||
if f.packageInstalled("xdg-desktop-portal-gtk") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "xdg-desktop-portal-gtk",
|
|
||||||
Status: status,
|
|
||||||
Description: "Desktop integration portal for GTK",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FedoraDistribution) packageInstalled(pkg string) bool {
|
func (f *FedoraDistribution) packageInstalled(pkg string) bool {
|
||||||
|
|||||||
@@ -113,45 +113,15 @@ func (g *GentooDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GentooDistribution) detectXDGPortal() deps.Dependency {
|
func (g *GentooDistribution) detectXDGPortal() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return g.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", g.packageInstalled("sys-apps/xdg-desktop-portal-gtk"))
|
||||||
if g.packageInstalled("sys-apps/xdg-desktop-portal-gtk") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "xdg-desktop-portal-gtk",
|
|
||||||
Status: status,
|
|
||||||
Description: "Desktop integration portal for GTK",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GentooDistribution) detectXwaylandSatellite() deps.Dependency {
|
func (g *GentooDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return g.detectPackage("xwayland-satellite", "Xwayland support", g.packageInstalled("gui-apps/xwayland-satellite"))
|
||||||
if g.packageInstalled("gui-apps/xwayland-satellite") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "xwayland-satellite",
|
|
||||||
Status: status,
|
|
||||||
Description: "Xwayland support",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GentooDistribution) detectAccountsService() deps.Dependency {
|
func (g *GentooDistribution) detectAccountsService() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return g.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", g.packageInstalled("sys-apps/accountsservice"))
|
||||||
if g.packageInstalled("sys-apps/accountsservice") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "accountsservice",
|
|
||||||
Status: status,
|
|
||||||
Description: "D-Bus interface for user account query and manipulation",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GentooDistribution) packageInstalled(pkg string) bool {
|
func (g *GentooDistribution) packageInstalled(pkg string) bool {
|
||||||
|
|||||||
@@ -87,17 +87,7 @@ func (o *OpenSUSEDistribution) DetectDependenciesWithTerminal(ctx context.Contex
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenSUSEDistribution) detectXDGPortal() deps.Dependency {
|
func (o *OpenSUSEDistribution) detectXDGPortal() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return o.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", o.packageInstalled("xdg-desktop-portal-gtk"))
|
||||||
if o.packageInstalled("xdg-desktop-portal-gtk") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "xdg-desktop-portal-gtk",
|
|
||||||
Status: status,
|
|
||||||
Description: "Desktop integration portal for GTK",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *OpenSUSEDistribution) packageInstalled(pkg string) bool {
|
func (o *OpenSUSEDistribution) packageInstalled(pkg string) bool {
|
||||||
|
|||||||
@@ -85,45 +85,15 @@ func (u *UbuntuDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *UbuntuDistribution) detectXDGPortal() deps.Dependency {
|
func (u *UbuntuDistribution) detectXDGPortal() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return u.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", u.packageInstalled("xdg-desktop-portal-gtk"))
|
||||||
if u.packageInstalled("xdg-desktop-portal-gtk") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "xdg-desktop-portal-gtk",
|
|
||||||
Status: status,
|
|
||||||
Description: "Desktop integration portal for GTK",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UbuntuDistribution) detectXwaylandSatellite() deps.Dependency {
|
func (u *UbuntuDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return u.detectCommand("xwayland-satellite", "Xwayland support")
|
||||||
if u.commandExists("xwayland-satellite") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "xwayland-satellite",
|
|
||||||
Status: status,
|
|
||||||
Description: "Xwayland support",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UbuntuDistribution) detectAccountsService() deps.Dependency {
|
func (u *UbuntuDistribution) detectAccountsService() deps.Dependency {
|
||||||
status := deps.StatusMissing
|
return u.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", u.packageInstalled("accountsservice"))
|
||||||
if u.packageInstalled("accountsservice") {
|
|
||||||
status = deps.StatusInstalled
|
|
||||||
}
|
|
||||||
|
|
||||||
return deps.Dependency{
|
|
||||||
Name: "accountsservice",
|
|
||||||
Status: status,
|
|
||||||
Description: "D-Bus interface for user account query and manipulation",
|
|
||||||
Required: true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UbuntuDistribution) packageInstalled(pkg string) bool {
|
func (u *UbuntuDistribution) packageInstalled(pkg string) bool {
|
||||||
|
|||||||
@@ -286,6 +286,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, loadInstalledPlugins
|
return m, loadInstalledPlugins
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
|
case pluginUpdatedMsg:
|
||||||
|
if msg.err != nil {
|
||||||
|
m.installedPluginsError = msg.err.Error()
|
||||||
|
} else {
|
||||||
|
m.installedPluginsError = ""
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
case pluginInstalledMsg:
|
case pluginInstalledMsg:
|
||||||
if msg.err != nil {
|
if msg.err != nil {
|
||||||
m.pluginsError = msg.err.Error()
|
m.pluginsError = msg.err.Error()
|
||||||
|
|||||||
@@ -75,14 +75,13 @@ type MenuItem struct {
|
|||||||
|
|
||||||
func NewModel(version string) Model {
|
func NewModel(version string) Model {
|
||||||
detector, _ := NewDetector()
|
detector, _ := NewDetector()
|
||||||
dependencies := detector.GetInstalledComponents()
|
|
||||||
|
|
||||||
// Use the proper detection method for both window managers
|
var dependencies []DependencyInfo
|
||||||
hyprlandInstalled, niriInstalled, err := detector.GetWindowManagerStatus()
|
var hyprlandInstalled, niriInstalled bool
|
||||||
if err != nil {
|
|
||||||
// Fallback to false if detection fails
|
if detector != nil {
|
||||||
hyprlandInstalled = false
|
dependencies = detector.GetInstalledComponents()
|
||||||
niriInstalled = false
|
hyprlandInstalled, niriInstalled, _ = detector.GetWindowManagerStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
m := Model{
|
m := Model{
|
||||||
@@ -201,6 +200,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, loadInstalledPlugins
|
return m, loadInstalledPlugins
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
|
case pluginUpdatedMsg:
|
||||||
|
if msg.err != nil {
|
||||||
|
m.installedPluginsError = msg.err.Error()
|
||||||
|
} else {
|
||||||
|
m.installedPluginsError = ""
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
case pluginInstalledMsg:
|
case pluginInstalledMsg:
|
||||||
if msg.err != nil {
|
if msg.err != nil {
|
||||||
m.pluginsError = msg.err.Error()
|
m.pluginsError = msg.err.Error()
|
||||||
|
|||||||
@@ -227,6 +227,11 @@ func (m Model) updatePluginInstalledDetail(msg tea.KeyMsg) (tea.Model, tea.Cmd)
|
|||||||
plugin := m.installedPluginsList[m.selectedInstalledIndex]
|
plugin := m.installedPluginsList[m.selectedInstalledIndex]
|
||||||
return m, uninstallPlugin(plugin)
|
return m, uninstallPlugin(plugin)
|
||||||
}
|
}
|
||||||
|
case "p":
|
||||||
|
if m.selectedInstalledIndex < len(m.installedPluginsList) {
|
||||||
|
plugin := m.installedPluginsList[m.selectedInstalledIndex]
|
||||||
|
return m, updatePlugin(plugin)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
@@ -246,6 +251,11 @@ type pluginInstalledMsg struct {
|
|||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type pluginUpdatedMsg struct {
|
||||||
|
pluginName string
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
func loadInstalledPlugins() tea.Msg {
|
func loadInstalledPlugins() tea.Msg {
|
||||||
manager, err := plugins.NewManager()
|
manager, err := plugins.NewManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -337,3 +347,31 @@ func uninstallPlugin(plugin pluginInfo) tea.Cmd {
|
|||||||
return pluginUninstalledMsg{pluginName: plugin.Name}
|
return pluginUninstalledMsg{pluginName: plugin.Name}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updatePlugin(plugin pluginInfo) tea.Cmd {
|
||||||
|
return func() tea.Msg {
|
||||||
|
manager, err := plugins.NewManager()
|
||||||
|
if err != nil {
|
||||||
|
return pluginUpdatedMsg{pluginName: plugin.Name, err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
p := plugins.Plugin{
|
||||||
|
ID: plugin.ID,
|
||||||
|
Name: plugin.Name,
|
||||||
|
Category: plugin.Category,
|
||||||
|
Author: plugin.Author,
|
||||||
|
Description: plugin.Description,
|
||||||
|
Repo: plugin.Repo,
|
||||||
|
Path: plugin.Path,
|
||||||
|
Capabilities: plugin.Capabilities,
|
||||||
|
Compositors: plugin.Compositors,
|
||||||
|
Dependencies: plugin.Dependencies,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := manager.Update(p); err != nil {
|
||||||
|
return pluginUpdatedMsg{pluginName: plugin.Name, err: err}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pluginUpdatedMsg{pluginName: plugin.Name}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DetectDMSPath checks for DMS installation following XDG Base Directory specification
|
// DetectDMSPath checks for DMS installation following XDG Base Directory specification
|
||||||
@@ -22,10 +23,10 @@ func DetectDMSPath() (string, error) {
|
|||||||
func DetectCompositors() []string {
|
func DetectCompositors() []string {
|
||||||
var compositors []string
|
var compositors []string
|
||||||
|
|
||||||
if commandExists("niri") {
|
if utils.CommandExists("niri") {
|
||||||
compositors = append(compositors, "niri")
|
compositors = append(compositors, "niri")
|
||||||
}
|
}
|
||||||
if commandExists("Hyprland") {
|
if utils.CommandExists("Hyprland") {
|
||||||
compositors = append(compositors, "Hyprland")
|
compositors = append(compositors, "Hyprland")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +63,7 @@ func PromptCompositorChoice(compositors []string) (string, error) {
|
|||||||
|
|
||||||
// EnsureGreetdInstalled checks if greetd is installed and installs it if not
|
// EnsureGreetdInstalled checks if greetd is installed and installs it if not
|
||||||
func EnsureGreetdInstalled(logFunc func(string), sudoPassword string) error {
|
func EnsureGreetdInstalled(logFunc func(string), sudoPassword string) error {
|
||||||
if commandExists("greetd") {
|
if utils.CommandExists("greetd") {
|
||||||
logFunc("✓ greetd is already installed")
|
logFunc("✓ greetd is already installed")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -144,7 +145,7 @@ func EnsureGreetdInstalled(logFunc func(string), sudoPassword string) error {
|
|||||||
// CopyGreeterFiles installs the dms-greeter wrapper and sets up cache directory
|
// CopyGreeterFiles installs the dms-greeter wrapper and sets up cache directory
|
||||||
func CopyGreeterFiles(dmsPath, compositor string, logFunc func(string), sudoPassword string) error {
|
func CopyGreeterFiles(dmsPath, compositor string, logFunc func(string), sudoPassword string) error {
|
||||||
// Check if dms-greeter is already in PATH
|
// Check if dms-greeter is already in PATH
|
||||||
if commandExists("dms-greeter") {
|
if utils.CommandExists("dms-greeter") {
|
||||||
logFunc("✓ dms-greeter wrapper already installed")
|
logFunc("✓ dms-greeter wrapper already installed")
|
||||||
} else {
|
} else {
|
||||||
// Install the wrapper script
|
// Install the wrapper script
|
||||||
@@ -204,7 +205,7 @@ func CopyGreeterFiles(dmsPath, compositor string, logFunc func(string), sudoPass
|
|||||||
|
|
||||||
// SetupParentDirectoryACLs sets ACLs on parent directories to allow traversal
|
// SetupParentDirectoryACLs sets ACLs on parent directories to allow traversal
|
||||||
func SetupParentDirectoryACLs(logFunc func(string), sudoPassword string) error {
|
func SetupParentDirectoryACLs(logFunc func(string), sudoPassword string) error {
|
||||||
if !commandExists("setfacl") {
|
if !utils.CommandExists("setfacl") {
|
||||||
logFunc("⚠ Warning: setfacl command not found. ACL support may not be available on this filesystem.")
|
logFunc("⚠ Warning: setfacl command not found. ACL support may not be available on this filesystem.")
|
||||||
logFunc(" If theme sync doesn't work, you may need to install acl package:")
|
logFunc(" If theme sync doesn't work, you may need to install acl package:")
|
||||||
logFunc(" - Fedora/RHEL: sudo dnf install acl")
|
logFunc(" - Fedora/RHEL: sudo dnf install acl")
|
||||||
@@ -419,7 +420,7 @@ user = "greeter"
|
|||||||
|
|
||||||
// Determine wrapper command path
|
// Determine wrapper command path
|
||||||
wrapperCmd := "dms-greeter"
|
wrapperCmd := "dms-greeter"
|
||||||
if !commandExists("dms-greeter") {
|
if !utils.CommandExists("dms-greeter") {
|
||||||
wrapperCmd = "/usr/local/bin/dms-greeter"
|
wrapperCmd = "/usr/local/bin/dms-greeter"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,8 +487,3 @@ func runSudoCmd(sudoPassword string, command string, args ...string) error {
|
|||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandExists(cmd string) bool {
|
|
||||||
_, err := exec.LookPath(cmd)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DiscoveryConfig struct {
|
type DiscoveryConfig struct {
|
||||||
@@ -14,13 +16,7 @@ type DiscoveryConfig struct {
|
|||||||
func DefaultDiscoveryConfig() *DiscoveryConfig {
|
func DefaultDiscoveryConfig() *DiscoveryConfig {
|
||||||
var searchPaths []string
|
var searchPaths []string
|
||||||
|
|
||||||
configHome := os.Getenv("XDG_CONFIG_HOME")
|
configHome := utils.XDGConfigHome()
|
||||||
if configHome == "" {
|
|
||||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
|
||||||
configHome = filepath.Join(homeDir, ".config")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if configHome != "" {
|
if configHome != "" {
|
||||||
searchPaths = append(searchPaths, filepath.Join(configHome, "DankMaterialShell", "cheatsheets"))
|
searchPaths = append(searchPaths, filepath.Join(configHome, "DankMaterialShell", "cheatsheets"))
|
||||||
}
|
}
|
||||||
@@ -43,7 +39,7 @@ func (d *DiscoveryConfig) FindJSONFiles() ([]string, error) {
|
|||||||
var files []string
|
var files []string
|
||||||
|
|
||||||
for _, searchPath := range d.SearchPaths {
|
for _, searchPath := range d.SearchPaths {
|
||||||
expandedPath, err := expandPath(searchPath)
|
expandedPath, err := utils.ExpandPath(searchPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -74,20 +70,6 @@ func (d *DiscoveryConfig) FindJSONFiles() ([]string, error) {
|
|||||||
return files, nil
|
return files, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func expandPath(path string) (string, error) {
|
|
||||||
expandedPath := os.ExpandEnv(path)
|
|
||||||
|
|
||||||
if strings.HasPrefix(expandedPath, "~") {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
expandedPath = filepath.Join(home, expandedPath[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Clean(expandedPath), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type JSONProviderFactory func(filePath string) (Provider, error)
|
type JSONProviderFactory func(filePath string) (Provider, error)
|
||||||
|
|
||||||
var jsonProviderFactory JSONProviderFactory
|
var jsonProviderFactory JSONProviderFactory
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDefaultDiscoveryConfig(t *testing.T) {
|
func TestDefaultDiscoveryConfig(t *testing.T) {
|
||||||
@@ -272,13 +274,13 @@ func TestExpandPathInDiscovery(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result, err := expandPath(tt.input)
|
result, err := utils.ExpandPath(tt.input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expandPath failed: %v", err)
|
t.Fatalf("expandPath failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result != tt.expected {
|
if result != tt.expected {
|
||||||
t.Errorf("expandPath(%q) = %q, want %q", tt.input, result, tt.expected)
|
t.Errorf("utils.ExpandPath(%q) = %q, want %q", tt.input, result, tt.expected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -42,15 +44,10 @@ func NewHyprlandParser() *HyprlandParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *HyprlandParser) ReadContent(directory string) error {
|
func (p *HyprlandParser) ReadContent(directory string) error {
|
||||||
expandedDir := os.ExpandEnv(directory)
|
expandedDir, err := utils.ExpandPath(directory)
|
||||||
expandedDir = filepath.Clean(expandedDir)
|
|
||||||
if strings.HasPrefix(expandedDir, "~") {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
expandedDir = filepath.Join(home, expandedDir[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := os.Stat(expandedDir)
|
info, err := os.Stat(expandedDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type JSONFileProvider struct {
|
type JSONFileProvider struct {
|
||||||
@@ -20,7 +20,7 @@ func NewJSONFileProvider(filePath string) (*JSONFileProvider, error) {
|
|||||||
return nil, fmt.Errorf("file path cannot be empty")
|
return nil, fmt.Errorf("file path cannot be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
expandedPath, err := expandPath(filePath)
|
expandedPath, err := utils.ExpandPath(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to expand path: %w", err)
|
return nil, fmt.Errorf("failed to expand path: %w", err)
|
||||||
}
|
}
|
||||||
@@ -117,17 +117,3 @@ func (j *JSONFileProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
|||||||
Binds: categorizedBinds,
|
Binds: categorizedBinds,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func expandPath(path string) (string, error) {
|
|
||||||
expandedPath := os.ExpandEnv(path)
|
|
||||||
|
|
||||||
if strings.HasPrefix(expandedPath, "~") {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
expandedPath = filepath.Join(home, expandedPath[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return filepath.Clean(expandedPath), nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewJSONFileProvider(t *testing.T) {
|
func TestNewJSONFileProvider(t *testing.T) {
|
||||||
@@ -266,13 +268,13 @@ func TestExpandPath(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
result, err := expandPath(tt.input)
|
result, err := utils.ExpandPath(tt.input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("expandPath failed: %v", err)
|
t.Fatalf("expandPath failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if result != tt.expected {
|
if result != tt.expected {
|
||||||
t.Errorf("expandPath(%q) = %q, want %q", tt.input, result, tt.expected)
|
t.Errorf("utils.ExpandPath(%q) = %q, want %q", tt.input, result, tt.expected)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -34,15 +36,10 @@ func NewMangoWCParser() *MangoWCParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *MangoWCParser) ReadContent(path string) error {
|
func (p *MangoWCParser) ReadContent(path string) error {
|
||||||
expandedPath := os.ExpandEnv(path)
|
expandedPath, err := utils.ExpandPath(path)
|
||||||
expandedPath = filepath.Clean(expandedPath)
|
|
||||||
if strings.HasPrefix(expandedPath, "~") {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
expandedPath = filepath.Join(home, expandedPath[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := os.Stat(expandedPath)
|
info, err := os.Stat(expandedPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
"github.com/sblinch/kdl-go"
|
"github.com/sblinch/kdl-go"
|
||||||
"github.com/sblinch/kdl-go/document"
|
"github.com/sblinch/kdl-go/document"
|
||||||
)
|
)
|
||||||
@@ -29,15 +30,7 @@ func NewNiriProvider(configDir string) *NiriProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func defaultNiriConfigDir() string {
|
func defaultNiriConfigDir() string {
|
||||||
if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
|
return filepath.Join(utils.XDGConfigHome(), "niri")
|
||||||
return filepath.Join(configHome, "niri")
|
|
||||||
}
|
|
||||||
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return filepath.Join(home, ".config", "niri")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *NiriProvider) Name() string {
|
func (n *NiriProvider) Name() string {
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -42,15 +44,10 @@ func NewSwayParser() *SwayParser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *SwayParser) ReadContent(path string) error {
|
func (p *SwayParser) ReadContent(path string) error {
|
||||||
expandedPath := os.ExpandEnv(path)
|
expandedPath, err := utils.ExpandPath(path)
|
||||||
expandedPath = filepath.Clean(expandedPath)
|
|
||||||
if strings.HasPrefix(expandedPath, "~") {
|
|
||||||
home, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
expandedPath = filepath.Join(home, expandedPath[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
info, err := os.Stat(expandedPath)
|
info, err := os.Stat(expandedPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -277,7 +278,7 @@ func appendConfig(opts *Options, cfgFile *os.File, checkCmd, fileName string) {
|
|||||||
if _, err := os.Stat(configPath); err != nil {
|
if _, err := os.Stat(configPath); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if checkCmd != "skip" && !commandExists(checkCmd) {
|
if checkCmd != "skip" && !utils.CommandExists(checkCmd) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := os.ReadFile(configPath)
|
data, err := os.ReadFile(configPath)
|
||||||
@@ -293,7 +294,7 @@ func appendTerminalConfig(opts *Options, cfgFile *os.File, tmpDir, checkCmd, fil
|
|||||||
if _, err := os.Stat(configPath); err != nil {
|
if _, err := os.Stat(configPath); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if checkCmd != "skip" && !commandExists(checkCmd) {
|
if checkCmd != "skip" && !utils.CommandExists(checkCmd) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data, err := os.ReadFile(configPath)
|
data, err := os.ReadFile(configPath)
|
||||||
@@ -390,11 +391,6 @@ func extractTOMLSection(content, startMarker, endMarker string) string {
|
|||||||
return content[startIdx : startIdx+endIdx]
|
return content[startIdx : startIdx+endIdx]
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandExists(name string) bool {
|
|
||||||
_, err := exec.LookPath(name)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkMatugenVersion() {
|
func checkMatugenVersion() {
|
||||||
matugenVersionOnce.Do(func() {
|
matugenVersionOnce.Do(func() {
|
||||||
cmd := exec.Command("matugen", "--version")
|
cmd := exec.Command("matugen", "--version")
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,33 +33,70 @@ func NewManagerWithFs(fs afero.Fs) (*Manager, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getPluginsDir() string {
|
func getPluginsDir() string {
|
||||||
configHome := os.Getenv("XDG_CONFIG_HOME")
|
return filepath.Join(utils.XDGConfigHome(), "DankMaterialShell", "plugins")
|
||||||
if configHome == "" {
|
|
||||||
homeDir, err := os.UserHomeDir()
|
|
||||||
if err != nil {
|
|
||||||
return filepath.Join(os.TempDir(), "DankMaterialShell", "plugins")
|
|
||||||
}
|
|
||||||
configHome = filepath.Join(homeDir, ".config")
|
|
||||||
}
|
|
||||||
return filepath.Join(configHome, "DankMaterialShell", "plugins")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) IsInstalled(plugin Plugin) (bool, error) {
|
func (m *Manager) IsInstalled(plugin Plugin) (bool, error) {
|
||||||
pluginPath := filepath.Join(m.pluginsDir, plugin.ID)
|
path, err := m.findInstalledPath(plugin.ID)
|
||||||
exists, err := afero.DirExists(m.fs, pluginPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if exists {
|
return path != "", nil
|
||||||
return true, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
systemPluginPath := filepath.Join("/etc/xdg/quickshell/dms-plugins", plugin.ID)
|
func (m *Manager) findInstalledPath(pluginID string) (string, error) {
|
||||||
systemExists, err := afero.DirExists(m.fs, systemPluginPath)
|
// Check user plugins directory
|
||||||
|
path, err := m.findInDir(m.pluginsDir, pluginID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return "", err
|
||||||
}
|
}
|
||||||
return systemExists, nil
|
if path != "" {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check system plugins directory
|
||||||
|
systemDir := "/etc/xdg/quickshell/dms-plugins"
|
||||||
|
return m.findInDir(systemDir, pluginID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) findInDir(dir, pluginID string) (string, error) {
|
||||||
|
// First, check if folder with exact ID name exists
|
||||||
|
exactPath := filepath.Join(dir, pluginID)
|
||||||
|
if exists, _ := afero.DirExists(m.fs, exactPath); exists {
|
||||||
|
return exactPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan all folders and check plugin.json for matching ID
|
||||||
|
exists, err := afero.DirExists(m.fs, dir)
|
||||||
|
if err != nil || !exists {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := afero.ReadDir(m.fs, dir)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
name := entry.Name()
|
||||||
|
if name == ".repos" || strings.HasSuffix(name, ".meta") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := filepath.Join(dir, name)
|
||||||
|
isPlugin := entry.IsDir() || entry.Mode()&os.ModeSymlink != 0
|
||||||
|
if !isPlugin {
|
||||||
|
if info, err := m.fs.Stat(fullPath); err == nil && info.IsDir() {
|
||||||
|
isPlugin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isPlugin && m.getPluginID(fullPath) == pluginID {
|
||||||
|
return fullPath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Install(plugin Plugin) error {
|
func (m *Manager) Install(plugin Plugin) error {
|
||||||
@@ -151,25 +189,19 @@ func (m *Manager) createSymlink(source, dest string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Update(plugin Plugin) error {
|
func (m *Manager) Update(plugin Plugin) error {
|
||||||
pluginPath := filepath.Join(m.pluginsDir, plugin.ID)
|
pluginPath, err := m.findInstalledPath(plugin.ID)
|
||||||
|
|
||||||
exists, err := afero.DirExists(m.fs, pluginPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to check if plugin exists: %w", err)
|
return fmt.Errorf("failed to find plugin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exists {
|
if pluginPath == "" {
|
||||||
systemPluginPath := filepath.Join("/etc/xdg/quickshell/dms-plugins", plugin.ID)
|
|
||||||
systemExists, err := afero.DirExists(m.fs, systemPluginPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to check if plugin exists: %w", err)
|
|
||||||
}
|
|
||||||
if systemExists {
|
|
||||||
return fmt.Errorf("cannot update system plugin: %s", plugin.Name)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("plugin not installed: %s", plugin.Name)
|
return fmt.Errorf("plugin not installed: %s", plugin.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(pluginPath, "/etc/xdg/quickshell/dms-plugins") {
|
||||||
|
return fmt.Errorf("cannot update system plugin: %s", plugin.Name)
|
||||||
|
}
|
||||||
|
|
||||||
metaPath := pluginPath + ".meta"
|
metaPath := pluginPath + ".meta"
|
||||||
metaExists, err := afero.Exists(m.fs, metaPath)
|
metaExists, err := afero.Exists(m.fs, metaPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -209,25 +241,19 @@ func (m *Manager) Update(plugin Plugin) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) Uninstall(plugin Plugin) error {
|
func (m *Manager) Uninstall(plugin Plugin) error {
|
||||||
pluginPath := filepath.Join(m.pluginsDir, plugin.ID)
|
pluginPath, err := m.findInstalledPath(plugin.ID)
|
||||||
|
|
||||||
exists, err := afero.DirExists(m.fs, pluginPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to check if plugin exists: %w", err)
|
return fmt.Errorf("failed to find plugin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exists {
|
if pluginPath == "" {
|
||||||
systemPluginPath := filepath.Join("/etc/xdg/quickshell/dms-plugins", plugin.ID)
|
|
||||||
systemExists, err := afero.DirExists(m.fs, systemPluginPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("failed to check if plugin exists: %w", err)
|
|
||||||
}
|
|
||||||
if systemExists {
|
|
||||||
return fmt.Errorf("cannot uninstall system plugin: %s", plugin.Name)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("plugin not installed: %s", plugin.Name)
|
return fmt.Errorf("plugin not installed: %s", plugin.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(pluginPath, "/etc/xdg/quickshell/dms-plugins") {
|
||||||
|
return fmt.Errorf("cannot uninstall system plugin: %s", plugin.Name)
|
||||||
|
}
|
||||||
|
|
||||||
metaPath := pluginPath + ".meta"
|
metaPath := pluginPath + ".meta"
|
||||||
metaExists, err := afero.Exists(m.fs, metaPath)
|
metaExists, err := afero.Exists(m.fs, metaPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -369,47 +395,174 @@ func (m *Manager) ListInstalled() ([]string, error) {
|
|||||||
|
|
||||||
// getPluginID reads the plugin.json file and returns the plugin ID
|
// getPluginID reads the plugin.json file and returns the plugin ID
|
||||||
func (m *Manager) getPluginID(pluginPath string) string {
|
func (m *Manager) getPluginID(pluginPath string) string {
|
||||||
|
manifest := m.getPluginManifest(pluginPath)
|
||||||
|
if manifest == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return manifest.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) getPluginManifest(pluginPath string) *pluginManifest {
|
||||||
manifestPath := filepath.Join(pluginPath, "plugin.json")
|
manifestPath := filepath.Join(pluginPath, "plugin.json")
|
||||||
data, err := afero.ReadFile(m.fs, manifestPath)
|
data, err := afero.ReadFile(m.fs, manifestPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var manifest struct {
|
var manifest pluginManifest
|
||||||
ID string `json:"id"`
|
|
||||||
}
|
|
||||||
if err := json.Unmarshal(data, &manifest); err != nil {
|
if err := json.Unmarshal(data, &manifest); err != nil {
|
||||||
return ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return manifest.ID
|
return &manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
type pluginManifest struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) GetPluginsDir() string {
|
func (m *Manager) GetPluginsDir() string {
|
||||||
return m.pluginsDir
|
return m.pluginsDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Manager) UninstallByIDOrName(idOrName string) error {
|
||||||
|
pluginPath, err := m.findInstalledPathByIDOrName(idOrName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pluginPath == "" {
|
||||||
|
return fmt.Errorf("plugin not found: %s", idOrName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(pluginPath, "/etc/xdg/quickshell/dms-plugins") {
|
||||||
|
return fmt.Errorf("cannot uninstall system plugin: %s", idOrName)
|
||||||
|
}
|
||||||
|
|
||||||
|
metaPath := pluginPath + ".meta"
|
||||||
|
metaExists, _ := afero.Exists(m.fs, metaPath)
|
||||||
|
|
||||||
|
if metaExists {
|
||||||
|
if err := m.fs.Remove(pluginPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to remove symlink: %w", err)
|
||||||
|
}
|
||||||
|
if err := m.fs.Remove(metaPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to remove metadata: %w", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err := m.fs.RemoveAll(pluginPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to remove plugin: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) UpdateByIDOrName(idOrName string) error {
|
||||||
|
pluginPath, err := m.findInstalledPathByIDOrName(idOrName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if pluginPath == "" {
|
||||||
|
return fmt.Errorf("plugin not found: %s", idOrName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(pluginPath, "/etc/xdg/quickshell/dms-plugins") {
|
||||||
|
return fmt.Errorf("cannot update system plugin: %s", idOrName)
|
||||||
|
}
|
||||||
|
|
||||||
|
metaPath := pluginPath + ".meta"
|
||||||
|
metaExists, _ := afero.Exists(m.fs, metaPath)
|
||||||
|
|
||||||
|
if metaExists {
|
||||||
|
// Plugin is from monorepo, but we don't know the repo URL without registry
|
||||||
|
// Just try to pull from existing .git in the symlink target
|
||||||
|
return fmt.Errorf("cannot update monorepo plugin without registry info: %s", idOrName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standalone plugin - just pull
|
||||||
|
if err := m.gitClient.Pull(pluginPath); err != nil {
|
||||||
|
return fmt.Errorf("failed to update plugin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) findInstalledPathByIDOrName(idOrName string) (string, error) {
|
||||||
|
path, err := m.findInDirByIDOrName(m.pluginsDir, idOrName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if path != "" {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
systemDir := "/etc/xdg/quickshell/dms-plugins"
|
||||||
|
return m.findInDirByIDOrName(systemDir, idOrName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) findInDirByIDOrName(dir, idOrName string) (string, error) {
|
||||||
|
// Check exact folder name match first
|
||||||
|
exactPath := filepath.Join(dir, idOrName)
|
||||||
|
if exists, _ := afero.DirExists(m.fs, exactPath); exists {
|
||||||
|
return exactPath, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := afero.DirExists(m.fs, dir)
|
||||||
|
if err != nil || !exists {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entries, err := afero.ReadDir(m.fs, dir)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, entry := range entries {
|
||||||
|
name := entry.Name()
|
||||||
|
if name == ".repos" || strings.HasSuffix(name, ".meta") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fullPath := filepath.Join(dir, name)
|
||||||
|
isPlugin := entry.IsDir() || entry.Mode()&os.ModeSymlink != 0
|
||||||
|
if !isPlugin {
|
||||||
|
if info, err := m.fs.Stat(fullPath); err == nil && info.IsDir() {
|
||||||
|
isPlugin = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isPlugin {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest := m.getPluginManifest(fullPath)
|
||||||
|
if manifest == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifest.ID == idOrName || manifest.Name == idOrName {
|
||||||
|
return fullPath, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
func (m *Manager) HasUpdates(pluginID string, plugin Plugin) (bool, error) {
|
func (m *Manager) HasUpdates(pluginID string, plugin Plugin) (bool, error) {
|
||||||
pluginPath := filepath.Join(m.pluginsDir, pluginID)
|
pluginPath, err := m.findInstalledPath(pluginID)
|
||||||
|
|
||||||
exists, err := afero.DirExists(m.fs, pluginPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to check if plugin exists: %w", err)
|
return false, fmt.Errorf("failed to find plugin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !exists {
|
if pluginPath == "" {
|
||||||
systemPluginPath := filepath.Join("/etc/xdg/quickshell/dms-plugins", pluginID)
|
|
||||||
systemExists, err := afero.DirExists(m.fs, systemPluginPath)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("failed to check system plugin: %w", err)
|
|
||||||
}
|
|
||||||
if systemExists {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return false, fmt.Errorf("plugin not installed: %s", pluginID)
|
return false, fmt.Errorf("plugin not installed: %s", pluginID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if there's a .meta file (plugin installed from a monorepo)
|
if strings.HasPrefix(pluginPath, "/etc/xdg/quickshell/dms-plugins") {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
metaPath := pluginPath + ".meta"
|
metaPath := pluginPath + ".meta"
|
||||||
metaExists, err := afero.Exists(m.fs, metaPath)
|
metaExists, err := afero.Exists(m.fs, metaPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package plugins
|
|||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FuzzySearch(query string, plugins []Plugin) []Plugin {
|
func FuzzySearch(query string, plugins []Plugin) []Plugin {
|
||||||
@@ -11,18 +13,12 @@ func FuzzySearch(query string, plugins []Plugin) []Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
queryLower := strings.ToLower(query)
|
queryLower := strings.ToLower(query)
|
||||||
var results []Plugin
|
return utils.Filter(plugins, func(p Plugin) bool {
|
||||||
|
return fuzzyMatch(queryLower, strings.ToLower(p.Name)) ||
|
||||||
for _, plugin := range plugins {
|
fuzzyMatch(queryLower, strings.ToLower(p.Category)) ||
|
||||||
if fuzzyMatch(queryLower, strings.ToLower(plugin.Name)) ||
|
fuzzyMatch(queryLower, strings.ToLower(p.Description)) ||
|
||||||
fuzzyMatch(queryLower, strings.ToLower(plugin.Category)) ||
|
fuzzyMatch(queryLower, strings.ToLower(p.Author))
|
||||||
fuzzyMatch(queryLower, strings.ToLower(plugin.Description)) ||
|
})
|
||||||
fuzzyMatch(queryLower, strings.ToLower(plugin.Author)) {
|
|
||||||
results = append(results, plugin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fuzzyMatch(query, text string) bool {
|
func fuzzyMatch(query, text string) bool {
|
||||||
@@ -39,57 +35,34 @@ func FilterByCategory(category string, plugins []Plugin) []Plugin {
|
|||||||
if category == "" {
|
if category == "" {
|
||||||
return plugins
|
return plugins
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []Plugin
|
|
||||||
categoryLower := strings.ToLower(category)
|
categoryLower := strings.ToLower(category)
|
||||||
|
return utils.Filter(plugins, func(p Plugin) bool {
|
||||||
for _, plugin := range plugins {
|
return strings.ToLower(p.Category) == categoryLower
|
||||||
if strings.ToLower(plugin.Category) == categoryLower {
|
})
|
||||||
results = append(results, plugin)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func FilterByCompositor(compositor string, plugins []Plugin) []Plugin {
|
func FilterByCompositor(compositor string, plugins []Plugin) []Plugin {
|
||||||
if compositor == "" {
|
if compositor == "" {
|
||||||
return plugins
|
return plugins
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []Plugin
|
|
||||||
compositorLower := strings.ToLower(compositor)
|
compositorLower := strings.ToLower(compositor)
|
||||||
|
return utils.Filter(plugins, func(p Plugin) bool {
|
||||||
for _, plugin := range plugins {
|
return utils.Any(p.Compositors, func(c string) bool {
|
||||||
for _, comp := range plugin.Compositors {
|
return strings.ToLower(c) == compositorLower
|
||||||
if strings.ToLower(comp) == compositorLower {
|
})
|
||||||
results = append(results, plugin)
|
})
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func FilterByCapability(capability string, plugins []Plugin) []Plugin {
|
func FilterByCapability(capability string, plugins []Plugin) []Plugin {
|
||||||
if capability == "" {
|
if capability == "" {
|
||||||
return plugins
|
return plugins
|
||||||
}
|
}
|
||||||
|
|
||||||
var results []Plugin
|
|
||||||
capabilityLower := strings.ToLower(capability)
|
capabilityLower := strings.ToLower(capability)
|
||||||
|
return utils.Filter(plugins, func(p Plugin) bool {
|
||||||
for _, plugin := range plugins {
|
return utils.Any(p.Capabilities, func(c string) bool {
|
||||||
for _, cap := range plugin.Capabilities {
|
return strings.ToLower(c) == capabilityLower
|
||||||
if strings.ToLower(cap) == capabilityLower {
|
})
|
||||||
results = append(results, plugin)
|
})
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func SortByFirstParty(plugins []Plugin) []Plugin {
|
func SortByFirstParty(plugins []Plugin) []Plugin {
|
||||||
@@ -103,3 +76,13 @@ func SortByFirstParty(plugins []Plugin) []Plugin {
|
|||||||
})
|
})
|
||||||
return plugins
|
return plugins
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func FindByIDOrName(idOrName string, plugins []Plugin) *Plugin {
|
||||||
|
if p, found := utils.Find(plugins, func(p Plugin) bool { return p.ID == idOrName }); found {
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
if p, found := utils.Find(plugins, func(p Plugin) bool { return p.Name == idOrName }); found {
|
||||||
|
return &p
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,10 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func BufferToImage(buf *ShmBuffer) *image.RGBA {
|
func BufferToImage(buf *ShmBuffer) *image.RGBA {
|
||||||
@@ -116,68 +119,28 @@ func GetOutputDir() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getXDGPicturesDir() string {
|
func getXDGPicturesDir() string {
|
||||||
configDir := os.Getenv("XDG_CONFIG_HOME")
|
userDirsFile := filepath.Join(utils.XDGConfigHome(), "user-dirs.dirs")
|
||||||
if configDir == "" {
|
|
||||||
home := os.Getenv("HOME")
|
|
||||||
if home == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
configDir = filepath.Join(home, ".config")
|
|
||||||
}
|
|
||||||
|
|
||||||
userDirsFile := filepath.Join(configDir, "user-dirs.dirs")
|
|
||||||
data, err := os.ReadFile(userDirsFile)
|
data, err := os.ReadFile(userDirsFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, line := range splitLines(string(data)) {
|
for _, line := range strings.Split(string(data), "\n") {
|
||||||
if len(line) == 0 || line[0] == '#' {
|
if len(line) == 0 || line[0] == '#' {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
const prefix = "XDG_PICTURES_DIR="
|
const prefix = "XDG_PICTURES_DIR="
|
||||||
if len(line) > len(prefix) && line[:len(prefix)] == prefix {
|
if !strings.HasPrefix(line, prefix) {
|
||||||
path := line[len(prefix):]
|
continue
|
||||||
path = trimQuotes(path)
|
|
||||||
path = expandHome(path)
|
|
||||||
return path
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
path := strings.Trim(line[len(prefix):], "\"")
|
||||||
|
expanded, err := utils.ExpandPath(path)
|
||||||
|
if err != nil {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
return expanded
|
||||||
func splitLines(s string) []string {
|
|
||||||
var lines []string
|
|
||||||
start := 0
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
if s[i] == '\n' {
|
|
||||||
lines = append(lines, s[start:i])
|
|
||||||
start = i + 1
|
|
||||||
}
|
}
|
||||||
}
|
return ""
|
||||||
if start < len(s) {
|
|
||||||
lines = append(lines, s[start:])
|
|
||||||
}
|
|
||||||
return lines
|
|
||||||
}
|
|
||||||
|
|
||||||
func trimQuotes(s string) string {
|
|
||||||
if len(s) >= 2 && s[0] == '"' && s[len(s)-1] == '"' {
|
|
||||||
return s[1 : len(s)-1]
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func expandHome(path string) string {
|
|
||||||
if len(path) >= 5 && path[:5] == "$HOME" {
|
|
||||||
home := os.Getenv("HOME")
|
|
||||||
return home + path[5:]
|
|
||||||
}
|
|
||||||
if len(path) >= 1 && path[0] == '~' {
|
|
||||||
home := os.Getenv("HOME")
|
|
||||||
return home + path[1:]
|
|
||||||
}
|
|
||||||
return path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func WriteToFile(buf *ShmBuffer, path string, format Format, quality int) error {
|
func WriteToFile(buf *ShmBuffer, path string, format Format, quality int) error {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ThemeColors struct {
|
type ThemeColors struct {
|
||||||
@@ -72,15 +74,7 @@ func loadColorsFile() *ColorScheme {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getColorsFilePath() string {
|
func getColorsFilePath() string {
|
||||||
cacheDir := os.Getenv("XDG_CACHE_HOME")
|
return filepath.Join(utils.XDGCacheHome(), "DankMaterialShell", "dms-colors.json")
|
||||||
if cacheDir == "" {
|
|
||||||
home := os.Getenv("HOME")
|
|
||||||
if home == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
cacheDir = filepath.Join(home, ".cache")
|
|
||||||
}
|
|
||||||
return filepath.Join(cacheDir, "DankMaterialShell", "dms-colors.json")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func isLightMode() bool {
|
func isLightMode() bool {
|
||||||
|
|||||||
@@ -15,52 +15,49 @@ func HandleUninstall(conn net.Conn, req models.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
registry, err := plugins.NewRegistry()
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginList, err := registry.List()
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list plugins: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var plugin *plugins.Plugin
|
|
||||||
for _, p := range pluginList {
|
|
||||||
if p.Name == name {
|
|
||||||
plugin = &p
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if plugin == nil {
|
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not found: %s", name))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
manager, err := plugins.NewManager()
|
manager, err := plugins.NewManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First try to find in registry (by name or ID)
|
||||||
|
registry, err := plugins.NewRegistry()
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginList, _ := registry.List()
|
||||||
|
plugin := plugins.FindByIDOrName(name, pluginList)
|
||||||
|
|
||||||
|
// If found in registry, use that
|
||||||
|
if plugin != nil {
|
||||||
installed, err := manager.IsInstalled(*plugin)
|
installed, err := manager.IsInstalled(*plugin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if plugin is installed: %v", err))
|
models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if plugin is installed: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !installed {
|
if !installed {
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name))
|
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := manager.Uninstall(*plugin); err != nil {
|
if err := manager.Uninstall(*plugin); err != nil {
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to uninstall plugin: %v", err))
|
models.RespondError(conn, req.ID, fmt.Sprintf("failed to uninstall plugin: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
models.Respond(conn, req.ID, SuccessResult{
|
||||||
|
Success: true,
|
||||||
|
Message: fmt.Sprintf("plugin uninstalled: %s", plugin.Name),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not in registry - try to find and uninstall from installed plugins directly
|
||||||
|
if err := manager.UninstallByIDOrName(name); err != nil {
|
||||||
|
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not found: %s", name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{
|
models.Respond(conn, req.ID, SuccessResult{
|
||||||
Success: true,
|
Success: true,
|
||||||
|
|||||||
@@ -15,52 +15,47 @@ func HandleUpdate(conn net.Conn, req models.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
registry, err := plugins.NewRegistry()
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginList, err := registry.List()
|
|
||||||
if err != nil {
|
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to list plugins: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var plugin *plugins.Plugin
|
|
||||||
for _, p := range pluginList {
|
|
||||||
if p.Name == name {
|
|
||||||
plugin = &p
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if plugin == nil {
|
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not found: %s", name))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
manager, err := plugins.NewManager()
|
manager, err := plugins.NewManager()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registry, err := plugins.NewRegistry()
|
||||||
|
if err != nil {
|
||||||
|
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginList, _ := registry.List()
|
||||||
|
plugin := plugins.FindByIDOrName(name, pluginList)
|
||||||
|
|
||||||
|
if plugin != nil {
|
||||||
installed, err := manager.IsInstalled(*plugin)
|
installed, err := manager.IsInstalled(*plugin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if plugin is installed: %v", err))
|
models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if plugin is installed: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !installed {
|
if !installed {
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name))
|
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := manager.Update(*plugin); err != nil {
|
if err := manager.Update(*plugin); err != nil {
|
||||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to update plugin: %v", err))
|
models.RespondError(conn, req.ID, fmt.Sprintf("failed to update plugin: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
models.Respond(conn, req.ID, SuccessResult{
|
||||||
|
Success: true,
|
||||||
|
Message: fmt.Sprintf("plugin updated: %s", plugin.Name),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not in registry - try to update from installed plugins directly
|
||||||
|
if err := manager.UpdateByIDOrName(name); err != nil {
|
||||||
|
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not found: %s", name))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
models.Respond(conn, req.ID, SuccessResult{
|
models.Respond(conn, req.ID, SuccessResult{
|
||||||
Success: true,
|
Success: true,
|
||||||
|
|||||||
8
core/internal/utils/exec.go
Normal file
8
core/internal/utils/exec.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "os/exec"
|
||||||
|
|
||||||
|
func CommandExists(cmd string) bool {
|
||||||
|
_, err := exec.LookPath(cmd)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
52
core/internal/utils/paths.go
Normal file
52
core/internal/utils/paths.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func XDGConfigHome() string {
|
||||||
|
if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
|
||||||
|
return configHome
|
||||||
|
}
|
||||||
|
if home, err := os.UserHomeDir(); err == nil {
|
||||||
|
return filepath.Join(home, ".config")
|
||||||
|
}
|
||||||
|
return filepath.Join(os.TempDir(), ".config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func XDGCacheHome() string {
|
||||||
|
if cacheHome := os.Getenv("XDG_CACHE_HOME"); cacheHome != "" {
|
||||||
|
return cacheHome
|
||||||
|
}
|
||||||
|
if home, err := os.UserHomeDir(); err == nil {
|
||||||
|
return filepath.Join(home, ".cache")
|
||||||
|
}
|
||||||
|
return filepath.Join(os.TempDir(), ".cache")
|
||||||
|
}
|
||||||
|
|
||||||
|
func XDGDataHome() string {
|
||||||
|
if dataHome := os.Getenv("XDG_DATA_HOME"); dataHome != "" {
|
||||||
|
return dataHome
|
||||||
|
}
|
||||||
|
if home, err := os.UserHomeDir(); err == nil {
|
||||||
|
return filepath.Join(home, ".local", "share")
|
||||||
|
}
|
||||||
|
return filepath.Join(os.TempDir(), ".local", "share")
|
||||||
|
}
|
||||||
106
core/internal/utils/paths_test.go
Normal file
106
core/internal/utils/paths_test.go
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExpandPathTilde(t *testing.T) {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no home directory")
|
||||||
|
}
|
||||||
|
result, err := ExpandPath("~/test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
expected := filepath.Join(home, "test")
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("expected %s, got %s", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpandPathEnvVar(t *testing.T) {
|
||||||
|
t.Setenv("TEST_PATH_VAR", "/custom/path")
|
||||||
|
result, err := ExpandPath("$TEST_PATH_VAR/subdir")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if result != "/custom/path/subdir" {
|
||||||
|
t.Errorf("expected /custom/path/subdir, got %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpandPathAbsolute(t *testing.T) {
|
||||||
|
result, err := ExpandPath("/absolute/path")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if result != "/absolute/path" {
|
||||||
|
t.Errorf("expected /absolute/path, got %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXDGConfigHomeDefault(t *testing.T) {
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", "")
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no home directory")
|
||||||
|
}
|
||||||
|
result := XDGConfigHome()
|
||||||
|
expected := filepath.Join(home, ".config")
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("expected %s, got %s", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXDGConfigHomeCustom(t *testing.T) {
|
||||||
|
t.Setenv("XDG_CONFIG_HOME", "/custom/config")
|
||||||
|
result := XDGConfigHome()
|
||||||
|
if result != "/custom/config" {
|
||||||
|
t.Errorf("expected /custom/config, got %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXDGCacheHomeDefault(t *testing.T) {
|
||||||
|
t.Setenv("XDG_CACHE_HOME", "")
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no home directory")
|
||||||
|
}
|
||||||
|
result := XDGCacheHome()
|
||||||
|
expected := filepath.Join(home, ".cache")
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("expected %s, got %s", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXDGCacheHomeCustom(t *testing.T) {
|
||||||
|
t.Setenv("XDG_CACHE_HOME", "/custom/cache")
|
||||||
|
result := XDGCacheHome()
|
||||||
|
if result != "/custom/cache" {
|
||||||
|
t.Errorf("expected /custom/cache, got %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXDGDataHomeDefault(t *testing.T) {
|
||||||
|
t.Setenv("XDG_DATA_HOME", "")
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
t.Skip("no home directory")
|
||||||
|
}
|
||||||
|
result := XDGDataHome()
|
||||||
|
expected := filepath.Join(home, ".local", "share")
|
||||||
|
if result != expected {
|
||||||
|
t.Errorf("expected %s, got %s", expected, result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestXDGDataHomeCustom(t *testing.T) {
|
||||||
|
t.Setenv("XDG_DATA_HOME", "/custom/data")
|
||||||
|
result := XDGDataHome()
|
||||||
|
if result != "/custom/data" {
|
||||||
|
t.Errorf("expected /custom/data, got %s", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
56
core/internal/utils/slices.go
Normal file
56
core/internal/utils/slices.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
func Filter[T any](items []T, predicate func(T) bool) []T {
|
||||||
|
var result []T
|
||||||
|
for _, item := range items {
|
||||||
|
if predicate(item) {
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func Find[T any](items []T, predicate func(T) bool) (T, bool) {
|
||||||
|
for _, item := range items {
|
||||||
|
if predicate(item) {
|
||||||
|
return item, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var zero T
|
||||||
|
return zero, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func Map[T, U any](items []T, transform func(T) U) []U {
|
||||||
|
result := make([]U, len(items))
|
||||||
|
for i, item := range items {
|
||||||
|
result[i] = transform(item)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func Contains[T comparable](items []T, target T) bool {
|
||||||
|
for _, item := range items {
|
||||||
|
if item == target {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func Any[T any](items []T, predicate func(T) bool) bool {
|
||||||
|
for _, item := range items {
|
||||||
|
if predicate(item) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func All[T any](items []T, predicate func(T) bool) bool {
|
||||||
|
for _, item := range items {
|
||||||
|
if !predicate(item) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
72
core/internal/utils/slices_test.go
Normal file
72
core/internal/utils/slices_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFilter(t *testing.T) {
|
||||||
|
nums := []int{1, 2, 3, 4, 5}
|
||||||
|
evens := Filter(nums, func(n int) bool { return n%2 == 0 })
|
||||||
|
if len(evens) != 2 || evens[0] != 2 || evens[1] != 4 {
|
||||||
|
t.Errorf("expected [2, 4], got %v", evens)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFilterEmpty(t *testing.T) {
|
||||||
|
result := Filter([]int{1, 2, 3}, func(n int) bool { return n > 10 })
|
||||||
|
if len(result) != 0 {
|
||||||
|
t.Errorf("expected empty slice, got %v", result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFind(t *testing.T) {
|
||||||
|
nums := []int{1, 2, 3, 4, 5}
|
||||||
|
val, found := Find(nums, func(n int) bool { return n == 3 })
|
||||||
|
if !found || val != 3 {
|
||||||
|
t.Errorf("expected 3, got %v (found=%v)", val, found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindNotFound(t *testing.T) {
|
||||||
|
nums := []int{1, 2, 3}
|
||||||
|
val, found := Find(nums, func(n int) bool { return n == 99 })
|
||||||
|
if found || val != 0 {
|
||||||
|
t.Errorf("expected zero value not found, got %v (found=%v)", val, found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMap(t *testing.T) {
|
||||||
|
nums := []int{1, 2, 3}
|
||||||
|
doubled := Map(nums, func(n int) int { return n * 2 })
|
||||||
|
if len(doubled) != 3 || doubled[0] != 2 || doubled[1] != 4 || doubled[2] != 6 {
|
||||||
|
t.Errorf("expected [2, 4, 6], got %v", doubled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapTypeConversion(t *testing.T) {
|
||||||
|
nums := []int{1, 2, 3}
|
||||||
|
strs := Map(nums, func(n int) string { return string(rune('a' + n - 1)) })
|
||||||
|
if strs[0] != "a" || strs[1] != "b" || strs[2] != "c" {
|
||||||
|
t.Errorf("expected [a, b, c], got %v", strs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestContains(t *testing.T) {
|
||||||
|
nums := []int{1, 2, 3}
|
||||||
|
if !Contains(nums, 2) {
|
||||||
|
t.Error("expected to contain 2")
|
||||||
|
}
|
||||||
|
if Contains(nums, 99) {
|
||||||
|
t.Error("expected not to contain 99")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAny(t *testing.T) {
|
||||||
|
nums := []int{1, 2, 3, 4, 5}
|
||||||
|
if !Any(nums, func(n int) bool { return n > 4 }) {
|
||||||
|
t.Error("expected any > 4")
|
||||||
|
}
|
||||||
|
if Any(nums, func(n int) bool { return n > 10 }) {
|
||||||
|
t.Error("expected none > 10")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
mocks_version "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/version"
|
mocks_version "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/version"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCompareVersions(t *testing.T) {
|
func TestCompareVersions(t *testing.T) {
|
||||||
@@ -150,7 +151,7 @@ func TestGetCurrentDMSVersion_NotInstalled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCurrentDMSVersion_GitTag(t *testing.T) {
|
func TestGetCurrentDMSVersion_GitTag(t *testing.T) {
|
||||||
if !commandExists("git") {
|
if !utils.CommandExists("git") {
|
||||||
t.Skip("git not available")
|
t.Skip("git not available")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +184,7 @@ func TestGetCurrentDMSVersion_GitTag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetCurrentDMSVersion_GitBranch(t *testing.T) {
|
func TestGetCurrentDMSVersion_GitBranch(t *testing.T) {
|
||||||
if !commandExists("git") {
|
if !utils.CommandExists("git") {
|
||||||
t.Skip("git not available")
|
t.Skip("git not available")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -314,11 +315,6 @@ func TestVersionInfo_HasUpdate_Tag(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func commandExists(cmd string) bool {
|
|
||||||
_, err := exec.LookPath(cmd)
|
|
||||||
return err == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetLatestDMSVersion_FallbackParsing(t *testing.T) {
|
func TestGetLatestDMSVersion_FallbackParsing(t *testing.T) {
|
||||||
jsonResponse := `{
|
jsonResponse := `{
|
||||||
"tag_name": "v0.1.17",
|
"tag_name": "v0.1.17",
|
||||||
|
|||||||
@@ -890,18 +890,52 @@ Item {
|
|||||||
id: rowLayout
|
id: rowLayout
|
||||||
Row {
|
Row {
|
||||||
spacing: 4
|
spacing: 4
|
||||||
visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex
|
visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex || loadedHasIcon
|
||||||
StyledText {
|
|
||||||
topPadding: 2
|
Item {
|
||||||
rightPadding: isActive ? 4 : 0
|
visible: loadedHasIcon && loadedIconData?.type === "icon"
|
||||||
visible: SettingsData.showWorkspaceIndex
|
width: wsIcon.width + (isActive && loadedIcons.length > 0 ? 4 : 0)
|
||||||
text: {
|
height: 18
|
||||||
return root.getWorkspaceIndex(modelData);
|
|
||||||
|
DankIcon {
|
||||||
|
id: wsIcon
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
name: loadedIconData?.value ?? ""
|
||||||
|
size: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
||||||
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||||
|
weight: (isActive && !isPlaceholder) ? 500 : 400
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
visible: loadedHasIcon && loadedIconData?.type === "text"
|
||||||
|
width: wsText.implicitWidth + (isActive && loadedIcons.length > 0 ? 4 : 0)
|
||||||
|
height: 18
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: wsText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: loadedIconData?.value ?? ""
|
||||||
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||||
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
||||||
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
visible: SettingsData.showWorkspaceIndex && !loadedHasIcon
|
||||||
|
width: wsIndexText.implicitWidth + (isActive && loadedIcons.length > 0 ? 4 : 0)
|
||||||
|
height: 18
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: wsIndexText
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
text: root.getWorkspaceIndex(modelData)
|
||||||
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||||
|
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
||||||
|
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
@@ -973,7 +1007,25 @@ Item {
|
|||||||
id: columnLayout
|
id: columnLayout
|
||||||
Column {
|
Column {
|
||||||
spacing: 4
|
spacing: 4
|
||||||
visible: loadedIcons.length > 0
|
visible: loadedIcons.length > 0 || loadedHasIcon
|
||||||
|
|
||||||
|
DankIcon {
|
||||||
|
visible: loadedHasIcon && loadedIconData?.type === "icon"
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
name: loadedIconData?.value ?? ""
|
||||||
|
size: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
||||||
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||||
|
weight: (isActive && !isPlaceholder) ? 500 : 400
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
visible: loadedHasIcon && loadedIconData?.type === "text"
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: loadedIconData?.value ?? ""
|
||||||
|
color: (isActive || isUrgent) ? Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.95) : isPlaceholder ? Theme.surfaceTextAlpha : Theme.surfaceTextMedium
|
||||||
|
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
||||||
|
font.weight: (isActive && !isPlaceholder) ? Font.DemiBold : Font.Normal
|
||||||
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: ScriptModel {
|
model: ScriptModel {
|
||||||
|
|||||||
Reference in New Issue
Block a user