mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
core: migrate to dankgo shared go modules, embed quickshell in DMS
binary to mount at runtime, -c or DMS_SHELL_DIR overrides required to explicitly override embedded configuration
This commit is contained in:
@@ -6,7 +6,6 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/plugins"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server"
|
||||
@@ -19,63 +18,6 @@ var versionCmd = &cobra.Command{
|
||||
Run: runVersion,
|
||||
}
|
||||
|
||||
var runCmd = &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Launch quickshell with DMS configuration",
|
||||
Long: "Launch quickshell with DMS configuration (qs -c dms)",
|
||||
PreRunE: findConfig,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
daemon, _ := cmd.Flags().GetBool("daemon")
|
||||
session, _ := cmd.Flags().GetBool("session")
|
||||
if v, _ := cmd.Flags().GetString("log-level"); v != "" {
|
||||
if err := os.Setenv("DMS_LOG_LEVEL", v); err != nil {
|
||||
log.Fatalf("Failed to set DMS_LOG_LEVEL: %v", err)
|
||||
}
|
||||
}
|
||||
if v, _ := cmd.Flags().GetString("log-file"); v != "" {
|
||||
if err := os.Setenv("DMS_LOG_FILE", v); err != nil {
|
||||
log.Fatalf("Failed to set DMS_LOG_FILE: %v", err)
|
||||
}
|
||||
}
|
||||
log.ApplyEnvOverrides()
|
||||
config.CleanupStrayHyprlandConfFile(log.Infof)
|
||||
if daemon {
|
||||
runShellDaemon(session)
|
||||
} else {
|
||||
runShellInteractive(session)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
var restartCmd = &cobra.Command{
|
||||
Use: "restart",
|
||||
Short: "Restart quickshell with DMS configuration",
|
||||
Long: "Kill existing DMS shell processes and restart quickshell with DMS configuration",
|
||||
PreRunE: findConfig,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
restartShell()
|
||||
},
|
||||
}
|
||||
|
||||
var restartDetachedCmd = &cobra.Command{
|
||||
Use: "restart-detached <pid>",
|
||||
Hidden: true,
|
||||
Args: cobra.ExactArgs(1),
|
||||
PreRunE: findConfig,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runDetachedRestart(args[0])
|
||||
},
|
||||
}
|
||||
|
||||
var killCmd = &cobra.Command{
|
||||
Use: "kill",
|
||||
Short: "Kill running DMS shell processes",
|
||||
Long: "Kill all running quickshell processes with DMS configuration",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
killShell()
|
||||
},
|
||||
}
|
||||
|
||||
var ipcCmd = &cobra.Command{
|
||||
Use: "ipc",
|
||||
Short: "Send IPC commands to running DMS shell",
|
||||
@@ -743,12 +685,9 @@ func checkAllPluginsCLI() error {
|
||||
}
|
||||
|
||||
func getCommonCommands() []*cobra.Command {
|
||||
return []*cobra.Command{
|
||||
commands := shellApp.Commands()
|
||||
return append(commands, []*cobra.Command{
|
||||
versionCmd,
|
||||
runCmd,
|
||||
restartCmd,
|
||||
restartDetachedCmd,
|
||||
killCmd,
|
||||
ipcCmd,
|
||||
debugSrvCmd,
|
||||
pluginsCmd,
|
||||
@@ -775,5 +714,5 @@ func getCommonCommands() []*cobra.Command {
|
||||
trashCmd,
|
||||
systemCmd,
|
||||
switchUserCmd,
|
||||
}
|
||||
}...)
|
||||
}
|
||||
|
||||
@@ -401,12 +401,12 @@ func checkVersions(qsMissingFeatures bool) []checkResult {
|
||||
}
|
||||
|
||||
func getDMSShellVersion() (version, path string) {
|
||||
if err := findConfig(nil, nil); err == nil && configPath != "" {
|
||||
versionFile := filepath.Join(configPath, "VERSION")
|
||||
if err := shellApp.ResolveConfig(nil, nil); err == nil && shellApp.ConfigPath() != "" {
|
||||
versionFile := filepath.Join(shellApp.ConfigPath(), "VERSION")
|
||||
if data, err := os.ReadFile(versionFile); err == nil {
|
||||
return strings.TrimSpace(string(data)), configPath
|
||||
return strings.TrimSpace(string(data)), shellApp.ConfigPath()
|
||||
}
|
||||
return "installed", configPath
|
||||
return "installed", shellApp.ConfigPath()
|
||||
}
|
||||
|
||||
if dmsPath, err := config.LocateDMSConfig(); err == nil {
|
||||
@@ -450,8 +450,8 @@ func checkDMSInstallation() []checkResult {
|
||||
var results []checkResult
|
||||
|
||||
dmsPath := ""
|
||||
if err := findConfig(nil, nil); err == nil && configPath != "" {
|
||||
dmsPath = configPath
|
||||
if err := shellApp.ResolveConfig(nil, nil); err == nil && shellApp.ConfigPath() != "" {
|
||||
dmsPath = shellApp.ConfigPath()
|
||||
} else if path, err := config.LocateDMSConfig(); err == nil {
|
||||
dmsPath = path
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Short: "Update DankMaterialShell to the latest version",
|
||||
Long: "Update DankMaterialShell to the latest version using the appropriate package manager for your distribution",
|
||||
PreRunE: findConfig,
|
||||
PreRunE: shellApp.ResolveConfig,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
runUpdate()
|
||||
},
|
||||
@@ -98,7 +98,7 @@ func runUpdate() {
|
||||
}
|
||||
|
||||
log.Info("Update complete! Restarting DMS...")
|
||||
restartShell()
|
||||
shellApp.Restart()
|
||||
}
|
||||
|
||||
func updateArchLinux() error {
|
||||
|
||||
@@ -278,7 +278,7 @@ func installGreeter(nonInteractive bool) error {
|
||||
}
|
||||
|
||||
fmt.Println("\nDetecting DMS installation...")
|
||||
dmsPath, err := greeter.DetectDMSPath()
|
||||
dmsPath, err := detectDMSPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -708,7 +708,7 @@ func syncGreeter(nonInteractive bool, forceAuth bool, local bool, profileOnly bo
|
||||
fmt.Printf("✓ Using local DMS path: %s\n", dmsPath)
|
||||
}
|
||||
} else {
|
||||
dmsPath, err = greeter.DetectDMSPath()
|
||||
dmsPath, err = detectDMSPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -944,6 +944,24 @@ func resolveDMSLocalCandidate(path string) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// detectDMSPath finds the QML tree the greeter copies from. On-disk installs
|
||||
// win; with a packaged greeter the embedded UI is a valid copy source, but an
|
||||
// extraction path must never be persisted into greetd config, so unpackaged
|
||||
// setups still require a real install.
|
||||
func detectDMSPath() (string, error) {
|
||||
path, err := greeter.DetectDMSPath()
|
||||
if err == nil {
|
||||
return path, nil
|
||||
}
|
||||
if !greeter.IsGreeterPackaged() {
|
||||
return "", err
|
||||
}
|
||||
if resolveErr := shellApp.ResolveConfig(nil, nil); resolveErr != nil {
|
||||
return "", err
|
||||
}
|
||||
return shellApp.ConfigPath(), nil
|
||||
}
|
||||
|
||||
func resolveLocalDMSPath() (string, error) {
|
||||
if override := strings.TrimSpace(os.Getenv("DMS_LOCAL_PATH")); override != "" {
|
||||
if resolved, ok := resolveDMSLocalCandidate(override); ok {
|
||||
|
||||
@@ -1,19 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var customConfigPath string
|
||||
var configPath string
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "dms",
|
||||
Short: "dms CLI",
|
||||
@@ -21,54 +11,5 @@ var rootCmd = &cobra.Command{
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&customConfigPath, "config", "c", "", "Specify a custom path to the DMS config directory")
|
||||
}
|
||||
|
||||
func findConfig(cmd *cobra.Command, args []string) error {
|
||||
if customConfigPath != "" {
|
||||
log.Debug("Custom config path provided via -c flag: %s", customConfigPath)
|
||||
shellPath := filepath.Join(customConfigPath, "shell.qml")
|
||||
|
||||
info, statErr := os.Stat(shellPath)
|
||||
|
||||
if statErr == nil && !info.IsDir() {
|
||||
configPath = customConfigPath
|
||||
log.Debug("Using config from: %s", configPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
if statErr != nil {
|
||||
return fmt.Errorf("custom config path error: %w", statErr)
|
||||
}
|
||||
|
||||
return fmt.Errorf("path is a directory, not a file: %s", shellPath)
|
||||
}
|
||||
|
||||
configStateFile := filepath.Join(getRuntimeDir(), "danklinux.path")
|
||||
if data, readErr := os.ReadFile(configStateFile); readErr == nil {
|
||||
if len(getAllDMSPIDs()) == 0 {
|
||||
os.Remove(configStateFile)
|
||||
} else {
|
||||
statePath := strings.TrimSpace(string(data))
|
||||
shellPath := filepath.Join(statePath, "shell.qml")
|
||||
|
||||
if info, statErr := os.Stat(shellPath); statErr == nil && !info.IsDir() {
|
||||
log.Debug("Using config from active session state file: %s", statePath)
|
||||
configPath = statePath
|
||||
log.Debug("Using config from: %s", configPath)
|
||||
return nil
|
||||
}
|
||||
os.Remove(configStateFile)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("No custom path or active session, searching default XDG locations...")
|
||||
var err error
|
||||
configPath, err = config.LocateDMSConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debug("Using config from: %s", configPath)
|
||||
return nil
|
||||
rootCmd.PersistentFlags().StringVarP(shellApp.CustomConfigVar(), "config", "c", "", "Path to a UI config dir (containing shell.qml) to use instead of the embedded UI (env: DMS_SHELL_DIR)")
|
||||
}
|
||||
|
||||
@@ -231,16 +231,16 @@ func setPopoutScreenshotMode(begin bool) {
|
||||
fn = "begin"
|
||||
}
|
||||
cmdArgs := []string{"ipc"}
|
||||
if pid, ok := getFirstDMSPID(); ok {
|
||||
if pid, ok := shellApp.SessionPID(); ok {
|
||||
cmdArgs = append(cmdArgs, "--pid", strconv.Itoa(pid))
|
||||
} else {
|
||||
if err := findConfig(nil, nil); err != nil {
|
||||
if err := shellApp.ResolveConfig(nil, nil); err != nil {
|
||||
return
|
||||
}
|
||||
if qsHasAnyDisplay() {
|
||||
cmdArgs = append(cmdArgs, "--any-display")
|
||||
}
|
||||
cmdArgs = append(cmdArgs, "-p", configPath)
|
||||
cmdArgs = append(cmdArgs, "-p", shellApp.ConfigPath())
|
||||
}
|
||||
cmdArgs = append(cmdArgs, "call", "screenshot", fn)
|
||||
_ = exec.Command("qs", cmdArgs...).Run()
|
||||
|
||||
@@ -12,13 +12,6 @@ import (
|
||||
var Version = "dev"
|
||||
|
||||
func init() {
|
||||
runCmd.Flags().BoolP("daemon", "d", false, "Run in daemon mode")
|
||||
runCmd.Flags().Bool("daemon-child", false, "Internal flag for daemon child process")
|
||||
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
||||
runCmd.Flags().String("log-level", "", "Log level: debug, info, warn, error, fatal (overrides DMS_LOG_LEVEL)")
|
||||
runCmd.Flags().String("log-file", "", "Append logs to this file in addition to stderr (overrides DMS_LOG_FILE)")
|
||||
runCmd.Flags().MarkHidden("daemon-child")
|
||||
|
||||
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd, greeterLaunchSessionCmd)
|
||||
authCmd.AddCommand(authSyncCmd, authResolveLockCmd, authListServicesCmd, authValidateCmd)
|
||||
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||
|
||||
@@ -12,13 +12,6 @@ import (
|
||||
var Version = "dev"
|
||||
|
||||
func init() {
|
||||
runCmd.Flags().BoolP("daemon", "d", false, "Run in daemon mode")
|
||||
runCmd.Flags().Bool("daemon-child", false, "Internal flag for daemon child process")
|
||||
runCmd.Flags().Bool("session", false, "Session managed (like as a systemd unit)")
|
||||
runCmd.Flags().String("log-level", "", "Log level: debug, info, warn, error, fatal (overrides DMS_LOG_LEVEL)")
|
||||
runCmd.Flags().String("log-file", "", "Append logs to this file in addition to stderr (overrides DMS_LOG_FILE)")
|
||||
runCmd.Flags().MarkHidden("daemon-child")
|
||||
|
||||
greeterCmd.AddCommand(greeterInstallCmd, greeterSyncCmd, greeterEnableCmd, greeterStatusCmd, greeterUninstallCmd, greeterLaunchSessionCmd)
|
||||
authCmd.AddCommand(authSyncCmd, authResolveLockCmd, authListServicesCmd, authValidateCmd)
|
||||
setupCmd.AddCommand(setupBindsCmd, setupLayoutCmd, setupColorsCmd, setupAlttabCmd, setupOutputsCmd, setupCursorCmd, setupWindowrulesCmd)
|
||||
|
||||
@@ -101,11 +101,8 @@ func getServerSocketPath() string {
|
||||
runtimeDir = os.TempDir()
|
||||
}
|
||||
|
||||
if parentPID, ok := sessionParentPID(os.Getenv("WAYLAND_DISPLAY")); ok {
|
||||
sessionSock := filepath.Join(runtimeDir, fmt.Sprintf("danklinux-%d.sock", parentPID))
|
||||
if _, err := os.Stat(sessionSock); err == nil {
|
||||
return sessionSock
|
||||
}
|
||||
if sessionSock, ok := shellApp.SessionSocketPath(); ok {
|
||||
return sessionSock
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(runtimeDir)
|
||||
|
||||
+6
-696
@@ -4,591 +4,20 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server"
|
||||
)
|
||||
|
||||
type ipcTargets map[string]map[string][]string
|
||||
|
||||
// getProcessExitCode returns the exit code from a ProcessState.
|
||||
// For normal exits, returns the exit code directly.
|
||||
// For signal termination, returns 128 + signal number (Unix convention).
|
||||
func getProcessExitCode(state *os.ProcessState) int {
|
||||
if state == nil {
|
||||
return 1
|
||||
}
|
||||
if code := state.ExitCode(); code != -1 {
|
||||
return code
|
||||
}
|
||||
// Process was killed by signal - extract signal number
|
||||
if status, ok := state.Sys().(syscall.WaitStatus); ok {
|
||||
if status.Signaled() {
|
||||
return 128 + int(status.Signal())
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
var isSessionManaged bool
|
||||
|
||||
func execDetachedRestart(targetPID int) {
|
||||
selfPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
cmd := exec.Command(selfPath, "restart-detached", strconv.Itoa(targetPID))
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setsid: true,
|
||||
}
|
||||
cmd.Start()
|
||||
}
|
||||
|
||||
func runDetachedRestart(targetPIDStr string) {
|
||||
targetPID, err := strconv.Atoi(targetPIDStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
proc, err := os.FindProcess(targetPID)
|
||||
if err == nil {
|
||||
proc.Signal(syscall.SIGTERM)
|
||||
}
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
killShell()
|
||||
runShellDaemon(false)
|
||||
}
|
||||
|
||||
func getRuntimeDir() string {
|
||||
if runtime := os.Getenv("XDG_RUNTIME_DIR"); runtime != "" {
|
||||
return runtime
|
||||
}
|
||||
return os.TempDir()
|
||||
}
|
||||
|
||||
func appendLogEnv(env []string) []string {
|
||||
if v := os.Getenv("DMS_LOG_LEVEL"); v != "" {
|
||||
env = append(env, "DMS_LOG_LEVEL="+v)
|
||||
}
|
||||
if v := os.Getenv("DMS_LOG_FILE"); v != "" {
|
||||
env = append(env, "DMS_LOG_FILE="+v)
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
func withDMSExecutable(env []string) []string {
|
||||
selfPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return env
|
||||
}
|
||||
return append(env, "DMS_EXECUTABLE="+selfPath)
|
||||
}
|
||||
|
||||
func hasSystemdRun() bool {
|
||||
_, err := exec.LookPath("systemd-run")
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func getPIDFilePath() string {
|
||||
return filepath.Join(getRuntimeDir(), fmt.Sprintf("danklinux-%d.pid", os.Getpid()))
|
||||
}
|
||||
|
||||
func getSessionFilePath() string {
|
||||
return filepath.Join(getRuntimeDir(), fmt.Sprintf("danklinux-%d.session", os.Getpid()))
|
||||
}
|
||||
|
||||
func writePIDFile(childPID int) error {
|
||||
pidFile := getPIDFilePath()
|
||||
if display := os.Getenv("WAYLAND_DISPLAY"); display != "" {
|
||||
if err := os.WriteFile(getSessionFilePath(), []byte(display), 0o644); err != nil {
|
||||
log.Warnf("Failed to write session file: %v", err)
|
||||
}
|
||||
}
|
||||
return os.WriteFile(pidFile, []byte(strconv.Itoa(childPID)), 0o644)
|
||||
}
|
||||
|
||||
func removePIDFile() {
|
||||
os.Remove(getPIDFilePath())
|
||||
os.Remove(getSessionFilePath())
|
||||
}
|
||||
|
||||
func getAllDMSPIDs() []int {
|
||||
dir := getRuntimeDir()
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var pids []int
|
||||
|
||||
for _, entry := range entries {
|
||||
if !strings.HasPrefix(entry.Name(), "danklinux-") || !strings.HasSuffix(entry.Name(), ".pid") {
|
||||
continue
|
||||
}
|
||||
|
||||
pidFile := filepath.Join(dir, entry.Name())
|
||||
data, err := os.ReadFile(pidFile)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
childPID, err := strconv.Atoi(strings.TrimSpace(string(data)))
|
||||
if err != nil {
|
||||
os.Remove(pidFile)
|
||||
continue
|
||||
}
|
||||
|
||||
proc, err := os.FindProcess(childPID)
|
||||
if err != nil {
|
||||
os.Remove(pidFile)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := proc.Signal(syscall.Signal(0)); err != nil {
|
||||
os.Remove(pidFile)
|
||||
continue
|
||||
}
|
||||
|
||||
pids = append(pids, childPID)
|
||||
|
||||
parentPIDStr := strings.TrimPrefix(entry.Name(), "danklinux-")
|
||||
parentPIDStr = strings.TrimSuffix(parentPIDStr, ".pid")
|
||||
if parentPID, err := strconv.Atoi(parentPIDStr); err == nil {
|
||||
if parentProc, err := os.FindProcess(parentPID); err == nil {
|
||||
if err := parentProc.Signal(syscall.Signal(0)); err == nil {
|
||||
pids = append(pids, parentPID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pids
|
||||
}
|
||||
|
||||
func runShellInteractive(session bool) {
|
||||
isSessionManaged = session
|
||||
go printASCII()
|
||||
fmt.Fprintf(os.Stderr, "dms %s\n", Version)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
socketPath := server.GetSocketPath()
|
||||
|
||||
configStateFile := filepath.Join(getRuntimeDir(), "danklinux.path")
|
||||
if err := os.WriteFile(configStateFile, []byte(configPath), 0o644); err != nil {
|
||||
log.Warnf("Failed to write config state file: %v", err)
|
||||
}
|
||||
defer os.Remove(configStateFile)
|
||||
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errChan <- fmt.Errorf("server panic: %v", r)
|
||||
}
|
||||
}()
|
||||
server.CLIVersion = Version
|
||||
if err := server.Start(false); err != nil {
|
||||
errChan <- fmt.Errorf("server error: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
ensureFontCache()
|
||||
log.Infof("Spawning quickshell with -p %s", configPath)
|
||||
|
||||
cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
|
||||
cmd.Env = withDMSExecutable(append(os.Environ(), "DMS_SOCKET="+socketPath))
|
||||
if os.Getenv("QT_LOGGING_RULES") == "" {
|
||||
if qtRules := log.GetQtLoggingRules(); qtRules != "" {
|
||||
cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules)
|
||||
}
|
||||
}
|
||||
|
||||
if isSessionManaged && hasSystemdRun() {
|
||||
cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope")
|
||||
}
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
|
||||
if !strings.HasPrefix(configPath, homeDir) {
|
||||
cmd.Env = append(cmd.Env, "DMS_DISABLE_HOT_RELOAD=1")
|
||||
}
|
||||
}
|
||||
|
||||
if os.Getenv("QT_QPA_PLATFORMTHEME") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME=gtk3")
|
||||
}
|
||||
if os.Getenv("QT_QPA_PLATFORMTHEME_QT6") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME_QT6=gtk3")
|
||||
}
|
||||
if os.Getenv("QT_QPA_PLATFORM") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORM=wayland;xcb")
|
||||
}
|
||||
if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" {
|
||||
cmd.Env = append(cmd.Env, "QSG_USE_SIMPLE_ANIMATION_DRIVER=1")
|
||||
}
|
||||
|
||||
cmd.Env = appendLogEnv(cmd.Env)
|
||||
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
tracker := &stderrTracker{parent: os.Stderr}
|
||||
cmd.Stderr = tracker
|
||||
|
||||
startTime := time.Now()
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Fatalf("Error starting quickshell: %v", err)
|
||||
}
|
||||
|
||||
// Write PID file for the quickshell child process
|
||||
if err := writePIDFile(cmd.Process.Pid); err != nil {
|
||||
log.Warnf("Failed to write PID file: %v", err)
|
||||
}
|
||||
defer removePIDFile()
|
||||
|
||||
defer func() {
|
||||
if cmd.Process != nil {
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
}()
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
|
||||
|
||||
go func() {
|
||||
if err := cmd.Wait(); err != nil {
|
||||
errChan <- fmt.Errorf("quickshell exited: %w", err)
|
||||
} else {
|
||||
errChan <- fmt.Errorf("quickshell exited")
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case sig := <-sigChan:
|
||||
if sig == syscall.SIGUSR1 {
|
||||
if isSessionManaged {
|
||||
log.Infof("Received SIGUSR1, exiting for systemd restart...")
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Infof("Received SIGUSR1, spawning detached restart process...")
|
||||
execDetachedRestart(os.Getpid())
|
||||
return
|
||||
}
|
||||
|
||||
// Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
|
||||
select {
|
||||
case <-errChan:
|
||||
cancel()
|
||||
os.Remove(socketPath)
|
||||
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||
logStartupFailure(startTime, exitCode, tracker)
|
||||
os.Exit(exitCode)
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
}
|
||||
|
||||
log.Infof("\nReceived signal %v, shutting down...", sig)
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
return
|
||||
|
||||
case err := <-errChan:
|
||||
log.Error(err)
|
||||
cancel()
|
||||
if cmd.Process != nil {
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
os.Remove(socketPath)
|
||||
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||
logStartupFailure(startTime, exitCode, tracker)
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func restartShell() {
|
||||
pids := getAllDMSPIDs()
|
||||
|
||||
if len(pids) == 0 {
|
||||
log.Info("No running DMS shell instances found. Starting daemon...")
|
||||
runShellDaemon(false)
|
||||
return
|
||||
}
|
||||
|
||||
currentPid := os.Getpid()
|
||||
uniquePids := make(map[int]bool)
|
||||
|
||||
for _, pid := range pids {
|
||||
if pid != currentPid {
|
||||
uniquePids[pid] = true
|
||||
}
|
||||
}
|
||||
|
||||
for pid := range uniquePids {
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
log.Errorf("Error finding process %d: %v", pid, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := proc.Signal(syscall.Signal(0)); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := proc.Signal(syscall.SIGUSR1); err != nil {
|
||||
log.Errorf("Error sending SIGUSR1 to process %d: %v", pid, err)
|
||||
} else {
|
||||
log.Infof("Sent SIGUSR1 to DMS process with PID %d", pid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func killShell() {
|
||||
pids := getAllDMSPIDs()
|
||||
|
||||
if len(pids) == 0 {
|
||||
log.Info("No running DMS shell instances found.")
|
||||
return
|
||||
}
|
||||
|
||||
currentPid := os.Getpid()
|
||||
uniquePids := make(map[int]bool)
|
||||
|
||||
for _, pid := range pids {
|
||||
if pid != currentPid {
|
||||
uniquePids[pid] = true
|
||||
}
|
||||
}
|
||||
|
||||
for pid := range uniquePids {
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
log.Errorf("Error finding process %d: %v", pid, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err := proc.Signal(syscall.Signal(0)); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := proc.Kill(); err != nil {
|
||||
log.Errorf("Error killing process %d: %v", pid, err)
|
||||
} else {
|
||||
log.Infof("Killed DMS process with PID %d", pid)
|
||||
}
|
||||
}
|
||||
|
||||
dir := getRuntimeDir()
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !strings.HasPrefix(entry.Name(), "danklinux-") {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(entry.Name(), ".pid") || strings.HasSuffix(entry.Name(), ".session") {
|
||||
os.Remove(filepath.Join(dir, entry.Name()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func runShellDaemon(session bool) {
|
||||
isSessionManaged = session
|
||||
isDaemonChild := slices.Contains(os.Args, "--daemon-child")
|
||||
|
||||
if !isDaemonChild {
|
||||
fmt.Fprintf(os.Stderr, "dms %s\n", Version)
|
||||
|
||||
cmd := exec.Command(os.Args[0], "run", "-d", "--daemon-child")
|
||||
cmd.Env = os.Environ()
|
||||
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||
Setsid: true,
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Fatalf("Error starting daemon: %v", err)
|
||||
}
|
||||
|
||||
log.Infof("DMS shell daemon started (PID: %d)", cmd.Process.Pid)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "dms %s\n", Version)
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
socketPath := server.GetSocketPath()
|
||||
|
||||
configStateFile := filepath.Join(getRuntimeDir(), "danklinux.path")
|
||||
if err := os.WriteFile(configStateFile, []byte(configPath), 0o644); err != nil {
|
||||
log.Warnf("Failed to write config state file: %v", err)
|
||||
}
|
||||
defer os.Remove(configStateFile)
|
||||
|
||||
errChan := make(chan error, 2)
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
errChan <- fmt.Errorf("server panic: %v", r)
|
||||
}
|
||||
}()
|
||||
server.CLIVersion = Version
|
||||
if err := server.Start(false); err != nil {
|
||||
errChan <- fmt.Errorf("server error: %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
ensureFontCache()
|
||||
log.Infof("Spawning quickshell with -p %s", configPath)
|
||||
|
||||
cmd := exec.CommandContext(ctx, "qs", "-p", configPath)
|
||||
cmd.Env = withDMSExecutable(append(os.Environ(), "DMS_SOCKET="+socketPath))
|
||||
if os.Getenv("QT_LOGGING_RULES") == "" {
|
||||
if qtRules := log.GetQtLoggingRules(); qtRules != "" {
|
||||
cmd.Env = append(cmd.Env, "QT_LOGGING_RULES="+qtRules)
|
||||
}
|
||||
}
|
||||
|
||||
// ! TODO - remove when QS 0.3 is up and we can use the pragma
|
||||
cmd.Env = append(cmd.Env, "QS_APP_ID=com.danklinux.dms")
|
||||
|
||||
if isSessionManaged && hasSystemdRun() {
|
||||
cmd.Env = append(cmd.Env, "DMS_DEFAULT_LAUNCH_PREFIX=systemd-run --user --scope")
|
||||
}
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil && os.Getenv("DMS_DISABLE_HOT_RELOAD") == "" {
|
||||
if !strings.HasPrefix(configPath, homeDir) {
|
||||
cmd.Env = append(cmd.Env, "DMS_DISABLE_HOT_RELOAD=1")
|
||||
}
|
||||
}
|
||||
|
||||
if os.Getenv("QT_QPA_PLATFORMTHEME") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME=gtk3")
|
||||
}
|
||||
if os.Getenv("QT_QPA_PLATFORMTHEME_QT6") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORMTHEME_QT6=gtk3")
|
||||
}
|
||||
if os.Getenv("QT_QPA_PLATFORM") == "" {
|
||||
cmd.Env = append(cmd.Env, "QT_QPA_PLATFORM=wayland;xcb")
|
||||
}
|
||||
if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" {
|
||||
cmd.Env = append(cmd.Env, "QSG_USE_SIMPLE_ANIMATION_DRIVER=1")
|
||||
}
|
||||
|
||||
cmd.Env = appendLogEnv(cmd.Env)
|
||||
|
||||
devNull, err := os.OpenFile("/dev/null", os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
log.Fatalf("Error opening /dev/null: %v", err)
|
||||
}
|
||||
defer devNull.Close()
|
||||
|
||||
cmd.Stdin = devNull
|
||||
cmd.Stdout = devNull
|
||||
tracker := &stderrTracker{parent: devNull}
|
||||
cmd.Stderr = tracker
|
||||
|
||||
startTime := time.Now()
|
||||
if err := cmd.Start(); err != nil {
|
||||
log.Fatalf("Error starting daemon: %v", err)
|
||||
}
|
||||
|
||||
// Write PID file for the quickshell child process
|
||||
if err := writePIDFile(cmd.Process.Pid); err != nil {
|
||||
log.Warnf("Failed to write PID file: %v", err)
|
||||
}
|
||||
defer removePIDFile()
|
||||
|
||||
defer func() {
|
||||
if cmd.Process != nil {
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
}()
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR1)
|
||||
|
||||
go func() {
|
||||
if err := cmd.Wait(); err != nil {
|
||||
errChan <- fmt.Errorf("quickshell exited: %w", err)
|
||||
} else {
|
||||
errChan <- fmt.Errorf("quickshell exited")
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case sig := <-sigChan:
|
||||
if sig == syscall.SIGUSR1 {
|
||||
if isSessionManaged {
|
||||
log.Infof("Received SIGUSR1, exiting for systemd restart...")
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Infof("Received SIGUSR1, spawning detached restart process...")
|
||||
execDetachedRestart(os.Getpid())
|
||||
return
|
||||
}
|
||||
|
||||
// Check if qs already crashed before we got SIGTERM (systemd sends SIGTERM when D-Bus name is released)
|
||||
select {
|
||||
case <-errChan:
|
||||
cancel()
|
||||
os.Remove(socketPath)
|
||||
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||
logStartupFailure(startTime, exitCode, tracker)
|
||||
os.Exit(exitCode)
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
}
|
||||
|
||||
cancel()
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
os.Remove(socketPath)
|
||||
return
|
||||
|
||||
case <-errChan:
|
||||
cancel()
|
||||
if cmd.Process != nil {
|
||||
cmd.Process.Signal(syscall.SIGTERM)
|
||||
}
|
||||
os.Remove(socketPath)
|
||||
exitCode := getProcessExitCode(cmd.ProcessState)
|
||||
logStartupFailure(startTime, exitCode, tracker)
|
||||
os.Exit(exitCode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var qsHasAnyDisplay = sync.OnceValue(func() bool {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
@@ -632,17 +61,17 @@ func parseTargetsFromIPCShowOutput(output string) ipcTargets {
|
||||
|
||||
func buildQsIPCBaseArgs() ([]string, error) {
|
||||
cmdArgs := []string{"ipc"}
|
||||
switch pid, ok := getSessionDMSPID(); {
|
||||
switch pid, ok := shellApp.SessionPID(); {
|
||||
case ok:
|
||||
cmdArgs = append(cmdArgs, "--pid", strconv.Itoa(pid))
|
||||
default:
|
||||
if err := findConfig(nil, nil); err != nil {
|
||||
if err := shellApp.ResolveConfig(nil, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if qsHasAnyDisplay() {
|
||||
cmdArgs = append(cmdArgs, "--any-display")
|
||||
}
|
||||
cmdArgs = append(cmdArgs, "-p", configPath)
|
||||
cmdArgs = append(cmdArgs, "-p", shellApp.ConfigPath())
|
||||
}
|
||||
return cmdArgs, nil
|
||||
}
|
||||
@@ -699,101 +128,6 @@ func getShellIPCCompletions(args []string, _ string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getFirstDMSPID() (int, bool) {
|
||||
dir := getRuntimeDir()
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !strings.HasPrefix(entry.Name(), "danklinux-") || !strings.HasSuffix(entry.Name(), ".pid") {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(dir, entry.Name()))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
pid, err := strconv.Atoi(strings.TrimSpace(string(data)))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if proc.Signal(syscall.Signal(0)) != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return pid, true
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func sessionParentPID(display string) (int, bool) {
|
||||
if display == "" {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
dir := getRuntimeDir()
|
||||
entries, err := os.ReadDir(dir)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
name := entry.Name()
|
||||
if !strings.HasPrefix(name, "danklinux-") || !strings.HasSuffix(name, ".session") {
|
||||
continue
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(dir, name))
|
||||
if err != nil || strings.TrimSpace(string(data)) != display {
|
||||
continue
|
||||
}
|
||||
|
||||
parentStr := strings.TrimSuffix(strings.TrimPrefix(name, "danklinux-"), ".session")
|
||||
parentPID, err := strconv.Atoi(parentStr)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return parentPID, true
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func getSessionDMSPID() (int, bool) {
|
||||
parentPID, ok := sessionParentPID(os.Getenv("WAYLAND_DISPLAY"))
|
||||
if !ok {
|
||||
return getFirstDMSPID()
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(filepath.Join(getRuntimeDir(), fmt.Sprintf("danklinux-%d.pid", parentPID)))
|
||||
if err != nil {
|
||||
return getFirstDMSPID()
|
||||
}
|
||||
|
||||
pid, err := strconv.Atoi(strings.TrimSpace(string(data)))
|
||||
if err != nil {
|
||||
return getFirstDMSPID()
|
||||
}
|
||||
|
||||
proc, err := os.FindProcess(pid)
|
||||
if err != nil || proc.Signal(syscall.Signal(0)) != nil {
|
||||
return getFirstDMSPID()
|
||||
}
|
||||
|
||||
return pid, true
|
||||
}
|
||||
|
||||
func runShellIPCCommand(args []string) {
|
||||
if len(args) == 0 {
|
||||
printIPCHelp()
|
||||
@@ -946,36 +280,12 @@ func rebuildFontCache() {
|
||||
}
|
||||
}
|
||||
|
||||
type stderrTracker struct {
|
||||
mu sync.Mutex
|
||||
buf strings.Builder
|
||||
parent io.Writer
|
||||
}
|
||||
|
||||
func (s *stderrTracker) Write(p []byte) (n int, err error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if s.buf.Len() < 8192 {
|
||||
s.buf.Write(p)
|
||||
}
|
||||
if s.parent != nil {
|
||||
return s.parent.Write(p)
|
||||
}
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
func (s *stderrTracker) String() string {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
return s.buf.String()
|
||||
}
|
||||
|
||||
// logStartupFailure logs diagnostic advice if qs crashes within 5s of launch.
|
||||
func logStartupFailure(startTime time.Time, exitCode int, tracker *stderrTracker) {
|
||||
if time.Since(startTime) >= 5*time.Second || exitCode == 0 || exitCode > 128 {
|
||||
func logStartupFailure(exitCode int, uptime time.Duration, stderrTail string) {
|
||||
if uptime >= 5*time.Second || exitCode == 0 || exitCode > 128 {
|
||||
return
|
||||
}
|
||||
if containsFontCrashSignature(tracker.String()) {
|
||||
if containsFontCrashSignature(stderrTail) {
|
||||
log.Errorf("DMS startup failed due to a potential font/rendering crash. Try running 'fc-cache -fv' and restarting DMS.")
|
||||
} else {
|
||||
log.Errorf("DMS startup failed (exit code %d). Run 'dms doctor' for more diagnostics.", exitCode)
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/server"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/shellembed"
|
||||
"github.com/AvengeMedia/dankgo/shellapp"
|
||||
)
|
||||
|
||||
var shellApp = shellapp.New(shellapp.Config{
|
||||
ID: "danklinux",
|
||||
EnvPrefix: "DMS",
|
||||
QSAppID: "com.danklinux.dms",
|
||||
Version: Version,
|
||||
Embedded: embeddedShell{},
|
||||
Boot: bootBackend,
|
||||
PreLaunch: preLaunch,
|
||||
ExtraEnv: dmsExtraEnv,
|
||||
OnUIExit: logStartupFailure,
|
||||
})
|
||||
|
||||
type embeddedShell struct{}
|
||||
|
||||
func (embeddedShell) Available() bool { return shellembed.Available() }
|
||||
|
||||
func (embeddedShell) Extract(baseDir string) (string, error) { return shellembed.Extract(baseDir) }
|
||||
|
||||
func (embeddedShell) Prune(baseDir, keep string) { shellembed.Prune(baseDir, keep) }
|
||||
|
||||
type dmsBackend struct {
|
||||
srv *server.Server
|
||||
done chan error
|
||||
}
|
||||
|
||||
func (b *dmsBackend) SocketPath() string { return b.srv.SocketPath() }
|
||||
|
||||
func (b *dmsBackend) Close() { b.srv.Close() }
|
||||
|
||||
func (b *dmsBackend) Done() <-chan error { return b.done }
|
||||
|
||||
func bootBackend(ctx context.Context) (shellapp.Backend, error) {
|
||||
config.CleanupStrayHyprlandConfFile(log.Infof)
|
||||
server.CLIVersion = Version
|
||||
|
||||
srv := server.New()
|
||||
if err := srv.Listen(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
backend := &dmsBackend{srv: srv, done: make(chan error, 1)}
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
backend.done <- fmt.Errorf("server panic: %v", r)
|
||||
}
|
||||
}()
|
||||
backend.done <- srv.Serve(false)
|
||||
}()
|
||||
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func preLaunch() {
|
||||
go printASCII()
|
||||
ensureFontCache()
|
||||
}
|
||||
|
||||
func dmsExtraEnv(string) []string {
|
||||
var env []string
|
||||
if selfPath, err := os.Executable(); err == nil {
|
||||
env = append(env, "DMS_EXECUTABLE="+selfPath)
|
||||
}
|
||||
if os.Getenv("QSG_USE_SIMPLE_ANIMATION_DRIVER") == "" {
|
||||
env = append(env, "QSG_USE_SIMPLE_ANIMATION_DRIVER=1")
|
||||
}
|
||||
return env
|
||||
}
|
||||
Reference in New Issue
Block a user