Add vendor

Specifically because I want to modify a dependency
This commit is contained in:
2024-08-26 01:00:50 +02:00
parent 64061af4c8
commit 3c5e991ec5
704 changed files with 291662 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/

View File

@@ -0,0 +1,13 @@
# 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>
FROM golang:1.17
RUN apt-get update && apt-get install -y \
xvfb libx11-dev libegl1-mesa-dev libgles2-mesa-dev \
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /app
COPY . .
CMD [ "sh", "-c", "./tests/test-docker.sh" ]

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021 Changkun Ou <contact@changkun.de>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,160 @@
# clipboard [![PkgGoDev](https://pkg.go.dev/badge/golang.design/x/clipboard)](https://pkg.go.dev/golang.design/x/clipboard) ![](https://changkun.de/urlstat?mode=github&repo=golang-design/clipboard) ![clipboard](https://github.com/golang-design/clipboard/workflows/clipboard/badge.svg?branch=main)
Cross platform (macOS/Linux/Windows/Android/iOS) clipboard package in Go
```go
import "golang.design/x/clipboard"
```
## Features
- Cross platform supports: **macOS, Linux (X11), Windows, iOS, and Android**
- Copy/paste UTF-8 text
- Copy/paste PNG encoded images (Desktop-only)
- Command `gclip` as a demo application
- Mobile app `gclip-gui` as a demo application
## API Usage
Package clipboard provides cross platform clipboard access and supports
macOS/Linux/Windows/Android/iOS platform. Before interacting with the
clipboard, one must call Init to assert if it is possible to use this
package:
```go
// Init returns an error if the package is not ready for use.
err := clipboard.Init()
if err != nil {
panic(err)
}
```
The most common operations are `Read` and `Write`. To use them:
```go
// write/read text format data of the clipboard, and
// the byte buffer regarding the text are UTF8 encoded.
clipboard.Write(clipboard.FmtText, []byte("text data"))
clipboard.Read(clipboard.FmtText)
// write/read image format data of the clipboard, and
// the byte buffer regarding the image are PNG encoded.
clipboard.Write(clipboard.FmtImage, []byte("image data"))
clipboard.Read(clipboard.FmtImage)
```
Note that read/write regarding image format assumes that the bytes are
PNG encoded since it serves the alpha blending purpose that might be
used in other graphical software.
In addition, `clipboard.Write` returns a channel that can receive an
empty struct as a signal, which indicates the corresponding write call
to the clipboard is outdated, meaning the clipboard has been overwritten
by others and the previously written data is lost. For instance:
```go
changed := clipboard.Write(clipboard.FmtText, []byte("text data"))
select {
case <-changed:
println(`"text data" is no longer available from clipboard.`)
}
```
You can ignore the returning channel if you don't need this type of
notification. Furthermore, when you need more than just knowing whether
clipboard data is changed, use the watcher API:
```go
ch := clipboard.Watch(context.TODO(), clipboard.FmtText)
for data := range ch {
// print out clipboard data whenever it is changed
println(string(data))
}
```
## Demos
- A command line tool `gclip` for command line clipboard accesses, see document [here](./cmd/gclip/README.md).
- A GUI application `gclip-gui` for functionality verifications on mobile systems, see a document [here](./cmd/gclip-gui/README.md).
## Command Usage
`gclip` command offers the ability to interact with the system clipboard
from the shell. To install:
```bash
$ go install golang.design/x/clipboard/cmd/gclip@latest
```
```bash
$ gclip
gclip is a command that provides clipboard interaction.
usage: gclip [-copy|-paste] [-f <file>]
options:
-copy
copy data to clipboard
-f string
source or destination to a given file path
-paste
paste data from clipboard
examples:
gclip -paste paste from clipboard and prints the content
gclip -paste -f x.txt paste from clipboard and save as text to x.txt
gclip -paste -f x.png paste from clipboard and save as image to x.png
cat x.txt | gclip -copy copy content from x.txt to clipboard
gclip -copy -f x.txt copy content from x.txt to clipboard
gclip -copy -f x.png copy x.png as image data to clipboard
```
If `-copy` is used, the command will exit when the data is no longer
available from the clipboard. You can always send the command to the
background using a shell `&` operator, for example:
```bash
$ cat x.txt | gclip -copy &
```
## Platform Specific Details
This package spent efforts to provide cross platform abstraction regarding
accessing system clipboards, but here are a few details you might need to know.
### Dependency
- macOS: require Cgo, no dependency
- Linux: require X11 dev package. For instance, install `libx11-dev` or `xorg-dev` or `libX11-devel` to access X window system.
- Windows: no Cgo, no dependency
- iOS/Android: collaborate with [`gomobile`](https://golang.org/x/mobile)
### Screenshot
In general, when you need test your implementation regarding images,
There are system level shortcuts to put screenshot image into your system clipboard:
- On macOS, use `Ctrl+Shift+Cmd+4`
- On Linux/Ubuntu, use `Ctrl+Shift+PrintScreen`
- On Windows, use `Shift+Win+s`
As described in the API documentation, the package supports read/write
UTF8 encoded plain text or PNG encoded image data. Thus,
the other types of data are not supported yet, i.e. undefined behavior.
## Who is using this package?
The main purpose of building this package is to support the
[midgard](https://changkun.de/s/midgard) project, which offers
clipboard-based features like universal clipboard service that syncs
clipboard content across multiple systems, allocating public accessible
for clipboard content, etc.
To know more projects, check our [wiki](https://github.com/golang-design/clipboard/wiki) page.
## License
MIT | &copy; 2021 The golang.design Initiative Authors, written by [Changkun Ou](https://changkun.de).

View File

@@ -0,0 +1,154 @@
// 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>
/*
Package clipboard provides cross platform clipboard access and supports
macOS/Linux/Windows/Android/iOS platform. Before interacting with the
clipboard, one must call Init to assert if it is possible to use this
package:
err := clipboard.Init()
if err != nil {
panic(err)
}
The most common operations are `Read` and `Write`. To use them:
// write/read text format data of the clipboard, and
// the byte buffer regarding the text are UTF8 encoded.
clipboard.Write(clipboard.FmtText, []byte("text data"))
clipboard.Read(clipboard.FmtText)
// write/read image format data of the clipboard, and
// the byte buffer regarding the image are PNG encoded.
clipboard.Write(clipboard.FmtImage, []byte("image data"))
clipboard.Read(clipboard.FmtImage)
Note that read/write regarding image format assumes that the bytes are
PNG encoded since it serves the alpha blending purpose that might be
used in other graphical software.
In addition, `clipboard.Write` returns a channel that can receive an
empty struct as a signal, which indicates the corresponding write call
to the clipboard is outdated, meaning the clipboard has been overwritten
by others and the previously written data is lost. For instance:
changed := clipboard.Write(clipboard.FmtText, []byte("text data"))
select {
case <-changed:
println(`"text data" is no longer available from clipboard.`)
}
You can ignore the returning channel if you don't need this type of
notification. Furthermore, when you need more than just knowing whether
clipboard data is changed, use the watcher API:
ch := clipboard.Watch(context.TODO(), clipboard.FmtText)
for data := range ch {
// print out clipboard data whenever it is changed
println(string(data))
}
*/
package clipboard // import "golang.design/x/clipboard"
import (
"context"
"errors"
"fmt"
"os"
"sync"
)
var (
// activate only for running tests.
debug = false
errUnavailable = errors.New("clipboard unavailable")
errUnsupported = errors.New("unsupported format")
)
// Format represents the format of clipboard data.
type Format int
// All sorts of supported clipboard data
const (
// FmtText indicates plain text clipboard format
FmtText Format = iota
// FmtImage indicates image/png clipboard format
FmtImage
)
var (
// Due to the limitation on operating systems (such as darwin),
// concurrent read can even cause panic, use a global lock to
// guarantee one read at a time.
lock = sync.Mutex{}
initOnce sync.Once
initError error
)
// Init initializes the clipboard package. It returns an error
// if the clipboard is not available to use. This may happen if the
// target system lacks required dependency, such as libx11-dev in X11
// environment. For example,
//
// err := clipboard.Init()
// if err != nil {
// panic(err)
// }
//
// If Init returns an error, any subsequent Read/Write/Watch call
// may result in an unrecoverable panic.
func Init() error {
initOnce.Do(func() {
initError = initialize()
})
return initError
}
// Read returns a chunk of bytes of the clipboard data if it presents
// in the desired format t presents. Otherwise, it returns nil.
func Read(t Format) []byte {
lock.Lock()
defer lock.Unlock()
buf, err := read(t)
if err != nil {
if debug {
fmt.Fprintf(os.Stderr, "read clipboard err: %v\n", err)
}
return nil
}
return buf
}
// Write writes a given buffer to the clipboard in a specified format.
// Write returned a receive-only channel can receive an empty struct
// as a signal, which indicates the clipboard has been overwritten from
// this write.
// If format t indicates an image, then the given buf assumes
// the image data is PNG encoded.
func Write(t Format, buf []byte) <-chan struct{} {
lock.Lock()
defer lock.Unlock()
changed, err := write(t, buf)
if err != nil {
if debug {
fmt.Fprintf(os.Stderr, "write to clipboard err: %v\n", err)
}
return nil
}
return changed
}
// Watch returns a receive-only channel that received the clipboard data
// whenever any change of clipboard data in the desired format happens.
//
// The returned channel will be closed if the given context is canceled.
func Watch(ctx context.Context, t Format) <-chan []byte {
return watch(ctx, t)
}

View File

@@ -0,0 +1,81 @@
// 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 android
// +build android
#include <android/log.h>
#include <jni.h>
#include <stdlib.h>
#include <string.h>
#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, \
"GOLANG.DESIGN/X/CLIPBOARD", __VA_ARGS__)
static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
jmethodID m = (*env)->GetMethodID(env, clazz, name, sig);
if (m == 0) {
(*env)->ExceptionClear(env);
LOG_FATAL("cannot find method %s %s", name, sig);
return 0;
}
return m;
}
jobject get_clipboard(uintptr_t jni_env, uintptr_t ctx) {
JNIEnv *env = (JNIEnv*)jni_env;
jclass ctxClass = (*env)->GetObjectClass(env, (jobject)ctx);
jmethodID getSystemService = find_method(env, ctxClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
jstring service = (*env)->NewStringUTF(env, "clipboard");
jobject ret = (jobject)(*env)->CallObjectMethod(env, (jobject)ctx, getSystemService, service);
jthrowable err = (*env)->ExceptionOccurred(env);
if (err != NULL) {
LOG_FATAL("cannot find clipboard");
(*env)->ExceptionClear(env);
return NULL;
}
return ret;
}
char *clipboard_read_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
JNIEnv *env = (JNIEnv*)jni_env;
jobject mgr = get_clipboard(jni_env, ctx);
if (mgr == NULL) {
return NULL;
}
jclass mgrClass = (*env)->GetObjectClass(env, mgr);
jmethodID getText = find_method(env, mgrClass, "getText", "()Ljava/lang/CharSequence;");
jobject content = (jstring)(*env)->CallObjectMethod(env, mgr, getText);
if (content == NULL) {
return NULL;
}
jclass clzCharSequence = (*env)->GetObjectClass(env, content);
jmethodID toString = (*env)->GetMethodID(env, clzCharSequence, "toString", "()Ljava/lang/String;");
jobject s = (*env)->CallObjectMethod(env, content, toString);
const char *chars = (*env)->GetStringUTFChars(env, s, NULL);
char *copy = strdup(chars);
(*env)->ReleaseStringUTFChars(env, s, chars);
return copy;
}
void clipboard_write_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *str) {
JNIEnv *env = (JNIEnv*)jni_env;
jobject mgr = get_clipboard(jni_env, ctx);
if (mgr == NULL) {
return;
}
jclass mgrClass = (*env)->GetObjectClass(env, mgr);
jmethodID setText = find_method(env, mgrClass, "setText", "(Ljava/lang/CharSequence;)V");
(*env)->CallVoidMethod(env, mgr, setText, (*env)->NewStringUTF(env, str));
}

View File

@@ -0,0 +1,103 @@
// 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 android
// +build android
package clipboard
/*
#cgo LDFLAGS: -landroid -llog
#include <stdlib.h>
char *clipboard_read_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx);
void clipboard_write_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *str);
*/
import "C"
import (
"bytes"
"context"
"time"
"unsafe"
"golang.org/x/mobile/app"
)
func initialize() error { return nil }
func read(t Format) (buf []byte, err error) {
switch t {
case FmtText:
s := ""
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
cs := C.clipboard_read_string(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx))
if cs == nil {
return nil
}
s = C.GoString(cs)
C.free(unsafe.Pointer(cs))
return nil
}); err != nil {
return nil, err
}
return []byte(s), nil
case FmtImage:
return nil, errUnsupported
default:
return nil, errUnsupported
}
}
// 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) {
done := make(chan struct{}, 1)
switch t {
case FmtText:
cs := C.CString(string(buf))
defer C.free(unsafe.Pointer(cs))
if err := app.RunOnJVM(func(vm, env, ctx uintptr) error {
C.clipboard_write_string(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx), cs)
done <- struct{}{}
return nil
}); err != nil {
return nil, err
}
return done, nil
case FmtImage:
return nil, errUnsupported
default:
return nil, errUnsupported
}
}
func watch(ctx context.Context, t Format) <-chan []byte {
recv := make(chan []byte, 1)
ti := time.NewTicker(time.Second)
last := Read(t)
go func() {
for {
select {
case <-ctx.Done():
close(recv)
return
case <-ti.C:
b := Read(t)
if b == nil {
continue
}
if bytes.Compare(last, b) != 0 {
recv <- b
last = b
}
}
}
}()
return recv
}

