1//go:build darwin
2
3package setup
4
5import (
6	"fmt"
7	homedir "github.com/mitchellh/go-homedir"
8	"howett.net/plist"
9	"os"
10	"os/exec"
11	"strings"
12)
13
14const launchServicesPlist = "~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist"
15
16// see https://unix.stackexchange.com/questions/497146/create-a-custom-url-protocol-handler
17func Install() error {
18	self, _ := os.Executable()
19	removeHandler(PROTOCOL)
20	err := addHandler(PROTOCOL, self)
21	if err != nil {
22		return err
23	}
24	return nil
25}
26
27func Uninstall() error {
28	_, err := getHandler(PROTOCOL)
29	if err != nil {
30		return err
31	}
32
33	err = removeHandler(PROTOCOL)
34	if err != nil {
35		return err
36	}
37	return nil
38}
39
40func PreparePath(path string, isLetter bool) string {
41	if isLetter {
42		// we assume that the path is auto-mounted under /media/<letter>
43		path = strings.Replace(path, ":\\", "/", -1)
44		return "/media/" + path
45	} else {
46		return "smb:" + path
47	}
48}
49
50func Run(path string) error {
51	out, err := exec.Command("open", path).CombinedOutput()
52	if err != nil {
53		return fmt.Errorf("Failed to execute open command.\n%s\n%s", err.Error(), out)
54	}
55
56	return nil
57}
58
59func readPlist() (map[string]interface{}, error) {
60	plistPath, err := homedir.Expand(launchServicesPlist)
61	if err != nil {
62		return nil, err
63	}
64
65	content, err := os.ReadFile(plistPath)
66	if err != nil {
67		return nil, err
68	}
69
70	var prefs interface{}
71	if _, err = plist.Unmarshal(content, &prefs); err != nil {
72		return nil, err
73	}
74
75	prefsMap, ok := prefs.(map[string]interface{})
76	if !ok {
77		return nil, fmt.Errorf("unable to convert plist to map")
78	}
79
80	return prefsMap, nil
81}
82
83func writePlist(prefsMap map[string]interface{}) error {
84	plistPath, err := homedir.Expand(launchServicesPlist)
85	if err != nil {
86		return err
87	}
88
89	updatedContent, err := plist.Marshal(prefsMap, plist.AutomaticFormat)
90	if err != nil {
91		return err
92	}
93
94	err = os.WriteFile(plistPath, updatedContent, 0644)
95	if err != nil {
96		return err
97	}
98
99	return nil
100}
101
102func addHandler(protocol, path string) error {
103	prefsMap, err := readPlist()
104	if err != nil {
105		return err
106	}
107
108	// Get the LSHandlers slice
109	lsHandlers, ok := prefsMap["LSHandlers"].([]interface{})
110	if !ok {
111		return fmt.Errorf("unable to get LSHandlers")
112	}
113
114	// Append a new handler to the LSHandlers slice
115	lsHandlers = append(lsHandlers, map[string]string{
116		"LSHandlerRoleAll":   path,
117		"LSHandlerURLScheme": protocol,
118	})
119
120	// Update the LSHandlers slice in the prefsMap
121	prefsMap["LSHandlers"] = lsHandlers
122
123	err = writePlist(prefsMap)
124	if err != nil {
125		return err
126	}
127
128	return nil
129}
130
131func removeHandler(protocol string) error {
132	prefsMap, err := readPlist()
133	if err != nil {
134		return err
135	}
136
137	// Get the LSHandlers slice
138	lsHandlers, ok := prefsMap["LSHandlers"].([]interface{})
139	if !ok {
140		return fmt.Errorf("unable to get LSHandlers")
141	}
142
143	// Create a new slice to hold the handlers that do not match the given protocol
144	newLSHandlers := make([]interface{}, 0)
145
146	// Iterate over the LSHandlers slice and add the handler to the new slice if it does not match the given protocol
147	for _, handler := range lsHandlers {
148		handlerMap, ok := handler.(map[string]interface{})
149		if !ok {
150			return fmt.Errorf("unable to convert handler to map")
151		}
152
153		if handlerMap["LSHandlerURLScheme"] != protocol {
154			newLSHandlers = append(newLSHandlers, handler)
155		}
156	}
157
158	// Update the LSHandlers slice in the prefsMap with the new slice
159	prefsMap["LSHandlers"] = newLSHandlers
160
161	err = writePlist(prefsMap)
162	if err != nil {
163		return err
164	}
165
166	return nil
167}
168
169func getHandler(protocol string) (string, error) {
170	prefsMap, err := readPlist()
171	if err != nil {
172		return "", err
173	}
174
175	// Get the LSHandlers slice
176	lsHandlers, ok := prefsMap["LSHandlers"].([]interface{})
177	if !ok {
178		return "", fmt.Errorf("unable to get LSHandlers")
179	}
180
181	// Iterate over the LSHandlers slice and find the handler that matches the given protocol
182	for _, handler := range lsHandlers {
183		handlerMap, ok := handler.(map[string]interface{})
184		if !ok {
185			return "", fmt.Errorf("unable to convert handler to map")
186		}
187
188		if handlerMap["LSHandlerURLScheme"] == protocol {
189			return handlerMap["LSHandlerRoleAll"].(string), nil
190		}
191	}
192
193	return "", fmt.Errorf("handler for protocol %s not found", protocol)
194}
195