Skip to content

Commit c3412a1

Browse files
authored
all: add mobile (android/ios) supports (#9)
1 parent 0f134f4 commit c3412a1

File tree

149 files changed

+49436
-12
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

149 files changed

+49436
-12
lines changed

.github/workflows/clipboard.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
if: ${{ runner.os == 'Linux' }}
2727
run: |
2828
sudo apt update
29-
sudo apt install -y xvfb libx11-dev
29+
sudo apt install -y xvfb libx11-dev libegl1-mesa-dev libgles2-mesa-dev
3030
Xvfb :0 -screen 0 1024x768x24 > /dev/null 2>&1 &
3131
# Wait for Xvfb
3232
MAX_ATTEMPTS=120 # About 60 seconds

README.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
# 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)
22

3-
cross platform clipboard package in Go
3+
Cross platform clipboard package in Go
44

55
```go
66
import "golang.design/x/clipboard"
77
```
88

99
## Features
1010

11-
- Cross platform supports: macOS, Linux (X11), and Windows
11+
- Cross platform supports: **macOS, Linux (X11), Windows, iOS, and Android**
1212
- Copy/paste UTF-8 text
13-
- Copy/paste PNG encoded images
13+
- Copy/paste PNG encoded images (Desktop-only)
1414
- Command `gclip` as a demo application
15+
- Mobile app `Gclip` as a demo application
1516

1617
## API Usage
1718

@@ -109,9 +110,10 @@ accessing system clipboards, but here are a few details you might need to know.
109110

110111
### Dependency
111112

112-
- macOS users: require Cgo, no dependency
113-
- Linux users: require X11 dev package. For instance, install `libx11-dev` or `xorg-dev` or `libX11-devel` to access X window system.
114-
- Windows users: no Cgo, no dependency
113+
- macOS: require Cgo, no dependency
114+
- Linux: require X11 dev package. For instance, install `libx11-dev` or `xorg-dev` or `libX11-devel` to access X window system.
115+
- Windows: no Cgo, no dependency
116+
- iOS/Android: collaborate with [`gomobile`](https://golang.org/x/mobile)
115117

116118
### Screenshot
117119

clipboard_android.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Copyright 2021 The golang.design Initiative Authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a MIT license that can be found in the LICENSE file.
4+
//
5+
// Written by Changkun Ou <changkun.de>
6+
7+
//go:build !linux && android
8+
9+
#include <android/log.h>
10+
#include <jni.h>
11+
#include <stdlib.h>
12+
#include <string.h>
13+
14+
#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, \
15+
"GOLANG.DESIGN/X/CLIPBOARD", __VA_ARGS__)
16+
17+
static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
18+
jmethodID m = (*env)->GetMethodID(env, clazz, name, sig);
19+
if (m == 0) {
20+
(*env)->ExceptionClear(env);
21+
LOG_FATAL("cannot find method %s %s", name, sig);
22+
return 0;
23+
}
24+
return m;
25+
}
26+
27+
jobject get_clipboard(uintptr_t jni_env, uintptr_t ctx) {
28+
JNIEnv *env = (JNIEnv*)jni_env;
29+
jclass ctxClass = (*env)->GetObjectClass(env, (jobject)ctx);
30+
jmethodID getSystemService = find_method(env, ctxClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
31+
32+
jstring service = (*env)->NewStringUTF(env, "clipboard");
33+
jobject ret = (jobject)(*env)->CallObjectMethod(env, (jobject)ctx, getSystemService, service);
34+
jthrowable err = (*env)->ExceptionOccurred(env);
35+
36+
if (err != NULL) {
37+
LOG_FATAL("cannot find clipboard");
38+
(*env)->ExceptionClear(env);
39+
return NULL;
40+
}
41+
return ret;
42+
}
43+
44+
char *clipboard_read_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) {
45+
JNIEnv *env = (JNIEnv*)jni_env;
46+
jobject mgr = get_clipboard(jni_env, ctx);
47+
if (mgr == NULL) {
48+
return NULL;
49+
}
50+
51+
jclass mgrClass = (*env)->GetObjectClass(env, mgr);
52+
jmethodID getText = find_method(env, mgrClass, "getText", "()Ljava/lang/CharSequence;");
53+
54+
jobject content = (jstring)(*env)->CallObjectMethod(env, mgr, getText);
55+
if (content == NULL) {
56+
return NULL;
57+
}
58+
59+
jclass clzCharSequence = (*env)->GetObjectClass(env, content);
60+
jmethodID toString = (*env)->GetMethodID(env, clzCharSequence, "toString", "()Ljava/lang/String;");
61+
jobject s = (*env)->CallObjectMethod(env, content, toString);
62+
63+
const char *chars = (*env)->GetStringUTFChars(env, s, NULL);
64+
char *copy = strdup(chars);
65+
(*env)->ReleaseStringUTFChars(env, s, chars);
66+
return copy;
67+
}
68+
69+
void clipboard_write_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *str) {
70+
JNIEnv *env = (JNIEnv*)jni_env;
71+
jobject mgr = get_clipboard(jni_env, ctx);
72+
if (mgr == NULL) {
73+
return;
74+
}
75+
76+
jclass mgrClass = (*env)->GetObjectClass(env, mgr);
77+
jmethodID setText = find_method(env, mgrClass, "setText", "(Ljava/lang/CharSequence;)V");
78+
79+
(*env)->CallVoidMethod(env, mgr, setText, (*env)->NewStringUTF(env, str));
80+
}