View File

@@ -0,0 +1,123 @@
// 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 darwin && !ios
// +build darwin,!ios
package clipboard
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework Cocoa
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
unsigned int clipboard_read_string(void **out);
unsigned int clipboard_read_image(void **out);
int clipboard_write_string(const void *bytes, NSInteger n);
int clipboard_write_image(const void *bytes, NSInteger n);
NSInteger clipboard_change_count();
*/
import "C"
import (
"context"
"time"
"unsafe"
)
func initialize() error { return nil }
func read(t Format) (buf []byte, err error) {
var (
data unsafe.Pointer
n C.uint
)
switch t {
case FmtText:
n = C.clipboard_read_string(&data)
case FmtImage:
n = C.clipboard_read_image(&data)
}
if data == nil {
return nil, errUnavailable
}
defer C.free(unsafe.Pointer(data))
if n == 0 {
return nil, nil
}
return C.GoBytes(data, C.int(n)), nil
}
// 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) {
var ok C.int
switch t {
case FmtText:
if len(buf) == 0 {
ok = C.clipboard_write_string(unsafe.Pointer(nil), 0)
} else {
ok = C.clipboard_write_string(unsafe.Pointer(&buf[0]),
C.NSInteger(len(buf)))
}
case FmtImage:
if len(buf) == 0 {
ok = C.clipboard_write_image(unsafe.Pointer(nil), 0)
} else {
ok = C.clipboard_write_image(unsafe.Pointer(&buf[0]),
C.NSInteger(len(buf)))
}
default:
return nil, errUnsupported
}
if ok != 0 {
return nil, errUnavailable
}
// use unbuffered data to prevent goroutine leak
changed := make(chan struct{}, 1)
cnt := C.long(C.clipboard_change_count())
go func() {
for {
// not sure if we are too slow or the user too fast :)
time.Sleep(time.Second)
cur := C.long(C.clipboard_change_count())
if cnt != cur {
changed <- struct{}{}
close(changed)
return
}
}
}()
return changed, nil
}
func watch(ctx context.Context, t Format) <-chan []byte {
recv := make(chan []byte, 1)
// not sure if we are too slow or the user too fast :)
ti := time.NewTicker(time.Second)
lastCount := C.long(C.clipboard_change_count())
go func() {
for {
select {
case <-ctx.Done():
close(recv)
return
case <-ti.C:
this := C.long(C.clipboard_change_count())
if lastCount != this {
b := Read(t)
if b == nil {
continue
}
recv <- b
lastCount = this
}
}
}
}()
return recv
}

