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  }