clipboard_android.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2021 The golang.design Initiative Authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a MIT license that can be found in the LICENSE file.
4+
//
5+
// Written by Changkun Ou <changkun.de>
6+
7+
//go:build !linux && android
8+
9+
package clipboard
10+
11+
/*
12+
#cgo LDFLAGS: -landroid -llog
13+
14+
#include <stdlib.h>
15+
char *clipboard_read_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx);
16+
void clipboard_write_string(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *str);
17+
18+
*/
19+
import "C"
20+
import (
21+
"bytes"
22+
"context"
23+
"errors"
24+
"time"
25+
"unsafe"
26+
27+
"golang.org/x/mobile/app"
28+
)
29+
30+
func read(t Format) (buf []byte, err error) {
31+
switch t {
32+
case FmtText:
33+
s := ""
34+
app.RunOnJVM(func(vm, env, ctx uintptr) error {
35+
cs := C.clipboard_read_string(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx))
36+
if cs == nil {
37+
return nil
38+
}
39+
40+
s = C.GoString(cs)
41+
C.free(unsafe.Pointer(cs))
42+
return nil
43+
})
44+
return []byte(s), nil
45+
case FmtImage:
46+
return nil, errors.New("unsupported")
47+
default:
48+
return nil, errors.New("unsupported")
49+
}
50+
}
51+
52+
// write writes the given data to clipboard and
53+
// returns true if success or false if failed.
54+
func write(t Format, buf []byte) (<-chan struct{}, error) {
55+
done := make(chan struct{}, 1)
56+
switch t {
57+
case FmtText:
58+
cs := C.CString(string(buf))
59+
defer C.free(unsafe.Pointer(cs))
60+
61+
app.RunOnJVM(func(vm, env, ctx uintptr) error {
62+
C.clipboard_write_string(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx), cs)
63+
done <- struct{}{}
64+
return nil
65+
})
66+
return done, nil
67+
case FmtImage:
68+
return nil, errors.New("unsupported")
69+
default:
70+
return nil, errors.New("unsupported")
71+
}
72+
}
73+
74+
func watch(ctx context.Context, t Format) <-chan []byte {
75+
recv := make(chan []byte, 1)
76+
ti := time.NewTicker(time.Second)
77+
last := Read(t)
78+
go func() {
79+
for {
80+
select {
81+
case <-ctx.Done():
82+
close(recv)
83+
return
84+
case <-ti.C:
85+
b := Read(t)
86+
if b == nil {
87+
continue
88+
}
89+
if bytes.Compare(last, b) != 0 {
90+
recv <- b
91+
last = b
92+
}
93+
}
94+
}
95+
}()
96+
return recv
97+
}