View File

@@ -0,0 +1,63 @@
// 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 darwin && !ios
// +build darwin,!ios
// Interact with NSPasteboard using Objective-C
// https://developer.apple.com/documentation/appkit/nspasteboard?language=objc
#import <Foundation/Foundation.h>
#import <Cocoa/Cocoa.h>
unsigned int clipboard_read_string(void **out) {
NSPasteboard * pasteboard = [NSPasteboard generalPasteboard];
NSData *data = [pasteboard dataForType:NSPasteboardTypeString];
if (data == nil) {
return 0;
}
NSUInteger siz = [data length];
*out = malloc(siz);
[data getBytes: *out length: siz];
return siz;
}
unsigned int clipboard_read_image(void **out) {
NSPasteboard * pasteboard = [NSPasteboard generalPasteboard];
NSData *data = [pasteboard dataForType:NSPasteboardTypePNG];
if (data == nil) {
return 0;
}
NSUInteger siz = [data length];
*out = malloc(siz);
[data getBytes: *out length: siz];
return siz;
}
int clipboard_write_string(const void *bytes, NSInteger n) {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSData *data = [NSData dataWithBytes: bytes length: n];
[pasteboard clearContents];
BOOL ok = [pasteboard setData: data forType:NSPasteboardTypeString];
if (!ok) {
return -1;
}
return 0;
}
int clipboard_write_image(const void *bytes, NSInteger n) {
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
NSData *data = [NSData dataWithBytes: bytes length: n];
[pasteboard clearContents];
BOOL ok = [pasteboard setData: data forType:NSPasteboardTypePNG];
if (!ok) {
return -1;
}
return 0;
}
NSInteger clipboard_change_count() {
return [[NSPasteboard generalPasteboard] changeCount];
}

