Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multi clipboard format support for Windows #29

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions clipboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,28 @@ func ReadAll() (string, error) {
return readAll()
}

func ReadAllWithFormat(cf uintptr) (string, error) {
return readAllWithFormat(cf)
}

// WriteAll write string to clipboard
func WriteAll(text string) error {
return writeAll(text)
}

// WriteAllWithFormat write string to clipboard with format
func WriteAllWithFormat(text string, cf uintptr) error {
return writeAllWithFormat(text, cf)
}

func ClearClipboard() error {
return clearClipboard()
}

func GetClipboardFormat(format string) (uintptr, error) {
return getClipboardFormat(format)
}

// Unsupported might be set true during clipboard init, to help callers decide
// whether or not to offer clipboard options.
var Unsupported bool
16 changes: 16 additions & 0 deletions clipboard_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ func readAll() (string, error) {
return string(out), nil
}

func readAllWithFormat(_ uintptr) (string, error) {
return readAll()
}

func writeAll(text string) error {
copyCmd := getCopyCommand()
in, err := copyCmd.StdinPipe()
Expand All @@ -50,3 +54,15 @@ func writeAll(text string) error {
}
return copyCmd.Wait()
}

func writeAllWithFormat(text string, _ uintptr) error {
return writeAll(text)
}

func clearClipboard() error {
return nil
}

func getClipboardFormat(_ string) (uintptr, error) {
return 0, nil
}
6 changes: 2 additions & 4 deletions clipboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package clipboard_test
package clipboard

import (
"testing"

. "github.com/atotto/clipboard"
)

func TestCopyAndPaste(t *testing.T) {
expected := "日本語"
expected := "日本語\n"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?


err := WriteAll(expected)
if err != nil {
Expand Down
16 changes: 16 additions & 0 deletions clipboard_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ func readAll() (string, error) {
return string(out), nil
}

func readAllWithFormat(_ uintptr) (string, error) {
return readAll()
}

func writeAll(text string) error {
if Unsupported {
return missingCommands
Expand All @@ -96,3 +100,15 @@ func writeAll(text string) error {
}
return copyCmd.Wait()
}

func writeAllWithFormat(text string, _ uintptr) error {
return writeAll(text)
}

func clearClipboard() error {
return nil
}

func getClipboardFormat(_ string) (uintptr, error) {
return 0, nil
}
82 changes: 64 additions & 18 deletions clipboard_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,26 @@ import (

const (
cfUnicodetext = 13
cfText = 1
gmemMoveable = 0x0002
)

var (
user32 = syscall.MustLoadDLL("user32")
openClipboard = user32.MustFindProc("OpenClipboard")
closeClipboard = user32.MustFindProc("CloseClipboard")
emptyClipboard = user32.MustFindProc("EmptyClipboard")
getClipboardData = user32.MustFindProc("GetClipboardData")
setClipboardData = user32.MustFindProc("SetClipboardData")
user32 = syscall.MustLoadDLL("user32")
openClipboard = user32.MustFindProc("OpenClipboard")
closeClipboard = user32.MustFindProc("CloseClipboard")
emptyClipboard = user32.MustFindProc("EmptyClipboard")
getClipboardData = user32.MustFindProc("GetClipboardData")
setClipboardData = user32.MustFindProc("SetClipboardData")
registerClipboardFormat = user32.MustFindProc("RegisterClipboardFormatW")

kernel32 = syscall.NewLazyDLL("kernel32")
globalAlloc = kernel32.NewProc("GlobalAlloc")
globalFree = kernel32.NewProc("GlobalFree")
globalSize = kernel32.NewProc("GlobalSize")
globalLock = kernel32.NewProc("GlobalLock")
globalUnlock = kernel32.NewProc("GlobalUnlock")
lstrcpy = kernel32.NewProc("lstrcpyW")
memcpy = kernel32.NewProc("RtlCopyMemory")
)

// waitOpenClipboard opens the clipboard, waiting for up to a second to do so.
Expand All @@ -50,14 +53,27 @@ func waitOpenClipboard() error {
}

func readAll() (string, error) {
return readAllWithFormat(cfText)
}

func readAllWithFormat(cf uintptr) (string, error) {
err := waitOpenClipboard()
if err != nil {
return "", err
}
defer closeClipboard.Call()

h, _, err := getClipboardData.Call(cfUnicodetext)
h, _, err := getClipboardData.Call(cf)
if h == 0 {
if err != syscall.Errno(0) {
return "", err
}

return "", nil
}

size, _, err := globalSize.Call(h)
if size == 0 {
return "", err
}

Expand All @@ -66,7 +82,7 @@ func readAll() (string, error) {
return "", err
}

text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:])
text := string((*[1 << 20]byte)(unsafe.Pointer(l))[:(size - 1)])

r, _, err := globalUnlock.Call(h)
if r == 0 {
Expand All @@ -77,25 +93,25 @@ func readAll() (string, error) {
}

func writeAll(text string) error {
return writeAllWithFormat(text, cfText)
}

func writeAllWithFormat(text string, cf uintptr) error {
err := waitOpenClipboard()
if err != nil {
return err
}
defer closeClipboard.Call()

r, _, err := emptyClipboard.Call(0)
if r == 0 {
return err
}

data := syscall.StringToUTF16(text)
data := syscall.StringByteSlice(text)

// "If the hMem parameter identifies a memory object, the object must have
// been allocated using the function with the GMEM_MOVEABLE flag."
h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0]))))
h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)))
if h == 0 {
return err
}

defer func() {
if h != 0 {
globalFree.Call(h)
Expand All @@ -107,7 +123,7 @@ func writeAll(text string) error {
return err
}

r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0])))
r, _, err := memcpy.Call(l, uintptr(unsafe.Pointer(&data[0])), uintptr(len(data)))
if r == 0 {
return err
}
Expand All @@ -119,10 +135,40 @@ func writeAll(text string) error {
}
}

r, _, err = setClipboardData.Call(cfUnicodetext, h)
r, _, err = setClipboardData.Call(cf, h)
if r == 0 {
return err
}

h = 0 // suppress deferred cleanup
return nil
}

func getClipboardFormat(text string) (uintptr, error) {
ptr, err := syscall.UTF16PtrFromString(text)
if err != nil {
return 0, err
}

cf, _, err := registerClipboardFormat.Call(uintptr(unsafe.Pointer(ptr)))
if cf == 0 {
return 0, err
}

return cf, nil
}

func clearClipboard() error {
err := waitOpenClipboard()
if err != nil {
return err
}
defer closeClipboard.Call()

r, _, err := emptyClipboard.Call()
if r == 0 {
return err
}

return nil
}
3 changes: 3 additions & 0 deletions cmd/goclip/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
bin
goclip
goclip.exe