|
|
@@ -0,0 +1,551 @@
|
|
|
+// Copyright 2021 The golang.design Initiative Authors.
|
|
|
+// All rights reserved. Use of this source code is governed
|
|
|
+// by a MIT license that can be found in the LICENSE file.
|
|
|
+//
|
|
|
+// Written by Changkun Ou <changkun.de>
|
|
|
+
|
|
|
+//go:build windows
|
|
|
+
|
|
|
+package clipboard
|
|
|
+
|
|
|
+// Interacting with Clipboard on Windows:
|
|
|
+// https://docs.microsoft.com/zh-cn/windows/win32/dataxchg/using-the-clipboard
|
|
|
+
|
|
|
+import (
|
|
|
+ "bytes"
|
|
|
+ "context"
|
|
|
+ "encoding/binary"
|
|
|
+ "errors"
|
|
|
+ "fmt"
|
|
|
+ "image"
|
|
|
+ "image/color"
|
|
|
+ "image/png"
|
|
|
+ "reflect"
|
|
|
+ "runtime"
|
|
|
+ "syscall"
|
|
|
+ "time"
|
|
|
+ "unicode/utf16"
|
|
|
+ "unsafe"
|
|
|
+
|
|
|
+ "golang.org/x/image/bmp"
|
|
|
+)
|
|
|
+
|
|
|
+func initialize() error { return nil }
|
|
|
+
|
|
|
+// readText reads the clipboard and returns the text data if presents.
|
|
|
+// The caller is responsible for opening/closing the clipboard before
|
|
|
+// calling this function.
|
|
|
+func readText() (buf []byte, err error) {
|
|
|
+ hMem, _, err := getClipboardData.Call(cFmtUnicodeText)
|
|
|
+ if hMem == 0 {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ p, _, err := gLock.Call(hMem)
|
|
|
+ if p == 0 {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ defer gUnlock.Call(hMem)
|
|
|
+
|
|
|
+ // Find NUL terminator
|
|
|
+ n := 0
|
|
|
+ for ptr := unsafe.Pointer(p); *(*uint16)(ptr) != 0; n++ {
|
|
|
+ ptr = unsafe.Pointer(uintptr(ptr) +
|
|
|
+ unsafe.Sizeof(*((*uint16)(unsafe.Pointer(p)))))
|
|
|
+ }
|
|
|
+
|
|
|
+ var s []uint16
|
|
|
+ h := (*reflect.SliceHeader)(unsafe.Pointer(&s))
|
|
|
+ h.Data = p
|
|
|
+ h.Len = n
|
|
|
+ h.Cap = n
|
|
|
+ return []byte(string(utf16.Decode(s))), nil
|
|
|
+}
|
|
|
+
|
|
|
+// writeText writes given data to the clipboard. It is the caller's
|
|
|
+// responsibility for opening/closing the clipboard before calling
|
|
|
+// this function.
|
|
|
+func writeText(buf []byte) error {
|
|
|
+ r, _, err := emptyClipboard.Call()
|
|
|
+ if r == 0 {
|
|
|
+ return fmt.Errorf("failed to clear clipboard: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // empty text, we are done here.
|
|
|
+ if len(buf) == 0 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ s, err := syscall.UTF16FromString(string(buf))
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("failed to convert given string: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ hMem, _, err := gAlloc.Call(gmemMoveable, uintptr(len(s)*int(unsafe.Sizeof(s[0]))))
|
|
|
+ if hMem == 0 {
|
|
|
+ return fmt.Errorf("failed to alloc global memory: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ p, _, err := gLock.Call(hMem)
|
|
|
+ if p == 0 {
|
|
|
+ return fmt.Errorf("failed to lock global memory: %w", err)
|
|
|
+ }
|
|
|
+ defer gUnlock.Call(hMem)
|
|
|
+
|
|
|
+ // no return value
|
|
|
+ memMove.Call(p, uintptr(unsafe.Pointer(&s[0])),
|
|
|
+ uintptr(len(s)*int(unsafe.Sizeof(s[0]))))
|
|
|
+
|
|
|
+ v, _, err := setClipboardData.Call(cFmtUnicodeText, hMem)
|
|
|
+ if v == 0 {
|
|
|
+ gFree.Call(hMem)
|
|
|
+ return fmt.Errorf("failed to set text to clipboard: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// readImage reads the clipboard and returns PNG encoded image data
|
|
|
+// if presents. The caller is responsible for opening/closing the
|
|
|
+// clipboard before calling this function.
|
|
|
+func readImage() ([]byte, error) {
|
|
|
+ hMem, _, err := getClipboardData.Call(cFmtDIBV5)
|
|
|
+ if hMem == 0 {
|
|
|
+ // second chance to try FmtDIB
|
|
|
+ return readImageDib()
|
|
|
+ }
|
|
|
+ p, _, err := gLock.Call(hMem)
|
|
|
+ if p == 0 {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ defer gUnlock.Call(hMem)
|
|
|
+
|
|
|
+ // inspect header information
|
|
|
+ info := (*bitmapV5Header)(unsafe.Pointer(p))
|
|
|
+
|
|
|
+ // maybe deal with other formats?
|
|
|
+ if info.BitCount != 32 {
|
|
|
+ return nil, errUnsupported
|
|
|
+ }
|
|
|
+
|
|
|
+ var data []byte
|
|
|
+ sh := (*reflect.SliceHeader)(unsafe.Pointer(&data))
|
|
|
+ sh.Data = uintptr(p)
|
|
|
+ sh.Cap = int(info.Size + 4*uint32(info.Width)*uint32(info.Height))
|
|
|
+ sh.Len = int(info.Size + 4*uint32(info.Width)*uint32(info.Height))
|
|
|
+ img := image.NewRGBA(image.Rect(0, 0, int(info.Width), int(info.Height)))
|
|
|
+ offset := int(info.Size)
|
|
|
+ stride := int(info.Width)
|
|
|
+ for y := 0; y < int(info.Height); y++ {
|
|
|
+ for x := 0; x < int(info.Width); x++ {
|
|
|
+ idx := offset + 4*(y*stride+x)
|
|
|
+ xhat := (x + int(info.Width)) % int(info.Width)
|
|
|
+ yhat := int(info.Height) - 1 - y
|
|
|
+ r := data[idx+2]
|
|
|
+ g := data[idx+1]
|
|
|
+ b := data[idx+0]
|
|
|
+ a := data[idx+3]
|
|
|
+ img.SetRGBA(xhat, yhat, color.RGBA{r, g, b, a})
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // always use PNG encoding.
|
|
|
+ var buf bytes.Buffer
|
|
|
+ png.Encode(&buf, img)
|
|
|
+ return buf.Bytes(), nil
|
|
|
+}
|
|
|
+
|
|
|
+func readImageDib() ([]byte, error) {
|
|
|
+ const (
|
|
|
+ fileHeaderLen = 14
|
|
|
+ infoHeaderLen = 40
|
|
|
+ cFmtDIB = 8
|
|
|
+ )
|
|
|
+
|
|
|
+ hClipDat, _, err := getClipboardData.Call(cFmtDIB)
|
|
|
+ if err != nil {
|
|
|
+ return nil, errors.New("not dib format data: " + err.Error())
|
|
|
+ }
|
|
|
+ pMemBlk, _, err := gLock.Call(hClipDat)
|
|
|
+ if pMemBlk == 0 {
|
|
|
+ return nil, errors.New("failed to call global lock: " + err.Error())
|
|
|
+ }
|
|
|
+ defer gUnlock.Call(hClipDat)
|
|
|
+
|
|
|
+ bmpHeader := (*bitmapHeader)(unsafe.Pointer(pMemBlk))
|
|
|
+ dataSize := bmpHeader.SizeImage + fileHeaderLen + infoHeaderLen
|
|
|
+
|
|
|
+ if bmpHeader.SizeImage == 0 && bmpHeader.Compression == 0 {
|
|
|
+ iSizeImage := bmpHeader.Height * ((bmpHeader.Width*uint32(bmpHeader.BitCount)/8 + 3) &^ 3)
|
|
|
+ dataSize += iSizeImage
|
|
|
+ }
|
|
|
+ buf := new(bytes.Buffer)
|
|
|
+ binary.Write(buf, binary.LittleEndian, uint16('B')|(uint16('M')<<8))
|
|
|
+ binary.Write(buf, binary.LittleEndian, uint32(dataSize))
|
|
|
+ binary.Write(buf, binary.LittleEndian, uint32(0))
|
|
|
+ const sizeof_colorbar = 0
|
|
|
+ binary.Write(buf, binary.LittleEndian, uint32(fileHeaderLen+infoHeaderLen+sizeof_colorbar))
|
|
|
+ j := 0
|
|
|
+ for i := fileHeaderLen; i < int(dataSize); i++ {
|
|
|
+ binary.Write(buf, binary.BigEndian, *(*byte)(unsafe.Pointer(pMemBlk + uintptr(j))))
|
|
|
+ j++
|
|
|
+ }
|
|
|
+ return bmpToPng(buf)
|
|
|
+}
|
|
|
+
|
|
|
+func bmpToPng(bmpBuf *bytes.Buffer) (buf []byte, err error) {
|
|
|
+ var f bytes.Buffer
|
|
|
+ original_image, err := bmp.Decode(bmpBuf)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ err = png.Encode(&f, original_image)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return f.Bytes(), nil
|
|
|
+}
|
|
|
+
|
|
|
+func writeImage(buf []byte) error {
|
|
|
+ r, _, err := emptyClipboard.Call()
|
|
|
+ if r == 0 {
|
|
|
+ return fmt.Errorf("failed to clear clipboard: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // empty text, we are done here.
|
|
|
+ if len(buf) == 0 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+
|
|
|
+ img, err := png.Decode(bytes.NewReader(buf))
|
|
|
+ if err != nil {
|
|
|
+ return fmt.Errorf("input bytes is not PNG encoded: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ offset := unsafe.Sizeof(bitmapV5Header{})
|
|
|
+ width := img.Bounds().Dx()
|
|
|
+ height := img.Bounds().Dy()
|
|
|
+ imageSize := 4 * width * height
|
|
|
+
|
|
|
+ data := make([]byte, int(offset)+imageSize)
|
|
|
+ for y := 0; y < height; y++ {
|
|
|
+ for x := 0; x < width; x++ {
|
|
|
+ idx := int(offset) + 4*(y*width+x)
|
|
|
+ r, g, b, a := img.At(x, height-1-y).RGBA()
|
|
|
+ data[idx+2] = uint8(r)
|
|
|
+ data[idx+1] = uint8(g)
|
|
|
+ data[idx+0] = uint8(b)
|
|
|
+ data[idx+3] = uint8(a)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ info := bitmapV5Header{}
|
|
|
+ info.Size = uint32(offset)
|
|
|
+ info.Width = int32(width)
|
|
|
+ info.Height = int32(height)
|
|
|
+ info.Planes = 1
|
|
|
+ info.Compression = 0 // BI_RGB
|
|
|
+ info.SizeImage = uint32(4 * info.Width * info.Height)
|
|
|
+ info.RedMask = 0xff0000 // default mask
|
|
|
+ info.GreenMask = 0xff00
|
|
|
+ info.BlueMask = 0xff
|
|
|
+ info.AlphaMask = 0xff000000
|
|
|
+ info.BitCount = 32 // we only deal with 32 bpp at the moment.
|
|
|
+ // Use calibrated RGB values as Go's image/png assumes linear color space.
|
|
|
+ // Other options:
|
|
|
+ // - LCS_CALIBRATED_RGB = 0x00000000
|
|
|
+ // - LCS_sRGB = 0x73524742
|
|
|
+ // - LCS_WINDOWS_COLOR_SPACE = 0x57696E20
|
|
|
+ // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/eb4bbd50-b3ce-4917-895c-be31f214797f
|
|
|
+ info.CSType = 0x73524742
|
|
|
+ // Use GL_IMAGES for GamutMappingIntent
|
|
|
+ // Other options:
|
|
|
+ // - LCS_GM_ABS_COLORIMETRIC = 0x00000008
|
|
|
+ // - LCS_GM_BUSINESS = 0x00000001
|
|
|
+ // - LCS_GM_GRAPHICS = 0x00000002
|
|
|
+ // - LCS_GM_IMAGES = 0x00000004
|
|
|
+ // https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-wmf/9fec0834-607d-427d-abd5-ab240fb0db38
|
|
|
+ info.Intent = 4 // LCS_GM_IMAGES
|
|
|
+
|
|
|
+ infob := make([]byte, int(unsafe.Sizeof(info)))
|
|
|
+ for i, v := range *(*[unsafe.Sizeof(info)]byte)(unsafe.Pointer(&info)) {
|
|
|
+ infob[i] = v
|
|
|
+ }
|
|
|
+ copy(data[:], infob[:])
|
|
|
+
|
|
|
+ hMem, _, err := gAlloc.Call(gmemMoveable,
|
|
|
+ uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
|
|
|
+ if hMem == 0 {
|
|
|
+ return fmt.Errorf("failed to alloc global memory: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ p, _, err := gLock.Call(hMem)
|
|
|
+ if p == 0 {
|
|
|
+ return fmt.Errorf("failed to lock global memory: %w", err)
|
|
|
+ }
|
|
|
+ defer gUnlock.Call(hMem)
|
|
|
+
|
|
|
+ memMove.Call(p, uintptr(unsafe.Pointer(&data[0])),
|
|
|
+ uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
|
|
|
+
|
|
|
+ v, _, err := setClipboardData.Call(cFmtDIBV5, hMem)
|
|
|
+ if v == 0 {
|
|
|
+ gFree.Call(hMem)
|
|
|
+ return fmt.Errorf("failed to set text to clipboard: %w", err)
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+func read(t Format) (buf []byte, err error) {
|
|
|
+ // On Windows, OpenClipboard and CloseClipboard must be executed on
|
|
|
+ // the same thread. Thus, lock the OS thread for further execution.
|
|
|
+ runtime.LockOSThread()
|
|
|
+ defer runtime.UnlockOSThread()
|
|
|
+
|
|
|
+ var format uintptr
|
|
|
+ switch t {
|
|
|
+ case FmtImage:
|
|
|
+ format = cFmtDIBV5
|
|
|
+ case FmtText:
|
|
|
+ fallthrough
|
|
|
+ default:
|
|
|
+ format = cFmtUnicodeText
|
|
|
+ }
|
|
|
+
|
|
|
+ // check if clipboard is avaliable for the requested format
|
|
|
+ r, _, err := isClipboardFormatAvailable.Call(format)
|
|
|
+ if r == 0 {
|
|
|
+ return nil, errUnavailable
|
|
|
+ }
|
|
|
+
|
|
|
+ // try again until open clipboard successed
|
|
|
+ for {
|
|
|
+ r, _, _ = openClipboard.Call()
|
|
|
+ if r == 0 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+ defer closeClipboard.Call()
|
|
|
+
|
|
|
+ switch format {
|
|
|
+ case cFmtDIBV5:
|
|
|
+ return readImage()
|
|
|
+ case cFmtUnicodeText:
|
|
|
+ fallthrough
|
|
|
+ default:
|
|
|
+ return readText()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// write writes the given data to clipboard and
|
|
|
+// returns true if success or false if failed.
|
|
|
+func write(t Format, buf []byte) (<-chan struct{}, error) {
|
|
|
+ errch := make(chan error)
|
|
|
+ changed := make(chan struct{}, 1)
|
|
|
+ go func() {
|
|
|
+ // make sure GetClipboardSequenceNumber happens with
|
|
|
+ // OpenClipboard on the same thread.
|
|
|
+ runtime.LockOSThread()
|
|
|
+ defer runtime.UnlockOSThread()
|
|
|
+ for {
|
|
|
+ r, _, _ := openClipboard.Call(0)
|
|
|
+ if r == 0 {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+
|
|
|
+ // var param uintptr
|
|
|
+ switch t {
|
|
|
+ case FmtImage:
|
|
|
+ err := writeImage(buf)
|
|
|
+ if err != nil {
|
|
|
+ errch <- err
|
|
|
+ closeClipboard.Call()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ case FmtText:
|
|
|
+ fallthrough
|
|
|
+ default:
|
|
|
+ // param = cFmtUnicodeText
|
|
|
+ err := writeText(buf)
|
|
|
+ if err != nil {
|
|
|
+ errch <- err
|
|
|
+ closeClipboard.Call()
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // Close the clipboard otherwise other applications cannot
|
|
|
+ // paste the data.
|
|
|
+ closeClipboard.Call()
|
|
|
+
|
|
|
+ cnt, _, _ := getClipboardSequenceNumber.Call()
|
|
|
+ errch <- nil
|
|
|
+ for {
|
|
|
+ time.Sleep(time.Second)
|
|
|
+ cur, _, _ := getClipboardSequenceNumber.Call()
|
|
|
+ if cur != cnt {
|
|
|
+ changed <- struct{}{}
|
|
|
+ close(changed)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ err := <-errch
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return changed, nil
|
|
|
+}
|
|
|
+
|
|
|
+func watch(ctx context.Context, t Format) <-chan []byte {
|
|
|
+ recv := make(chan []byte, 1)
|
|
|
+ ready := make(chan struct{})
|
|
|
+ go func() {
|
|
|
+ // not sure if we are too slow or the user too fast :)
|
|
|
+ ti := time.NewTicker(time.Second)
|
|
|
+ cnt, _, _ := getClipboardSequenceNumber.Call()
|
|
|
+ ready <- struct{}{}
|
|
|
+ for {
|
|
|
+ select {
|
|
|
+ case <-ctx.Done():
|
|
|
+ close(recv)
|
|
|
+ return
|
|
|
+ case <-ti.C:
|
|
|
+ cur, _, _ := getClipboardSequenceNumber.Call()
|
|
|
+ if cnt != cur {
|
|
|
+ b := Read(t)
|
|
|
+ if b == nil {
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ recv <- b
|
|
|
+ cnt = cur
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }()
|
|
|
+ <-ready
|
|
|
+ return recv
|
|
|
+}
|
|
|
+
|
|
|
+const (
|
|
|
+ cFmtBitmap = 2 // Win+PrintScreen
|
|
|
+ cFmtUnicodeText = 13
|
|
|
+ cFmtDIBV5 = 17
|
|
|
+ // Screenshot taken from special shortcut is in different format (why??), see:
|
|
|
+ // https://jpsoft.com/forums/threads/detecting-clipboard-format.5225/
|
|
|
+ cFmtDataObject = 49161 // Shift+Win+s, returned from enumClipboardFormats
|
|
|
+ gmemMoveable = 0x0002
|
|
|
+)
|
|
|
+
|
|
|
+// BITMAPV5Header structure, see:
|
|
|
+// https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header
|
|
|
+type bitmapV5Header struct {
|
|
|
+ Size uint32
|
|
|
+ Width int32
|
|
|
+ Height int32
|
|
|
+ Planes uint16
|
|
|
+ BitCount uint16
|
|
|
+ Compression uint32
|
|
|
+ SizeImage uint32
|
|
|
+ XPelsPerMeter int32
|
|
|
+ YPelsPerMeter int32
|
|
|
+ ClrUsed uint32
|
|
|
+ ClrImportant uint32
|
|
|
+ RedMask uint32
|
|
|
+ GreenMask uint32
|
|
|
+ BlueMask uint32
|
|
|
+ AlphaMask uint32
|
|
|
+ CSType uint32
|
|
|
+ Endpoints struct {
|
|
|
+ CiexyzRed, CiexyzGreen, CiexyzBlue struct {
|
|
|
+ CiexyzX, CiexyzY, CiexyzZ int32 // FXPT2DOT30
|
|
|
+ }
|
|
|
+ }
|
|
|
+ GammaRed uint32
|
|
|
+ GammaGreen uint32
|
|
|
+ GammaBlue uint32
|
|
|
+ Intent uint32
|
|
|
+ ProfileData uint32
|
|
|
+ ProfileSize uint32
|
|
|
+ Reserved uint32
|
|
|
+}
|
|
|
+
|
|
|
+type bitmapHeader struct {
|
|
|
+ Size uint32
|
|
|
+ Width uint32
|
|
|
+ Height uint32
|
|
|
+ PLanes uint16
|
|
|
+ BitCount uint16
|
|
|
+ Compression uint32
|
|
|
+ SizeImage uint32
|
|
|
+ XPelsPerMeter uint32
|
|
|
+ YPelsPerMeter uint32
|
|
|
+ ClrUsed uint32
|
|
|
+ ClrImportant uint32
|
|
|
+}
|
|
|
+
|
|
|
+// Calling a Windows DLL, see:
|
|
|
+// https://github.com/golang/go/wiki/WindowsDLLs
|
|
|
+var (
|
|
|
+ user32 = syscall.MustLoadDLL("user32")
|
|
|
+ // Opens the clipboard for examination and prevents other
|
|
|
+ // applications from modifying the clipboard content.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-openclipboard
|
|
|
+ openClipboard = user32.MustFindProc("OpenClipboard")
|
|
|
+ // Closes the clipboard.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-closeclipboard
|
|
|
+ closeClipboard = user32.MustFindProc("CloseClipboard")
|
|
|
+ // Empties the clipboard and frees handles to data in the clipboard.
|
|
|
+ // The function then assigns ownership of the clipboard to the
|
|
|
+ // window that currently has the clipboard open.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-emptyclipboard
|
|
|
+ emptyClipboard = user32.MustFindProc("EmptyClipboard")
|
|
|
+ // Retrieves data from the clipboard in a specified format.
|
|
|
+ // The clipboard must have been opened previously.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclipboarddata
|
|
|
+ getClipboardData = user32.MustFindProc("GetClipboardData")
|
|
|
+ // Places data on the clipboard in a specified clipboard format.
|
|
|
+ // The window must be the current clipboard owner, and the
|
|
|
+ // application must have called the OpenClipboard function. (When
|
|
|
+ // responding to the WM_RENDERFORMAT message, the clipboard owner
|
|
|
+ // must not call OpenClipboard before calling SetClipboardData.)
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setclipboarddata
|
|
|
+ setClipboardData = user32.MustFindProc("SetClipboardData")
|
|
|
+ // Determines whether the clipboard contains data in the specified format.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-isclipboardformatavailable
|
|
|
+ isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable")
|
|
|
+ // Clipboard data formats are stored in an ordered list. To perform
|
|
|
+ // an enumeration of clipboard data formats, you make a series of
|
|
|
+ // calls to the EnumClipboardFormats function. For each call, the
|
|
|
+ // format parameter specifies an available clipboard format, and the
|
|
|
+ // function returns the next available clipboard format.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-isclipboardformatavailable
|
|
|
+ enumClipboardFormats = user32.MustFindProc("EnumClipboardFormats")
|
|
|
+ // Retrieves the clipboard sequence number for the current window station.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getclipboardsequencenumber
|
|
|
+ getClipboardSequenceNumber = user32.MustFindProc("GetClipboardSequenceNumber")
|
|
|
+ // Registers a new clipboard format. This format can then be used as
|
|
|
+ // a valid clipboard format.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-registerclipboardformata
|
|
|
+ registerClipboardFormatA = user32.MustFindProc("RegisterClipboardFormatA")
|
|
|
+
|
|
|
+ kernel32 = syscall.NewLazyDLL("kernel32")
|
|
|
+
|
|
|
+ // Locks a global memory object and returns a pointer to the first
|
|
|
+ // byte of the object's memory block.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globallock
|
|
|
+ gLock = kernel32.NewProc("GlobalLock")
|
|
|
+ // Decrements the lock count associated with a memory object that was
|
|
|
+ // allocated with GMEM_MOVEABLE. This function has no effect on memory
|
|
|
+ // objects allocated with GMEM_FIXED.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalunlock
|
|
|
+ gUnlock = kernel32.NewProc("GlobalUnlock")
|
|
|
+ // Allocates the specified number of bytes from the heap.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalalloc
|
|
|
+ gAlloc = kernel32.NewProc("GlobalAlloc")
|
|
|
+ // Frees the specified global memory object and invalidates its handle.
|
|
|
+ // https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-globalfree
|
|
|
+ gFree = kernel32.NewProc("GlobalFree")
|
|
|
+ memMove = kernel32.NewProc("RtlMoveMemory")
|
|
|
+)
|