View File

@@ -0,0 +1,81 @@
// 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 ios
// +build ios
package clipboard
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework UIKit -framework MobileCoreServices
#import <stdlib.h>
void clipboard_write_string(char *s);
char *clipboard_read_string();
*/
import "C"
import (
"bytes"
"context"
"time"
"unsafe"
)
func initialize() error { return nil }
func read(t Format) (buf []byte, err error) {
switch t {
case FmtText:
return []byte(C.GoString(C.clipboard_read_string())), nil
case FmtImage:
return nil, errUnsupported
default:
return nil, errUnsupported
}
}
// SetContent sets the clipboard content for iOS
func write(t Format, buf []byte) (<-chan struct{}, error) {
done := make(chan struct{}, 1)
switch t {
case FmtText:
cs := C.CString(string(buf))
defer C.free(unsafe.Pointer(cs))
C.clipboard_write_string(cs)
return done, nil
case FmtImage:
return nil, errUnsupported
default:
return nil, errUnsupported
}
}
func watch(ctx context.Context, t Format) <-chan []byte {
recv := make(chan []byte, 1)
ti := time.NewTicker(time.Second)
last := Read(t)
go func() {
for {
select {
case <-ctx.Done():
close(recv)
return
case <-ti.C:
b := Read(t)
if b == nil {
continue
}
if bytes.Compare(last, b) != 0 {
recv <- b
last = b
}
}
}
}()
return recv
}