clipboard_darwin.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
//
55
// Written by Changkun Ou <changkun.de>
66

7-
//go:build darwin
8-
// +build darwin
7+
//go:build darwin && !ios
98

109
package clipboard
1110

clipboard_darwin.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
//
55
// Written by Changkun Ou <changkun.de>
66

7+
//go:build darwin && !ios
8+
79
// Interact with NSPasteboard using Objective-C
810
// https://developer.apple.com/documentation/appkit/nspasteboard?language=objc
911

clipboard_ios.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2021 The golang.design Initiative Authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a MIT license that can be found in the LICENSE file.
4+
//
5+
// Written by Changkun Ou <changkun.de>
6+
7+
//go:build ios
8+
9+
package clipboard
10+
11+
/*
12+
#cgo CFLAGS: -x objective-c
13+
#cgo LDFLAGS: -framework Foundation -framework UIKit -framework MobileCoreServices
14+
15+
#import <stdlib.h>
16+
void clipboard_write_string(char *s);
17+
char *clipboard_read_string();
18+
*/
19+
import "C"
20+
import (
21+
"bytes"
22+
"context"
23+
"errors"
24+
"time"
25+
"unsafe"
26+
)
27+
28+
func read(t Format) (buf []byte, err error) {
29+
switch t {
30+
case FmtText:
31+
return []byte(C.GoString(C.clipboard_read_string())), nil
32+
case FmtImage:
33+
return nil, errors.New("unimplemented")
34+
default:
35+
return nil, errors.New("unimplemented")
36+
}
37+
}
38+
39+
// SetContent sets the clipboard content for iOS
40+
func write(t Format, buf []byte) (<-chan struct{}, error) {
41+
done := make(chan struct{}, 1)
42+
switch t {
43+
case FmtText:
44+
cs := C.CString(string(buf))
45+
defer C.free(unsafe.Pointer(cs))
46+
47+
C.clipboard_write_string(cs)
48+
return done, nil
49+
case FmtImage:
50+
return nil, errors.New("unimplemented")
51+
default:
52+
return nil, errors.New("unimplemented")
53+
}
54+
}
55+
56+
func watch(ctx context.Context, t Format) <-chan []byte {
57+
recv := make(chan []byte, 1)
58+
ti := time.NewTicker(time.Second)
59+
last := Read(t)
60+
go func() {
61+
for {
62+
select {
63+
case <-ctx.Done():
64+
close(recv)
65+
return
66+
case <-ti.C:
67+
b := Read(t)
68+
if b == nil {
69+
continue
70+
}
71+
if bytes.Compare(last, b) != 0 {
72+
recv <- b
73+
last = b
74+
}
75+
}
76+
}
77+
}()
78+
return recv
79+
}

clipboard_ios.m

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2021 The golang.design Initiative Authors.
2+
// All rights reserved. Use of this source code is governed
3+
// by a MIT license that can be found in the LICENSE file.
4+
//
5+
// Written by Changkun Ou <changkun.de>
6+
7+
//go:build ios
8+
9+
#import <UIKit/UIKit.h>
10+
#import <MobileCoreServices/MobileCoreServices.h>
11+
12+
void clipboard_write_string(char *s) {
13+
NSString *value = [NSString stringWithUTF8String:s];
14+
[[UIPasteboard generalPasteboard] setString:value];
15+
}
16+
17+
char *clipboard_read_string() {
18+
NSString *str = [[UIPasteboard generalPasteboard] string];
19+
return (char *)[str UTF8String];
20+
}

clipboard_linux.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
//
55
// Written by Changkun Ou <changkun.de>
66

7+
//go:build linux && !android
8+
79
#include <stdlib.h>
810
#include <stdio.h>
911
#include <stdint.h>

clipboard_linux.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@
44
//
55
// Written by Changkun Ou <changkun.de>
66

7-
// +build linux
8-
//go:build linux
7+
//go:build linux && !android
98

109
package clipboard
1110

0 commit comments

Comments
 (0)