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