View File

@@ -0,0 +1,21 @@
// 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 ios
// +build ios
#import <UIKit/UIKit.h>
#import <MobileCoreServices/MobileCoreServices.h>
void clipboard_write_string(char *s) {
NSString *value = [NSString stringWithUTF8String:s];
[[UIPasteboard generalPasteboard] setString:value];
}
char *clipboard_read_string() {
NSString *str = [[UIPasteboard generalPasteboard] string];
return (char *)[str UTF8String];
}

View File

@@ -0,0 +1,264 @@
// 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 linux && !android
// +build linux,!android
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <dlfcn.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
// syncStatus is a function from the Go side.
extern void syncStatus(uintptr_t handle, int status);
void *libX11;
Display* (*P_XOpenDisplay)(int);
void (*P_XCloseDisplay)(Display*);
Window (*P_XDefaultRootWindow)(Display*);
Window (*P_XCreateSimpleWindow)(Display*, Window, int, int, int, int, int, int, int);
Atom (*P_XInternAtom)(Display*, char*, int);
void (*P_XSetSelectionOwner)(Display*, Atom, Window, unsigned long);
Window (*P_XGetSelectionOwner)(Display*, Atom);
void (*P_XNextEvent)(Display*, XEvent*);
int (*P_XChangeProperty)(Display*, Window, Atom, Atom, int, int, unsigned char*, int);
void (*P_XSendEvent)(Display*, Window, int, long , XEvent*);
int (*P_XGetWindowProperty) (Display*, Window, Atom, long, long, Bool, Atom, Atom*, int*, unsigned long *, unsigned long *, unsigned char **);
void (*P_XFree) (void*);
void (*P_XDeleteProperty) (Display*, Window, Atom);
void (*P_XConvertSelection)(Display*, Atom, Atom, Atom, Window, Time);
int initX11() {
if (libX11) {
return 1;
}
libX11 = dlopen("libX11.so", RTLD_LAZY);
if (!libX11) {
return 0;
}
P_XOpenDisplay = (Display* (*)(int)) dlsym(libX11, "XOpenDisplay");
P_XCloseDisplay = (void (*)(Display*)) dlsym(libX11, "XCloseDisplay");
P_XDefaultRootWindow = (Window (*)(Display*)) dlsym(libX11, "XDefaultRootWindow");
P_XCreateSimpleWindow = (Window (*)(Display*, Window, int, int, int, int, int, int, int)) dlsym(libX11, "XCreateSimpleWindow");
P_XInternAtom = (Atom (*)(Display*, char*, int)) dlsym(libX11, "XInternAtom");
P_XSetSelectionOwner = (void (*)(Display*, Atom, Window, unsigned long)) dlsym(libX11, "XSetSelectionOwner");
P_XGetSelectionOwner = (Window (*)(Display*, Atom)) dlsym(libX11, "XGetSelectionOwner");
P_XNextEvent = (void (*)(Display*, XEvent*)) dlsym(libX11, "XNextEvent");
P_XChangeProperty = (int (*)(Display*, Window, Atom, Atom, int, int, unsigned char*, int)) dlsym(libX11, "XChangeProperty");
P_XSendEvent = (void (*)(Display*, Window, int, long , XEvent*)) dlsym(libX11, "XSendEvent");
P_XGetWindowProperty = (int (*)(Display*, Window, Atom, long, long, Bool, Atom, Atom*, int*, unsigned long *, unsigned long *, unsigned char **)) dlsym(libX11, "XGetWindowProperty");
P_XFree = (void (*)(void*)) dlsym(libX11, "XFree");
P_XDeleteProperty = (void (*)(Display*, Window, Atom)) dlsym(libX11, "XDeleteProperty");
P_XConvertSelection = (void (*)(Display*, Atom, Atom, Atom, Window, Time)) dlsym(libX11, "XConvertSelection");
return 1;
}
int clipboard_test() {
if (!initX11()) {
return -1;
}
Display* d = NULL;
for (int i = 0; i < 42; i++) {
d = (*P_XOpenDisplay)(0);
if (d == NULL) {
continue;
}
break;
}
if (d == NULL) {
return -1;
}
(*P_XCloseDisplay)(d);
return 0;
}
// clipboard_write writes the given buf of size n as type typ.
// if start is provided, the value of start will be changed to 1 to indicate
// if the write is availiable for reading.
int clipboard_write(char *typ, unsigned char *buf, size_t n, uintptr_t handle) {
if (!initX11()) {
return -1;
}
Display* d = NULL;
for (int i = 0; i < 42; i++) {
d = (*P_XOpenDisplay)(0);
if (d == NULL) {
continue;
}
break;
}
if (d == NULL) {
syncStatus(handle, -1);
return -1;
}
Window w = (*P_XCreateSimpleWindow)(d, (*P_XDefaultRootWindow)(d), 0, 0, 1, 1, 0, 0, 0);
// Use False because these may not available for the first time.
Atom sel = (*P_XInternAtom)(d, "CLIPBOARD", 0);
Atom atomString = (*P_XInternAtom)(d, "UTF8_STRING", 0);
Atom atomImage = (*P_XInternAtom)(d, "image/png", 0);
Atom targetsAtom = (*P_XInternAtom)(d, "TARGETS", 0);
// Use True to makesure the requested type is a valid type.
Atom target = (*P_XInternAtom)(d, typ, 1);
if (target == None) {
(*P_XCloseDisplay)(d);
syncStatus(handle, -2);
return -2;
}
(*P_XSetSelectionOwner)(d, sel, w, CurrentTime);
if ((*P_XGetSelectionOwner)(d, sel) != w) {
(*P_XCloseDisplay)(d);
syncStatus(handle, -3);
return -3;
}
XEvent event;
XSelectionRequestEvent* xsr;
int notified = 0;
for (;;) {
if (notified == 0) {
syncStatus(handle, 1); // notify Go side
notified = 1;
}
(*P_XNextEvent)(d, &event);
switch (event.type) {
case SelectionClear:
// For debugging:
// printf("x11write: lost ownership of clipboard selection.\n");
// fflush(stdout);
(*P_XCloseDisplay)(d);
return 0;
case SelectionNotify:
// For debugging:
// printf("x11write: notify.\n");
// fflush(stdout);
break;
case SelectionRequest:
if (event.xselectionrequest.selection != sel) {
break;
}
XSelectionRequestEvent * xsr = &event.xselectionrequest;
XSelectionEvent ev = {0};
int R = 0;
ev.type = SelectionNotify;
ev.display = xsr->display;
ev.requestor = xsr->requestor;
ev.selection = xsr->selection;
ev.time = xsr->time;
ev.target = xsr->target;
ev.property = xsr->property;
if (ev.target == atomString && ev.target == target) {
R = (*P_XChangeProperty)(ev.display, ev.requestor, ev.property,
atomString, 8, PropModeReplace, buf, n);
} else if (ev.target == atomImage && ev.target == target) {
R = (*P_XChangeProperty)(ev.display, ev.requestor, ev.property,
atomImage, 8, PropModeReplace, buf, n);
} else if (ev.target == targetsAtom) {
// Reply atoms for supported targets, other clients should
// request the clipboard again and obtain the data if their
// implementation is correct.
Atom targets[] = { atomString, atomImage };
R = (*P_XChangeProperty)(ev.display, ev.requestor, ev.property,
XA_ATOM, 32, PropModeReplace,
(unsigned char *)&targets, sizeof(targets)/sizeof(Atom));
} else {
ev.property = None;
}
if ((R & 2) == 0) (*P_XSendEvent)(d, ev.requestor, 0, 0, (XEvent *)&ev);
break;
}
}
}
// read_data reads the property of a selection if the target atom matches
// the actual atom.
unsigned long read_data(XSelectionEvent *sev, Atom sel, Atom prop, Atom target, char **buf) {
if (!initX11()) {
return -1;
}
unsigned char *data;
Atom actual;
int format;
unsigned long n = 0;
unsigned long size = 0;
if (sev->property == None || sev->selection != sel || sev->property != prop) {
return 0;
}
int ret = (*P_XGetWindowProperty)(sev->display, sev->requestor, sev->property,
0L, (~0L), 0, AnyPropertyType, &actual, &format, &size, &n, &data);
if (ret != Success) {
return 0;
}
if (actual == target && buf != NULL) {
*buf = (char *)malloc(size * sizeof(char));
memcpy(*buf, data, size*sizeof(char));
}
(*P_XFree)(data);
(*P_XDeleteProperty)(sev->display, sev->requestor, sev->property);
return size * sizeof(char);
}
// clipboard_read reads the clipboard selection in given format typ.
// the readed bytes is written into buf and returns the size of the buffer.
//
// The caller of this function should responsible for the free of the buf.
unsigned long clipboard_read(char* typ, char **buf) {
if (!initX11()) {
return -1;
}
Display* d = NULL;
for (int i = 0; i < 42; i++) {
d = (*P_XOpenDisplay)(0);
if (d == NULL) {
continue;
}
break;
}
if (d == NULL) {
return -1;
}
Window w = (*P_XCreateSimpleWindow)(d, (*P_XDefaultRootWindow)(d), 0, 0, 1, 1, 0, 0, 0);
// Use False because these may not available for the first time.
Atom sel = (*P_XInternAtom)(d, "CLIPBOARD", False);
Atom prop = (*P_XInternAtom)(d, "GOLANG_DESIGN_DATA", False);
// Use True to makesure the requested type is a valid type.
Atom target = (*P_XInternAtom)(d, typ, True);
if (target == None) {
(*P_XCloseDisplay)(d);
return -2;
}
(*P_XConvertSelection)(d, sel, target, prop, w, CurrentTime);
XEvent event;
for (;;) {
(*P_XNextEvent)(d, &event);
if (event.type != SelectionNotify) continue;
break;
}
unsigned long n = read_data((XSelectionEvent *)&event.xselection, sel, prop, target, buf);
(*P_XCloseDisplay)(d);
return n;
}

