github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/osutils/shortcut/shortcut_windows.go (about) 1 package shortcut 2 3 import ( 4 "errors" 5 "os" 6 "path/filepath" 7 "strings" 8 9 "github.com/ActiveState/cli/internal/locale" 10 "github.com/go-ole/go-ole" 11 "github.com/go-ole/go-ole/oleutil" 12 13 "github.com/ActiveState/cli/internal/errs" 14 "github.com/ActiveState/cli/internal/fileutils" 15 "github.com/ActiveState/cli/internal/logging" 16 ) 17 18 type WindowStyle int 19 20 // Shortcut WindowStyle values 21 const ( 22 Normal WindowStyle = 1 23 Maximized = 3 24 Minimized = 7 25 ) 26 27 type Shortcut struct { 28 dir string 29 name string 30 target string 31 args string 32 dispatch *ole.IDispatch 33 } 34 35 func New(dir, name, target string, args ...string) *Shortcut { 36 return &Shortcut{ 37 dir, name, target, strings.Join(args, " "), nil, 38 } 39 } 40 41 func (s *Shortcut) Enable() error { 42 // ALWAYS errors with "Incorrect function", which can apparently be safely ignored.. 43 _ = ole.CoInitializeEx(0, ole.COINIT_APARTMENTTHREADED|ole.COINIT_SPEED_OVER_MEMORY) 44 45 oleShellObject, err := oleutil.CreateObject("WScript.Shell") 46 if err != nil { 47 return errs.Wrap(err, "Could not create shell object") 48 } 49 defer oleShellObject.Release() 50 51 wshell, err := oleShellObject.QueryInterface(ole.IID_IDispatch) 52 if err != nil { 53 return errs.Wrap(err, "Could not interface with shell object") 54 } 55 defer wshell.Release() 56 57 if err := fileutils.MkdirUnlessExists(s.dir); err != nil { 58 if os.IsPermission(err) { 59 return locale.NewInputError("err_shortcutdir_writable", "", s.dir) 60 } else { 61 return errs.Wrap(err, "Could not create Shortcut directory") 62 } 63 } 64 65 filename := filepath.Join(s.dir, s.name+".lnk") 66 logging.Debug("Creating Shortcut: %s", filename) 67 cs, err := oleutil.CallMethod(wshell, "CreateShortcut", filename) 68 if err != nil { 69 logging.Debug("OLE Error details: %s", err.Error()) 70 oleErr := &ole.OleError{} 71 if errors.As(err, &oleErr) { 72 logging.Debug("OLE Error details: \nCode:%d\nDescription:%s\nError:%s\nString:%s\nSuberror:%s", oleErr.Code(), oleErr.Description(), oleErr.Error(), oleErr.String(), oleErr.SubError().Error()) 73 return errs.Wrap(err, "oleutil CreateShortcut returned error: %s", oleErr.String()) 74 } 75 return errs.Wrap(err, "Could not call CreateShortcut on shell object") 76 } 77 78 s.dispatch = cs.ToIDispatch() 79 80 err = s.setTarget(s.target, s.args) 81 if err != nil { 82 return errs.Wrap(err, "Could not set Shortcut target") 83 } 84 85 return nil 86 } 87 88 func (s *Shortcut) setTarget(target, args string) error { 89 logging.Debug("Setting TargetPath: %s", target) 90 _, err := oleutil.PutProperty(s.dispatch, "TargetPath", target) 91 if err != nil { 92 return errs.Wrap(err, "Could not set Shortcut target") 93 } 94 95 logging.Debug("Setting Arguments: %s", args) 96 _, err = oleutil.PutProperty(s.dispatch, "Arguments", args) 97 if err != nil { 98 return errs.Wrap(err, "Could not set Shortcut arguments") 99 } 100 101 _, err = oleutil.CallMethod(s.dispatch, "Save") 102 if err != nil { 103 return errs.Wrap(err, "Could not save Shortcut") 104 } 105 106 return nil 107 } 108 109 func (s *Shortcut) setIcon(path string) error { 110 logging.Debug("Setting Icon: %s", path) 111 _, err := oleutil.PutProperty(s.dispatch, "IconLocation", path) 112 if err != nil { 113 return errs.Wrap(err, "Could not set IconLocation") 114 } 115 116 _, err = oleutil.CallMethod(s.dispatch, "Save") 117 if err != nil { 118 return errs.Wrap(err, "Could not save Shortcut") 119 } 120 121 return nil 122 } 123 124 func (s *Shortcut) SetWindowStyle(style WindowStyle) error { 125 _, err := oleutil.PutProperty(s.dispatch, "WindowStyle", int(style)) 126 if err != nil { 127 return errs.Wrap(err, "Could not set shortcut to run minimized") 128 } 129 130 _, err = oleutil.CallMethod(s.dispatch, "Save") 131 if err != nil { 132 return errs.Wrap(err, "Could not save Shortcut") 133 } 134 135 return nil 136 } 137 138 func (s *Shortcut) SetIconBlob(blob []byte) error { 139 logging.Debug("Setting Icon blob") 140 141 filepath := filepath.Join(filepath.Dir(s.target), strings.Split(filepath.Base(s.target), ".")[0]+"_generated.ico") 142 if fileutils.FileExists(filepath) { 143 if err := os.Remove(filepath); err != nil { 144 return errs.Wrap(err, "Could not remove old ico file: %s", filepath) 145 } 146 } 147 148 err := fileutils.WriteFile(filepath, blob) 149 if err != nil { 150 return errs.Wrap(err, "Could not create ico file: %s", filepath) 151 } 152 153 return s.setIcon(filepath) 154 } 155 156 func (s *Shortcut) Path() string { 157 return filepath.Join(s.dir, s.name+".lnk") 158 }