mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-01-24 13:32:50 -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")
|
||||
exponent, _ := cmd.Flags().GetFloat64("exponent")
|
||||
|
||||
// For backlight/leds devices, try logind backend first (requires D-Bus connection)
|
||||
parts := strings.SplitN(deviceID, ":", 2)
|
||||
if len(parts) == 2 && (parts[0] == "backlight" || parts[0] == "leds") {
|
||||
subsystem := parts[0]
|
||||
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
|
||||
} else {
|
||||
log.Debugf("logind.SetBrightness failed: %v", err)
|
||||
}
|
||||
} else {
|
||||
log.Debugf("sysfs.GetDeviceByID failed: %v", err)
|
||||
}
|
||||
}
|
||||
if ok := tryLogindBrightness(parts[0], parts[1], deviceID, percent, exponential, exponent); ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to direct sysfs (requires write permissions)
|
||||
sysfs, err := brightness.NewSysfsBackend()
|
||||
if 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)
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
printASCII()
|
||||
fmt.Printf("%s\n", formatVersion(Version))
|
||||
@@ -408,49 +426,70 @@ func uninstallPluginCLI(idOrName string) error {
|
||||
return fmt.Errorf("failed to create registry: %w", err)
|
||||
}
|
||||
|
||||
pluginList, err := registry.List()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list plugins: %w", err)
|
||||
}
|
||||
pluginList, _ := registry.List()
|
||||
plugin := plugins.FindByIDOrName(idOrName, pluginList)
|
||||
|
||||
// First, try to find by ID (preferred method)
|
||||
var plugin *plugins.Plugin
|
||||
for _, p := range pluginList {
|
||||
if p.ID == idOrName {
|
||||
plugin = &p
|
||||
break
|
||||
if plugin != nil {
|
||||
installed, err := manager.IsInstalled(*plugin)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check install status: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to name for backward compatibility
|
||||
if plugin == nil {
|
||||
for _, p := range pluginList {
|
||||
if p.Name == idOrName {
|
||||
plugin = &p
|
||||
break
|
||||
}
|
||||
if !installed {
|
||||
return fmt.Errorf("plugin not installed: %s", plugin.Name)
|
||||
}
|
||||
|
||||
fmt.Printf("Uninstalling plugin: %s (ID: %s)\n", plugin.Name, plugin.ID)
|
||||
if err := manager.Uninstall(*plugin); err != nil {
|
||||
return fmt.Errorf("failed to uninstall plugin: %w", err)
|
||||
}
|
||||
fmt.Printf("Plugin uninstalled successfully: %s\n", plugin.Name)
|
||||
return nil
|
||||
}
|
||||
|
||||
if plugin == nil {
|
||||
return fmt.Errorf("plugin not found: %s", idOrName)
|
||||
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
|
||||
}
|
||||
|
||||
installed, err := manager.IsInstalled(*plugin)
|
||||
func updatePluginCLI(idOrName string) error {
|
||||
manager, err := plugins.NewManager()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to check install status: %w", err)
|
||||
return fmt.Errorf("failed to create manager: %w", err)
|
||||
}
|
||||
|
||||
if !installed {
|
||||
return fmt.Errorf("plugin not installed: %s", plugin.Name)
|
||||
registry, err := plugins.NewRegistry()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create registry: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("Uninstalling plugin: %s (ID: %s)\n", plugin.Name, plugin.ID)
|
||||
if err := manager.Uninstall(*plugin); err != nil {
|
||||
return fmt.Errorf("failed to uninstall plugin: %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("Plugin uninstalled successfully: %s\n", plugin.Name)
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -121,10 +122,10 @@ func updateArchLinux() error {
|
||||
var helper string
|
||||
var updateCmd *exec.Cmd
|
||||
|
||||
if commandExists("yay") {
|
||||
if utils.CommandExists("yay") {
|
||||
helper = "yay"
|
||||
updateCmd = exec.Command("yay", "-S", packageName)
|
||||
} else if commandExists("paru") {
|
||||
} else if utils.CommandExists("paru") {
|
||||
helper = "paru"
|
||||
updateCmd = exec.Command("paru", "-S", packageName)
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
@@ -448,7 +449,7 @@ func enableGreeter() error {
|
||||
fmt.Println("Detecting installed compositors...")
|
||||
compositors := greeter.DetectCompositors()
|
||||
|
||||
if commandExists("sway") {
|
||||
if utils.CommandExists("sway") {
|
||||
compositors = append(compositors, "sway")
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ func init() {
|
||||
updateCmd.AddCommand(updateCheckCmd)
|
||||
|
||||
// Add subcommands to plugins
|
||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd)
|
||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
||||
|
||||
// Add common commands to root
|
||||
rootCmd.AddCommand(getCommonCommands()...)
|
||||
|
||||
@@ -21,7 +21,7 @@ func init() {
|
||||
greeterCmd.AddCommand(greeterSyncCmd, greeterEnableCmd, greeterStatusCmd)
|
||||
|
||||
// Add subcommands to plugins
|
||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd)
|
||||
pluginsCmd.AddCommand(pluginsBrowseCmd, pluginsListCmd, pluginsInstallCmd, pluginsUninstallCmd, pluginsUpdateCmd)
|
||||
|
||||
// Add common commands to root
|
||||
rootCmd.AddCommand(getCommonCommands()...)
|
||||
|
||||
@@ -104,7 +104,6 @@ func getAllDMSPIDs() []int {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if the child process is still alive
|
||||
proc, err := os.FindProcess(childPID)
|
||||
if err != nil {
|
||||
os.Remove(pidFile)
|
||||
@@ -112,18 +111,15 @@ func getAllDMSPIDs() []int {
|
||||
}
|
||||
|
||||
if err := proc.Signal(syscall.Signal(0)); err != nil {
|
||||
// Process is dead, remove stale PID file
|
||||
os.Remove(pidFile)
|
||||
continue
|
||||
}
|
||||
|
||||
pids = append(pids, childPID)
|
||||
|
||||
// Also get the parent PID from the filename
|
||||
parentPIDStr := strings.TrimPrefix(entry.Name(), "danklinux-")
|
||||
parentPIDStr = strings.TrimSuffix(parentPIDStr, ".pid")
|
||||
if parentPID, err := strconv.Atoi(parentPIDStr); err == nil {
|
||||
// Check if parent is still alive
|
||||
if parentProc, err := os.FindProcess(parentPID); err == nil {
|
||||
if err := parentProc.Signal(syscall.Signal(0)); err == nil {
|
||||
pids = append(pids, parentPID)
|
||||
@@ -225,7 +221,6 @@ func runShellInteractive(session bool) {
|
||||
return
|
||||
}
|
||||
|
||||
// All other signals: clean shutdown
|
||||
log.Infof("\nReceived signal %v, shutting down...", sig)
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
@@ -282,7 +277,6 @@ func restartShell() {
|
||||
}
|
||||
|
||||
func killShell() {
|
||||
// Get all tracked DMS PIDs from PID files
|
||||
pids := getAllDMSPIDs()
|
||||
|
||||
if len(pids) == 0 {
|
||||
@@ -293,14 +287,12 @@ func killShell() {
|
||||
currentPid := os.Getpid()
|
||||
uniquePids := make(map[int]bool)
|
||||
|
||||
// Deduplicate and filter out current process
|
||||
for _, pid := range pids {
|
||||
if pid != currentPid {
|
||||
uniquePids[pid] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Kill all tracked processes
|
||||
for pid := range uniquePids {
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
@@ -308,7 +300,6 @@ func killShell() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if process is still alive before killing
|
||||
if err := proc.Signal(syscall.Signal(0)); err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -320,7 +311,6 @@ func killShell() {
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up any remaining PID files
|
||||
dir := getRuntimeDir()
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
@@ -337,7 +327,6 @@ func killShell() {
|
||||
|
||||
func runShellDaemon(session bool) {
|
||||
isSessionManaged = session
|
||||
// Check if this is the daemon child process by looking for the hidden flag
|
||||
isDaemonChild := false
|
||||
for _, arg := range os.Args {
|
||||
if arg == "--daemon-child" {
|
||||
|
||||
@@ -6,12 +6,6 @@ import (
|
||||
"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) {
|
||||
path, err := exec.LookPath(cmd)
|
||||
if err != nil {
|
||||
|
||||
@@ -5,19 +5,14 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
// LocateDMSConfig searches for DMS installation following XDG Base Directory specification
|
||||
func LocateDMSConfig() (string, error) {
|
||||
var primaryPaths []string
|
||||
|
||||
configHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
if configHome == "" {
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
configHome = filepath.Join(homeDir, ".config")
|
||||
}
|
||||
}
|
||||
|
||||
configHome := utils.XDGConfigHome()
|
||||
if configHome != "" {
|
||||
primaryPaths = append(primaryPaths, filepath.Join(configHome, "quickshell", "dms"))
|
||||
}
|
||||
|
||||
@@ -113,13 +113,14 @@ func RGBToHSV(rgb RGB) HSV {
|
||||
delta := max - min
|
||||
|
||||
var h float64
|
||||
if delta == 0 {
|
||||
switch {
|
||||
case delta == 0:
|
||||
h = 0
|
||||
} else if max == rgb.R {
|
||||
case max == rgb.R:
|
||||
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
|
||||
} else {
|
||||
default:
|
||||
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 {
|
||||
status := deps.StatusMissing
|
||||
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,
|
||||
}
|
||||
return a.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", a.packageInstalled("xdg-desktop-portal-gtk"))
|
||||
}
|
||||
|
||||
func (a *ArchDistribution) detectAccountsService() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
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,
|
||||
}
|
||||
return a.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", a.packageInstalled("accountsservice"))
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// Common dependency detection methods
|
||||
func (b *BaseDistribution) detectGit() deps.Dependency {
|
||||
func (b *BaseDistribution) detectCommand(name, description string) deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
if b.commandExists("git") {
|
||||
if b.commandExists(name) {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
|
||||
return deps.Dependency{
|
||||
Name: "git",
|
||||
Name: name,
|
||||
Status: status,
|
||||
Description: "Version control system",
|
||||
Description: description,
|
||||
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 {
|
||||
status := deps.StatusMissing
|
||||
if b.commandExists("matugen") {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
|
||||
return deps.Dependency{
|
||||
Name: "matugen",
|
||||
Status: status,
|
||||
Description: "Material Design color generation tool",
|
||||
Required: true,
|
||||
}
|
||||
return b.detectCommand("matugen", "Material Design color generation tool")
|
||||
}
|
||||
|
||||
func (b *BaseDistribution) detectDgop() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
if b.commandExists("dgop") {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
|
||||
return deps.Dependency{
|
||||
Name: "dgop",
|
||||
Status: status,
|
||||
Description: "Desktop portal management tool",
|
||||
Required: true,
|
||||
}
|
||||
return b.detectCommand("dgop", "Desktop portal management tool")
|
||||
}
|
||||
|
||||
func (b *BaseDistribution) detectDMS() deps.Dependency {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
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) {
|
||||
if !commandExists("git") {
|
||||
if !utils.CommandExists("git") {
|
||||
t.Skip("git not available")
|
||||
}
|
||||
|
||||
@@ -80,7 +81,7 @@ func TestBaseDistribution_detectDMS_Installed(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestBaseDistribution_detectDMS_NeedsUpdate(t *testing.T) {
|
||||
if !commandExists("git") {
|
||||
if !utils.CommandExists("git") {
|
||||
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) {
|
||||
logChan := make(chan string, 10)
|
||||
defer close(logChan)
|
||||
|
||||
@@ -75,45 +75,15 @@ func (d *DebianDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) detectXDGPortal() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
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,
|
||||
}
|
||||
return d.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", d.packageInstalled("xdg-desktop-portal-gtk"))
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
if d.commandExists("xwayland-satellite") {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
|
||||
return deps.Dependency{
|
||||
Name: "xwayland-satellite",
|
||||
Status: status,
|
||||
Description: "Xwayland support",
|
||||
Required: true,
|
||||
}
|
||||
return d.detectCommand("xwayland-satellite", "Xwayland support")
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) detectAccountsService() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
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,
|
||||
}
|
||||
return d.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", d.packageInstalled("accountsservice"))
|
||||
}
|
||||
|
||||
func (d *DebianDistribution) packageInstalled(pkg string) bool {
|
||||
|
||||
@@ -97,17 +97,7 @@ func (f *FedoraDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
||||
}
|
||||
|
||||
func (f *FedoraDistribution) detectXDGPortal() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
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,
|
||||
}
|
||||
return f.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", f.packageInstalled("xdg-desktop-portal-gtk"))
|
||||
}
|
||||
|
||||
func (f *FedoraDistribution) packageInstalled(pkg string) bool {
|
||||
|
||||
@@ -113,45 +113,15 @@ func (g *GentooDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
||||
}
|
||||
|
||||
func (g *GentooDistribution) detectXDGPortal() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
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,
|
||||
}
|
||||
return g.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", g.packageInstalled("sys-apps/xdg-desktop-portal-gtk"))
|
||||
}
|
||||
|
||||
func (g *GentooDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
if g.packageInstalled("gui-apps/xwayland-satellite") {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
|
||||
return deps.Dependency{
|
||||
Name: "xwayland-satellite",
|
||||
Status: status,
|
||||
Description: "Xwayland support",
|
||||
Required: true,
|
||||
}
|
||||
return g.detectPackage("xwayland-satellite", "Xwayland support", g.packageInstalled("gui-apps/xwayland-satellite"))
|
||||
}
|
||||
|
||||
func (g *GentooDistribution) detectAccountsService() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
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,
|
||||
}
|
||||
return g.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", g.packageInstalled("sys-apps/accountsservice"))
|
||||
}
|
||||
|
||||
func (g *GentooDistribution) packageInstalled(pkg string) bool {
|
||||
|
||||
@@ -87,17 +87,7 @@ func (o *OpenSUSEDistribution) DetectDependenciesWithTerminal(ctx context.Contex
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) detectXDGPortal() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
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,
|
||||
}
|
||||
return o.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", o.packageInstalled("xdg-desktop-portal-gtk"))
|
||||
}
|
||||
|
||||
func (o *OpenSUSEDistribution) packageInstalled(pkg string) bool {
|
||||
|
||||
@@ -85,45 +85,15 @@ func (u *UbuntuDistribution) DetectDependenciesWithTerminal(ctx context.Context,
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) detectXDGPortal() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
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,
|
||||
}
|
||||
return u.detectPackage("xdg-desktop-portal-gtk", "Desktop integration portal for GTK", u.packageInstalled("xdg-desktop-portal-gtk"))
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
if u.commandExists("xwayland-satellite") {
|
||||
status = deps.StatusInstalled
|
||||
}
|
||||
|
||||
return deps.Dependency{
|
||||
Name: "xwayland-satellite",
|
||||
Status: status,
|
||||
Description: "Xwayland support",
|
||||
Required: true,
|
||||
}
|
||||
return u.detectCommand("xwayland-satellite", "Xwayland support")
|
||||
}
|
||||
|
||||
func (u *UbuntuDistribution) detectAccountsService() deps.Dependency {
|
||||
status := deps.StatusMissing
|
||||
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,
|
||||
}
|
||||
return u.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", u.packageInstalled("accountsservice"))
|
||||
}
|
||||
|
||||
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, nil
|
||||
case pluginUpdatedMsg:
|
||||
if msg.err != nil {
|
||||
m.installedPluginsError = msg.err.Error()
|
||||
} else {
|
||||
m.installedPluginsError = ""
|
||||
}
|
||||
return m, nil
|
||||
case pluginInstalledMsg:
|
||||
if msg.err != nil {
|
||||
m.pluginsError = msg.err.Error()
|
||||
|
||||
@@ -75,14 +75,13 @@ type MenuItem struct {
|
||||
|
||||
func NewModel(version string) Model {
|
||||
detector, _ := NewDetector()
|
||||
dependencies := detector.GetInstalledComponents()
|
||||
|
||||
// Use the proper detection method for both window managers
|
||||
hyprlandInstalled, niriInstalled, err := detector.GetWindowManagerStatus()
|
||||
if err != nil {
|
||||
// Fallback to false if detection fails
|
||||
hyprlandInstalled = false
|
||||
niriInstalled = false
|
||||
var dependencies []DependencyInfo
|
||||
var hyprlandInstalled, niriInstalled bool
|
||||
|
||||
if detector != nil {
|
||||
dependencies = detector.GetInstalledComponents()
|
||||
hyprlandInstalled, niriInstalled, _ = detector.GetWindowManagerStatus()
|
||||
}
|
||||
|
||||
m := Model{
|
||||
@@ -201,6 +200,13 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
return m, loadInstalledPlugins
|
||||
}
|
||||
return m, nil
|
||||
case pluginUpdatedMsg:
|
||||
if msg.err != nil {
|
||||
m.installedPluginsError = msg.err.Error()
|
||||
} else {
|
||||
m.installedPluginsError = ""
|
||||
}
|
||||
return m, nil
|
||||
case pluginInstalledMsg:
|
||||
if msg.err != nil {
|
||||
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]
|
||||
return m, uninstallPlugin(plugin)
|
||||
}
|
||||
case "p":
|
||||
if m.selectedInstalledIndex < len(m.installedPluginsList) {
|
||||
plugin := m.installedPluginsList[m.selectedInstalledIndex]
|
||||
return m, updatePlugin(plugin)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -246,6 +251,11 @@ type pluginInstalledMsg struct {
|
||||
err error
|
||||
}
|
||||
|
||||
type pluginUpdatedMsg struct {
|
||||
pluginName string
|
||||
err error
|
||||
}
|
||||
|
||||
func loadInstalledPlugins() tea.Msg {
|
||||
manager, err := plugins.NewManager()
|
||||
if err != nil {
|
||||
@@ -337,3 +347,31 @@ func uninstallPlugin(plugin pluginInfo) tea.Cmd {
|
||||
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/distros"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
// DetectDMSPath checks for DMS installation following XDG Base Directory specification
|
||||
@@ -22,10 +23,10 @@ func DetectDMSPath() (string, error) {
|
||||
func DetectCompositors() []string {
|
||||
var compositors []string
|
||||
|
||||
if commandExists("niri") {
|
||||
if utils.CommandExists("niri") {
|
||||
compositors = append(compositors, "niri")
|
||||
}
|
||||
if commandExists("Hyprland") {
|
||||
if utils.CommandExists("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
|
||||
func EnsureGreetdInstalled(logFunc func(string), sudoPassword string) error {
|
||||
if commandExists("greetd") {
|
||||
if utils.CommandExists("greetd") {
|
||||
logFunc("✓ greetd is already installed")
|
||||
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
|
||||
func CopyGreeterFiles(dmsPath, compositor string, logFunc func(string), sudoPassword string) error {
|
||||
// Check if dms-greeter is already in PATH
|
||||
if commandExists("dms-greeter") {
|
||||
if utils.CommandExists("dms-greeter") {
|
||||
logFunc("✓ dms-greeter wrapper already installed")
|
||||
} else {
|
||||
// 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
|
||||
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(" If theme sync doesn't work, you may need to install acl package:")
|
||||
logFunc(" - Fedora/RHEL: sudo dnf install acl")
|
||||
@@ -419,7 +420,7 @@ user = "greeter"
|
||||
|
||||
// Determine wrapper command path
|
||||
wrapperCmd := "dms-greeter"
|
||||
if !commandExists("dms-greeter") {
|
||||
if !utils.CommandExists("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
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
func commandExists(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
type DiscoveryConfig struct {
|
||||
@@ -14,13 +16,7 @@ type DiscoveryConfig struct {
|
||||
func DefaultDiscoveryConfig() *DiscoveryConfig {
|
||||
var searchPaths []string
|
||||
|
||||
configHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
if configHome == "" {
|
||||
if homeDir, err := os.UserHomeDir(); err == nil {
|
||||
configHome = filepath.Join(homeDir, ".config")
|
||||
}
|
||||
}
|
||||
|
||||
configHome := utils.XDGConfigHome()
|
||||
if configHome != "" {
|
||||
searchPaths = append(searchPaths, filepath.Join(configHome, "DankMaterialShell", "cheatsheets"))
|
||||
}
|
||||
@@ -43,7 +39,7 @@ func (d *DiscoveryConfig) FindJSONFiles() ([]string, error) {
|
||||
var files []string
|
||||
|
||||
for _, searchPath := range d.SearchPaths {
|
||||
expandedPath, err := expandPath(searchPath)
|
||||
expandedPath, err := utils.ExpandPath(searchPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
@@ -74,20 +70,6 @@ func (d *DiscoveryConfig) FindJSONFiles() ([]string, error) {
|
||||
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)
|
||||
|
||||
var jsonProviderFactory JSONProviderFactory
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
func TestDefaultDiscoveryConfig(t *testing.T) {
|
||||
@@ -272,13 +274,13 @@ func TestExpandPathInDiscovery(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := expandPath(tt.input)
|
||||
result, err := utils.ExpandPath(tt.input)
|
||||
if err != nil {
|
||||
t.Fatalf("expandPath failed: %v", err)
|
||||
}
|
||||
|
||||
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"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -42,14 +44,9 @@ func NewHyprlandParser() *HyprlandParser {
|
||||
}
|
||||
|
||||
func (p *HyprlandParser) ReadContent(directory string) error {
|
||||
expandedDir := os.ExpandEnv(directory)
|
||||
expandedDir = filepath.Clean(expandedDir)
|
||||
if strings.HasPrefix(expandedDir, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
expandedDir = filepath.Join(home, expandedDir[1:])
|
||||
expandedDir, err := utils.ExpandPath(directory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := os.Stat(expandedDir)
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
type JSONFileProvider struct {
|
||||
@@ -20,7 +20,7 @@ func NewJSONFileProvider(filePath string) (*JSONFileProvider, error) {
|
||||
return nil, fmt.Errorf("file path cannot be empty")
|
||||
}
|
||||
|
||||
expandedPath, err := expandPath(filePath)
|
||||
expandedPath, err := utils.ExpandPath(filePath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to expand path: %w", err)
|
||||
}
|
||||
@@ -117,17 +117,3 @@ func (j *JSONFileProvider) GetCheatSheet() (*keybinds.CheatSheet, error) {
|
||||
Binds: categorizedBinds,
|
||||
}, 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"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
func TestNewJSONFileProvider(t *testing.T) {
|
||||
@@ -266,13 +268,13 @@ func TestExpandPath(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := expandPath(tt.input)
|
||||
result, err := utils.ExpandPath(tt.input)
|
||||
if err != nil {
|
||||
t.Fatalf("expandPath failed: %v", err)
|
||||
}
|
||||
|
||||
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"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -34,14 +36,9 @@ func NewMangoWCParser() *MangoWCParser {
|
||||
}
|
||||
|
||||
func (p *MangoWCParser) ReadContent(path string) error {
|
||||
expandedPath := os.ExpandEnv(path)
|
||||
expandedPath = filepath.Clean(expandedPath)
|
||||
if strings.HasPrefix(expandedPath, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
expandedPath = filepath.Join(home, expandedPath[1:])
|
||||
expandedPath, err := utils.ExpandPath(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := os.Stat(expandedPath)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/keybinds"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
"github.com/sblinch/kdl-go"
|
||||
"github.com/sblinch/kdl-go/document"
|
||||
)
|
||||
@@ -29,15 +30,7 @@ func NewNiriProvider(configDir string) *NiriProvider {
|
||||
}
|
||||
|
||||
func defaultNiriConfigDir() string {
|
||||
if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
|
||||
return filepath.Join(configHome, "niri")
|
||||
}
|
||||
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return filepath.Join(home, ".config", "niri")
|
||||
return filepath.Join(utils.XDGConfigHome(), "niri")
|
||||
}
|
||||
|
||||
func (n *NiriProvider) Name() string {
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -42,14 +44,9 @@ func NewSwayParser() *SwayParser {
|
||||
}
|
||||
|
||||
func (p *SwayParser) ReadContent(path string) error {
|
||||
expandedPath := os.ExpandEnv(path)
|
||||
expandedPath = filepath.Clean(expandedPath)
|
||||
if strings.HasPrefix(expandedPath, "~") {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
expandedPath = filepath.Join(home, expandedPath[1:])
|
||||
expandedPath, err := utils.ExpandPath(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := os.Stat(expandedPath)
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/dank16"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -277,7 +278,7 @@ func appendConfig(opts *Options, cfgFile *os.File, checkCmd, fileName string) {
|
||||
if _, err := os.Stat(configPath); err != nil {
|
||||
return
|
||||
}
|
||||
if checkCmd != "skip" && !commandExists(checkCmd) {
|
||||
if checkCmd != "skip" && !utils.CommandExists(checkCmd) {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
return
|
||||
}
|
||||
if checkCmd != "skip" && !commandExists(checkCmd) {
|
||||
if checkCmd != "skip" && !utils.CommandExists(checkCmd) {
|
||||
return
|
||||
}
|
||||
data, err := os.ReadFile(configPath)
|
||||
@@ -390,11 +391,6 @@ func extractTOMLSection(content, startMarker, endMarker string) string {
|
||||
return content[startIdx : startIdx+endIdx]
|
||||
}
|
||||
|
||||
func commandExists(name string) bool {
|
||||
_, err := exec.LookPath(name)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func checkMatugenVersion() {
|
||||
matugenVersionOnce.Do(func() {
|
||||
cmd := exec.Command("matugen", "--version")
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
@@ -32,33 +33,70 @@ func NewManagerWithFs(fs afero.Fs) (*Manager, error) {
|
||||
}
|
||||
|
||||
func getPluginsDir() string {
|
||||
configHome := os.Getenv("XDG_CONFIG_HOME")
|
||||
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")
|
||||
return filepath.Join(utils.XDGConfigHome(), "DankMaterialShell", "plugins")
|
||||
}
|
||||
|
||||
func (m *Manager) IsInstalled(plugin Plugin) (bool, error) {
|
||||
pluginPath := filepath.Join(m.pluginsDir, plugin.ID)
|
||||
exists, err := afero.DirExists(m.fs, pluginPath)
|
||||
path, err := m.findInstalledPath(plugin.ID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if exists {
|
||||
return true, nil
|
||||
return path != "", nil
|
||||
}
|
||||
|
||||
func (m *Manager) findInstalledPath(pluginID string) (string, error) {
|
||||
// Check user plugins directory
|
||||
path, err := m.findInDir(m.pluginsDir, pluginID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if path != "" {
|
||||
return path, nil
|
||||
}
|
||||
|
||||
systemPluginPath := filepath.Join("/etc/xdg/quickshell/dms-plugins", plugin.ID)
|
||||
systemExists, err := afero.DirExists(m.fs, systemPluginPath)
|
||||
if err != nil {
|
||||
return false, err
|
||||
// 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
|
||||
}
|
||||
return systemExists, 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 {
|
||||
@@ -151,25 +189,19 @@ func (m *Manager) createSymlink(source, dest string) error {
|
||||
}
|
||||
|
||||
func (m *Manager) Update(plugin Plugin) error {
|
||||
pluginPath := filepath.Join(m.pluginsDir, plugin.ID)
|
||||
|
||||
exists, err := afero.DirExists(m.fs, pluginPath)
|
||||
pluginPath, err := m.findInstalledPath(plugin.ID)
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
if pluginPath == "" {
|
||||
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"
|
||||
metaExists, err := afero.Exists(m.fs, metaPath)
|
||||
if err != nil {
|
||||
@@ -209,25 +241,19 @@ func (m *Manager) Update(plugin Plugin) error {
|
||||
}
|
||||
|
||||
func (m *Manager) Uninstall(plugin Plugin) error {
|
||||
pluginPath := filepath.Join(m.pluginsDir, plugin.ID)
|
||||
|
||||
exists, err := afero.DirExists(m.fs, pluginPath)
|
||||
pluginPath, err := m.findInstalledPath(plugin.ID)
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
if pluginPath == "" {
|
||||
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"
|
||||
metaExists, err := afero.Exists(m.fs, metaPath)
|
||||
if err != nil {
|
||||
@@ -369,47 +395,174 @@ func (m *Manager) ListInstalled() ([]string, error) {
|
||||
|
||||
// getPluginID reads the plugin.json file and returns the plugin ID
|
||||
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")
|
||||
data, err := afero.ReadFile(m.fs, manifestPath)
|
||||
if err != nil {
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
var manifest struct {
|
||||
ID string `json:"id"`
|
||||
}
|
||||
var manifest pluginManifest
|
||||
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 {
|
||||
return m.pluginsDir
|
||||
}
|
||||
|
||||
func (m *Manager) HasUpdates(pluginID string, plugin Plugin) (bool, error) {
|
||||
pluginPath := filepath.Join(m.pluginsDir, pluginID)
|
||||
|
||||
exists, err := afero.DirExists(m.fs, pluginPath)
|
||||
func (m *Manager) UninstallByIDOrName(idOrName string) error {
|
||||
pluginPath, err := m.findInstalledPathByIDOrName(idOrName)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to check if plugin exists: %w", err)
|
||||
return err
|
||||
}
|
||||
if pluginPath == "" {
|
||||
return fmt.Errorf("plugin not found: %s", idOrName)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
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 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 systemExists {
|
||||
return false, nil
|
||||
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) {
|
||||
pluginPath, err := m.findInstalledPath(pluginID)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to find plugin: %w", err)
|
||||
}
|
||||
|
||||
if pluginPath == "" {
|
||||
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"
|
||||
metaExists, err := afero.Exists(m.fs, metaPath)
|
||||
if err != nil {
|
||||
|
||||
@@ -3,6 +3,8 @@ package plugins
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
func FuzzySearch(query string, plugins []Plugin) []Plugin {
|
||||
@@ -11,18 +13,12 @@ func FuzzySearch(query string, plugins []Plugin) []Plugin {
|
||||
}
|
||||
|
||||
queryLower := strings.ToLower(query)
|
||||
var results []Plugin
|
||||
|
||||
for _, plugin := range plugins {
|
||||
if fuzzyMatch(queryLower, strings.ToLower(plugin.Name)) ||
|
||||
fuzzyMatch(queryLower, strings.ToLower(plugin.Category)) ||
|
||||
fuzzyMatch(queryLower, strings.ToLower(plugin.Description)) ||
|
||||
fuzzyMatch(queryLower, strings.ToLower(plugin.Author)) {
|
||||
results = append(results, plugin)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
return utils.Filter(plugins, func(p Plugin) bool {
|
||||
return fuzzyMatch(queryLower, strings.ToLower(p.Name)) ||
|
||||
fuzzyMatch(queryLower, strings.ToLower(p.Category)) ||
|
||||
fuzzyMatch(queryLower, strings.ToLower(p.Description)) ||
|
||||
fuzzyMatch(queryLower, strings.ToLower(p.Author))
|
||||
})
|
||||
}
|
||||
|
||||
func fuzzyMatch(query, text string) bool {
|
||||
@@ -39,57 +35,34 @@ func FilterByCategory(category string, plugins []Plugin) []Plugin {
|
||||
if category == "" {
|
||||
return plugins
|
||||
}
|
||||
|
||||
var results []Plugin
|
||||
categoryLower := strings.ToLower(category)
|
||||
|
||||
for _, plugin := range plugins {
|
||||
if strings.ToLower(plugin.Category) == categoryLower {
|
||||
results = append(results, plugin)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
return utils.Filter(plugins, func(p Plugin) bool {
|
||||
return strings.ToLower(p.Category) == categoryLower
|
||||
})
|
||||
}
|
||||
|
||||
func FilterByCompositor(compositor string, plugins []Plugin) []Plugin {
|
||||
if compositor == "" {
|
||||
return plugins
|
||||
}
|
||||
|
||||
var results []Plugin
|
||||
compositorLower := strings.ToLower(compositor)
|
||||
|
||||
for _, plugin := range plugins {
|
||||
for _, comp := range plugin.Compositors {
|
||||
if strings.ToLower(comp) == compositorLower {
|
||||
results = append(results, plugin)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
return utils.Filter(plugins, func(p Plugin) bool {
|
||||
return utils.Any(p.Compositors, func(c string) bool {
|
||||
return strings.ToLower(c) == compositorLower
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func FilterByCapability(capability string, plugins []Plugin) []Plugin {
|
||||
if capability == "" {
|
||||
return plugins
|
||||
}
|
||||
|
||||
var results []Plugin
|
||||
capabilityLower := strings.ToLower(capability)
|
||||
|
||||
for _, plugin := range plugins {
|
||||
for _, cap := range plugin.Capabilities {
|
||||
if strings.ToLower(cap) == capabilityLower {
|
||||
results = append(results, plugin)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
return utils.Filter(plugins, func(p Plugin) bool {
|
||||
return utils.Any(p.Capabilities, func(c string) bool {
|
||||
return strings.ToLower(c) == capabilityLower
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func SortByFirstParty(plugins []Plugin) []Plugin {
|
||||
@@ -103,3 +76,13 @@ func SortByFirstParty(plugins []Plugin) []Plugin {
|
||||
})
|
||||
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"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
func BufferToImage(buf *ShmBuffer) *image.RGBA {
|
||||
@@ -116,70 +119,30 @@ func GetOutputDir() string {
|
||||
}
|
||||
|
||||
func getXDGPicturesDir() string {
|
||||
configDir := os.Getenv("XDG_CONFIG_HOME")
|
||||
if configDir == "" {
|
||||
home := os.Getenv("HOME")
|
||||
if home == "" {
|
||||
return ""
|
||||
}
|
||||
configDir = filepath.Join(home, ".config")
|
||||
}
|
||||
|
||||
userDirsFile := filepath.Join(configDir, "user-dirs.dirs")
|
||||
userDirsFile := filepath.Join(utils.XDGConfigHome(), "user-dirs.dirs")
|
||||
data, err := os.ReadFile(userDirsFile)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, line := range splitLines(string(data)) {
|
||||
for _, line := range strings.Split(string(data), "\n") {
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
const prefix = "XDG_PICTURES_DIR="
|
||||
if len(line) > len(prefix) && line[:len(prefix)] == prefix {
|
||||
path := line[len(prefix):]
|
||||
path = trimQuotes(path)
|
||||
path = expandHome(path)
|
||||
return path
|
||||
if !strings.HasPrefix(line, prefix) {
|
||||
continue
|
||||
}
|
||||
path := strings.Trim(line[len(prefix):], "\"")
|
||||
expanded, err := utils.ExpandPath(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return expanded
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return WriteToFileWithFormat(buf, path, format, quality, uint32(FormatARGB8888))
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
type ThemeColors struct {
|
||||
@@ -72,15 +74,7 @@ func loadColorsFile() *ColorScheme {
|
||||
}
|
||||
|
||||
func getColorsFilePath() string {
|
||||
cacheDir := os.Getenv("XDG_CACHE_HOME")
|
||||
if cacheDir == "" {
|
||||
home := os.Getenv("HOME")
|
||||
if home == "" {
|
||||
return ""
|
||||
}
|
||||
cacheDir = filepath.Join(home, ".cache")
|
||||
}
|
||||
return filepath.Join(cacheDir, "DankMaterialShell", "dms-colors.json")
|
||||
return filepath.Join(utils.XDGCacheHome(), "DankMaterialShell", "dms-colors.json")
|
||||
}
|
||||
|
||||
func isLightMode() bool {
|
||||
|
||||
@@ -15,50 +15,47 @@ func HandleUninstall(conn net.Conn, req models.Request) {
|
||||
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()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
installed, err := manager.IsInstalled(*plugin)
|
||||
// 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 check if plugin is installed: %v", err))
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create registry: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if !installed {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name))
|
||||
pluginList, _ := registry.List()
|
||||
plugin := plugins.FindByIDOrName(name, pluginList)
|
||||
|
||||
// If found in registry, use that
|
||||
if plugin != nil {
|
||||
installed, err := manager.IsInstalled(*plugin)
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if plugin is installed: %v", err))
|
||||
return
|
||||
}
|
||||
if !installed {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name))
|
||||
return
|
||||
}
|
||||
if err := manager.Uninstall(*plugin); err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to uninstall plugin: %v", err))
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("plugin uninstalled: %s", plugin.Name),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.Uninstall(*plugin); err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to uninstall plugin: %v", err))
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
@@ -15,50 +15,45 @@ func HandleUpdate(conn net.Conn, req models.Request) {
|
||||
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()
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to create manager: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
installed, err := manager.IsInstalled(*plugin)
|
||||
registry, err := plugins.NewRegistry()
|
||||
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 create registry: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
if !installed {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name))
|
||||
pluginList, _ := registry.List()
|
||||
plugin := plugins.FindByIDOrName(name, pluginList)
|
||||
|
||||
if plugin != nil {
|
||||
installed, err := manager.IsInstalled(*plugin)
|
||||
if err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to check if plugin is installed: %v", err))
|
||||
return
|
||||
}
|
||||
if !installed {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("plugin not installed: %s", name))
|
||||
return
|
||||
}
|
||||
if err := manager.Update(*plugin); err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to update plugin: %v", err))
|
||||
return
|
||||
}
|
||||
models.Respond(conn, req.ID, SuccessResult{
|
||||
Success: true,
|
||||
Message: fmt.Sprintf("plugin updated: %s", plugin.Name),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if err := manager.Update(*plugin); err != nil {
|
||||
models.RespondError(conn, req.ID, fmt.Sprintf("failed to update plugin: %v", err))
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
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"
|
||||
|
||||
mocks_version "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/version"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||
)
|
||||
|
||||
func TestCompareVersions(t *testing.T) {
|
||||
@@ -150,7 +151,7 @@ func TestGetCurrentDMSVersion_NotInstalled(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetCurrentDMSVersion_GitTag(t *testing.T) {
|
||||
if !commandExists("git") {
|
||||
if !utils.CommandExists("git") {
|
||||
t.Skip("git not available")
|
||||
}
|
||||
|
||||
@@ -183,7 +184,7 @@ func TestGetCurrentDMSVersion_GitTag(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetCurrentDMSVersion_GitBranch(t *testing.T) {
|
||||
if !commandExists("git") {
|
||||
if !utils.CommandExists("git") {
|
||||
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) {
|
||||
jsonResponse := `{
|
||||
"tag_name": "v0.1.17",
|
||||
|
||||
@@ -890,17 +890,51 @@ Item {
|
||||
id: rowLayout
|
||||
Row {
|
||||
spacing: 4
|
||||
visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex
|
||||
StyledText {
|
||||
topPadding: 2
|
||||
rightPadding: isActive ? 4 : 0
|
||||
visible: SettingsData.showWorkspaceIndex
|
||||
text: {
|
||||
return root.getWorkspaceIndex(modelData);
|
||||
visible: loadedIcons.length > 0 || SettingsData.showWorkspaceIndex || loadedHasIcon
|
||||
|
||||
Item {
|
||||
visible: loadedHasIcon && loadedIconData?.type === "icon"
|
||||
width: wsIcon.width + (isActive && loadedIcons.length > 0 ? 4 : 0)
|
||||
height: 18
|
||||
|
||||
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
|
||||
font.pixelSize: Theme.barTextSize(barThickness, barConfig?.fontScale)
|
||||
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
|
||||
}
|
||||
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 {
|
||||
@@ -973,7 +1007,25 @@ Item {
|
||||
id: columnLayout
|
||||
Column {
|
||||
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 {
|
||||
model: ScriptModel {
|
||||
|
||||
Reference in New Issue
Block a user