LoginSignup
3
1

More than 5 years have passed since last update.

Goでクリップボードにテキスト/ファイルリストの読み書きをする(Windows)

Posted at

Goにもクリップボード使えるライブラリありますが、ファイルリストの読み書きできるのなさそうなので作ってみました。Windows限定ですけど

clip.go
package main

import (
    "fmt"
    "syscall"
    "unsafe"

    "github.com/AllenDang/w32"
)

// #include <string.h>
import "C"

type clipboardFormat uint

const (
    textFormat     clipboardFormat = w32.CF_UNICODETEXT
    fileListFormat clipboardFormat = w32.CF_HDROP
)

func isAvailable(format clipboardFormat) bool {
    return w32.IsClipboardFormatAvailable(uint(format))
}

func getText() (string, error) {
    if w32.OpenClipboard(0) {
        defer w32.CloseClipboard()
        hclip := w32.HGLOBAL(w32.GetClipboardData(w32.CF_UNICODETEXT))
        if hclip == 0 {
            return "", fmt.Errorf("GetClipboardData")
        }

        u16pstr := w32.GlobalLock(hclip)
        defer w32.GlobalUnlock(hclip)
        return w32.UTF16PtrToString((*uint16)(u16pstr)), nil
    }
    return "", fmt.Errorf("OpenClipboard")
}

func getFileList() ([]string, error) {
    if w32.OpenClipboard(0) {
        defer w32.CloseClipboard()
        hclip := w32.HDROP(w32.GetClipboardData(w32.CF_HDROP))
        if hclip == 0 {
            return nil, fmt.Errorf("GetClipboardData")
        }

        _, count := w32.DragQueryFile(hclip, 0xffffffff)
        files := make([]string, count)
        for i := uint(0); i < count; i++ {
            files[i], _ = w32.DragQueryFile(hclip, i)
        }
        return files, nil
    }
    return nil, fmt.Errorf("OpenClipboard")
}

func setText(text string) error {
    if w32.OpenClipboard(0) {
        defer w32.CloseClipboard()
        u16str, err := syscall.UTF16FromString(text)
        if err != nil {
            return fmt.Errorf("UTF16FromString")
        }

        hmem := w32.GlobalAlloc(w32.GHND, uint32(2*len(u16str)))
        outPstr := w32.GlobalLock(hmem)
        defer w32.GlobalUnlock(hmem)

        w32.Lstrcpy((*[1]uint16)(outPstr)[:], &u16str[0])
        w32.EmptyClipboard()
        w32.SetClipboardData(w32.CF_UNICODETEXT, w32.HANDLE(hmem))
    }
    return fmt.Errorf("OpenClipboard")
}

func buildU16files(files []string) ([]uint16, error) {
    var u16files []uint16
    for _, s := range files {
        u16str, err := syscall.UTF16FromString(s)
        if err != nil {
            return nil, fmt.Errorf("UTF16FromString")
        }
        u16files = append(u16files, u16str...)
    }
    u16files = append(u16files, 0)
    return u16files, nil
}
func setFileList(files []string) error {
    if w32.OpenClipboard(0) {
        defer w32.CloseClipboard()
        u16files, err := buildU16files(files)
        if err != nil {
            return err
        }

        type POINT struct {
            x int32
            y int32
        }
        type DROPFILES struct {
            pFiles w32.DWORD
            pt     POINT
            fNC    w32.BOOL
            fWide  w32.BOOL
        }

        dfiles := DROPFILES{fWide: w32.BoolToBOOL(true)}
        dfiles.pFiles = w32.DWORD(unsafe.Sizeof(dfiles))

        hmem := w32.GlobalAlloc(w32.GHND, uint32(dfiles.pFiles)+uint32(2*len(u16files)))
        outFiles := w32.GlobalLock(hmem)
        defer w32.GlobalUnlock(hmem)

        C.memcpy(outFiles, unsafe.Pointer(&dfiles), C.size_t(dfiles.pFiles))
        C.memcpy(unsafe.Pointer(uintptr(outFiles)+uintptr(dfiles.pFiles)),
            unsafe.Pointer(&u16files[0]), C.size_t(2*len(u16files)))

        w32.EmptyClipboard()
        w32.SetClipboardData(w32.CF_HDROP, w32.HANDLE(hmem))
    }
    return fmt.Errorf("OpenClipboard")
}

func main() {
    if isAvailable(textFormat) {
        text, err := getText()
        if err != nil {
            fmt.Println("error:", err)
            return
        }
        fmt.Println("clipdata[text]:", text)
        setText("text:" + text)

    } else if isAvailable(fileListFormat) {
        files, err := getFileList()
        if err != nil {
            fmt.Println("error:", err)
            return
        }
        fmt.Println("clipdata[fileList]:", files)
        if len(files) > 0 {
            setFileList(files[1:])
        }
    }
}

テキストの読み込み

CF_UNICODETEXTを指定してGetClipboardDataでハンドルとってきて、GlobalLockでデータとってくればOK

ファイルリストの読み込み

DragQueryFile0xffffffffを指定するとファイルリストの件数が取れます。
その後、件数分DragQueryFileを読んでファイルパスを取得しています。

テキストの書き込み

GlobalAllocで書き込み先のメモリを確保して、GlobalLockで書き込み先のポインタを取得。その後、書き込み先に文字列コピーしています。

EmptyClipboardをしないと直前にクリップボードを使っていたアプリ内での貼り付けがうまくいかないことがあります。

(*[1]uint16)(outPstr)[:]は、unsafe.Pointerからスライスへの変換です。(*[0]uint16)にするとpanicになります。(なぜ?)

ファイルリストの書き込み

ファイルリストの書き込みでは、書き込み先にDROPFILES構造体と"ファイル\0ファイル\0 ... ファイル\0\0"を書き込む必要があります。

C.memcpyの部分はw32.MoveMemoryでもできそうです。

main部分

この部分はサンプルコードで、テキストの場合はクリップボードの先頭に"text:"を追加、ファイルリストの場合はファイルを1つ減らすようにしています。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1