View File

@@ -0,0 +1,171 @@
// 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 linux && !android
// +build linux,!android
package clipboard
/*
#cgo LDFLAGS: -ldl
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
int clipboard_test();
int clipboard_write(
char* typ,
unsigned char* buf,
size_t n,
uintptr_t handle
);
unsigned long clipboard_read(char* typ, char **out);
*/
import "C"
import (
"bytes"
"context"
"fmt"
"os"
"runtime"
"runtime/cgo"
"time"
"unsafe"
)
var helpmsg = `%w: Failed to initialize the X11 display, and the clipboard package
will not work properly. Install the following dependency may help:
apt install -y libx11-dev
If the clipboard package is in an environment without a frame buffer,
such as a cloud server, it may also be necessary to install xvfb:
apt install -y xvfb
and initialize a virtual frame buffer:
Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 &
export DISPLAY=:99.0
Then this package should be ready to use.
`
func initialize() error {
ok := C.clipboard_test()
if ok != 0 {
return fmt.Errorf(helpmsg, errUnavailable)
}
return nil
}
func read(t Format) (buf []byte, err error) {
switch t {
case FmtText:
return readc("UTF8_STRING")
case FmtImage:
return readc("image/png")
}
return nil, errUnsupported
}
func readc(t string) ([]byte, error) {
ct := C.CString(t)
defer C.free(unsafe.Pointer(ct))
var data *C.char
n := C.clipboard_read(ct, &data)
if data == nil {
return nil, errUnavailable
}
defer C.free(unsafe.Pointer(data))
switch {
case n == 0:
return nil, nil
default:
return C.GoBytes(unsafe.Pointer(data), C.int(n)), nil
}
}
// 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) {
var s string
switch t {
case FmtText:
s = "UTF8_STRING"
case FmtImage:
s = "image/png"
}
start := make(chan int)
done := make(chan struct{}, 1)
go func() { // serve as a daemon until the ownership is terminated.
runtime.LockOSThread()
defer runtime.UnlockOSThread()
cs := C.CString(s)
defer C.free(unsafe.Pointer(cs))
h := cgo.NewHandle(start)
var ok C.int
if len(buf) == 0 {
ok = C.clipboard_write(cs, nil, 0, C.uintptr_t(h))
} else {
ok = C.clipboard_write(cs, (*C.uchar)(unsafe.Pointer(&(buf[0]))), C.size_t(len(buf)), C.uintptr_t(h))
}
if ok != C.int(0) {
fmt.Fprintf(os.Stderr, "write failed with status: %d\n", int(ok))
}
done <- struct{}{}
close(done)
}()
status := <-start
if status < 0 {
return nil, errUnavailable
}
// wait until enter event loop
return done, nil
}
func watch(ctx context.Context, t Format) <-chan []byte {
recv := make(chan []byte, 1)
ti := time.NewTicker(time.Second)
last := Read(t)
go func() {
for {
select {
case <-ctx.Done():
close(recv)
return
case <-ti.C:
b := Read(t)
if b == nil {
continue
}
if !bytes.Equal(last, b) {
recv <- b
last = b
}
}
}
}()
return recv
}
type syncChan struct {
c chan int
}
//export syncStatus
func syncStatus(h uintptr, val int) {
v := cgo.Handle(h).Value().(chan int)
v <- val
cgo.Handle(h).Delete()
}

View File

@@ -0,0 +1,26 @@
//go:build !windows && !cgo
// +build !windows,!cgo
package clipboard
import "context"
func initialize() error {
panic("clipboard: cannot use when CGO_ENABLED=0")
}
func read(t Format) (buf []byte, err error) {
panic("clipboard: cannot use when CGO_ENABLED=0")
}
func readc(t string) ([]byte, error) {
panic("clipboard: cannot use when CGO_ENABLED=0")
}
func write(t Format, buf []byte) (<-chan struct{}, error) {
panic("clipboard: cannot use when CGO_ENABLED=0")
}
func watch(ctx context.Context, t Format) <-chan []byte {
panic("clipboard: cannot use when CGO_ENABLED=0")
}

View File

@@ -0,0 +1,552 @@
// 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
// +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(50 * time.Millisecond)
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")
)