os_android.go raw
1 // SPDX-License-Identifier: Unlicense OR MIT
2
3 package wm
4
5 /*
6 #cgo CFLAGS: -Werror
7 #cgo LDFLAGS: -landroid
8
9 #include <android/native_window_jni.h>
10 #include <android/configuration.h>
11 #include <android/keycodes.h>
12 #include <android/input.h>
13 #include <stdlib.h>
14
15 __attribute__ ((visibility ("hidden"))) jint gio_jni_GetEnv(JavaVM *vm, JNIEnv **env, jint version);
16 __attribute__ ((visibility ("hidden"))) jint gio_jni_GetJavaVM(JNIEnv *env, JavaVM **jvm);
17 __attribute__ ((visibility ("hidden"))) jint gio_jni_AttachCurrentThread(JavaVM *vm, JNIEnv **p_env, void *thr_args);
18 __attribute__ ((visibility ("hidden"))) jint gio_jni_DetachCurrentThread(JavaVM *vm);
19
20 __attribute__ ((visibility ("hidden"))) jobject gio_jni_NewGlobalRef(JNIEnv *env, jobject obj);
21 __attribute__ ((visibility ("hidden"))) void gio_jni_DeleteGlobalRef(JNIEnv *env, jobject obj);
22 __attribute__ ((visibility ("hidden"))) jclass gio_jni_GetObjectClass(JNIEnv *env, jobject obj);
23 __attribute__ ((visibility ("hidden"))) jmethodID gio_jni_GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
24 __attribute__ ((visibility ("hidden"))) jmethodID gio_jni_GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
25 __attribute__ ((visibility ("hidden"))) jfloat gio_jni_CallFloatMethod(JNIEnv *env, jobject obj, jmethodID methodID);
26 __attribute__ ((visibility ("hidden"))) jint gio_jni_CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID);
27 __attribute__ ((visibility ("hidden"))) void gio_jni_CallStaticVoidMethodA(JNIEnv *env, jclass cls, jmethodID methodID, const jvalue *args);
28 __attribute__ ((visibility ("hidden"))) void gio_jni_CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args);
29 __attribute__ ((visibility ("hidden"))) jbyte *gio_jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr);
30 __attribute__ ((visibility ("hidden"))) void gio_jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes);
31 __attribute__ ((visibility ("hidden"))) jsize gio_jni_GetArrayLength(JNIEnv *env, jbyteArray arr);
32 __attribute__ ((visibility ("hidden"))) jstring gio_jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len);
33 __attribute__ ((visibility ("hidden"))) jsize gio_jni_GetStringLength(JNIEnv *env, jstring str);
34 __attribute__ ((visibility ("hidden"))) const jchar *gio_jni_GetStringChars(JNIEnv *env, jstring str);
35 __attribute__ ((visibility ("hidden"))) jthrowable gio_jni_ExceptionOccurred(JNIEnv *env);
36 __attribute__ ((visibility ("hidden"))) void gio_jni_ExceptionClear(JNIEnv *env);
37 __attribute__ ((visibility ("hidden"))) jobject gio_jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args);
38 __attribute__ ((visibility ("hidden"))) jobject gio_jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args);
39 */
40 import "C"
41
42 import (
43 "errors"
44 "fmt"
45 "image"
46 "reflect"
47 "runtime"
48 "runtime/debug"
49 "sync"
50 "time"
51 "unicode/utf16"
52 "unsafe"
53
54 "github.com/p9c/p9/pkg/gel/gio/f32"
55 "github.com/p9c/p9/pkg/gel/gio/io/clipboard"
56 "github.com/p9c/p9/pkg/gel/gio/io/key"
57 "github.com/p9c/p9/pkg/gel/gio/io/pointer"
58 "github.com/p9c/p9/pkg/gel/gio/io/system"
59 "github.com/p9c/p9/pkg/gel/gio/unit"
60 )
61
62 type window struct {
63 callbacks Callbacks
64
65 view C.jobject
66
67 dpi int
68 fontScale float32
69 insets system.Insets
70
71 stage system.Stage
72 started bool
73
74 state, newState windowState
75
76 // mu protects the fields following it.
77 mu sync.Mutex
78 win *C.ANativeWindow
79 animating bool
80 }
81
82 // windowState tracks the View or Activity specific state lost when Android
83 // re-creates our Activity.
84 type windowState struct {
85 cursor *pointer.CursorName
86 }
87
88 // gioView hold cached JNI methods for GioView.
89 var gioView struct {
90 once sync.Once
91 getDensity C.jmethodID
92 getFontScale C.jmethodID
93 showTextInput C.jmethodID
94 hideTextInput C.jmethodID
95 postFrameCallback C.jmethodID
96 setCursor C.jmethodID
97 }
98
99 // ViewEvent is sent whenever the Window's underlying Android view
100 // changes.
101 type ViewEvent struct {
102 // View is a JNI global reference to the android.view.View
103 // instance backing the Window. The reference is valid until
104 // the next ViewEvent is received.
105 // A zero View means that there is currently no view attached.
106 View uintptr
107 }
108
109 type jvalue uint64 // The largest JNI type fits in 64 bits.
110
111 var dataDirChan = make(chan string, 1)
112
113 var android struct {
114 // mu protects all fields of this structure. However, once a
115 // non-nil jvm is returned from javaVM, all the other fields may
116 // be accessed unlocked.
117 mu sync.Mutex
118 jvm *C.JavaVM
119
120 // appCtx is the global Android App context.
121 appCtx C.jobject
122 // gioCls is the class of the Gio class.
123 gioCls C.jclass
124
125 mwriteClipboard C.jmethodID
126 mreadClipboard C.jmethodID
127 mwakeupMainThread C.jmethodID
128 }
129
130 // view maps from GioView JNI refenreces to windows.
131 var views = make(map[C.jlong]*window)
132
133 // windows maps from Callbacks to windows
134 var windows = make(map[Callbacks]*window)
135
136 var mainWindow = newWindowRendezvous()
137
138 var mainFuncs = make(chan func(env *C.JNIEnv), 1)
139
140 func getMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
141 m := C.CString(method)
142 defer C.free(unsafe.Pointer(m))
143 s := C.CString(sig)
144 defer C.free(unsafe.Pointer(s))
145 jm := C.gio_jni_GetMethodID(env, class, m, s)
146 if err := exception(env); err != nil {
147 panic(err)
148 }
149 return jm
150 }
151
152 func getStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID {
153 m := C.CString(method)
154 defer C.free(unsafe.Pointer(m))
155 s := C.CString(sig)
156 defer C.free(unsafe.Pointer(s))
157 jm := C.gio_jni_GetStaticMethodID(env, class, m, s)
158 if err := exception(env); err != nil {
159 panic(err)
160 }
161 return jm
162 }
163
164 //export Java_org_gioui_Gio_runGoMain
165 func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.jobject) {
166 initJVM(env, class, context)
167 dirBytes := C.gio_jni_GetByteArrayElements(env, jdataDir)
168 if dirBytes == nil {
169 panic("runGoMain: GetByteArrayElements failed")
170 }
171 n := C.gio_jni_GetArrayLength(env, jdataDir)
172 dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n)
173 dataDirChan <- dataDir
174 C.gio_jni_ReleaseByteArrayElements(env, jdataDir, dirBytes)
175
176 runMain()
177 }
178
179 func initJVM(env *C.JNIEnv, gio C.jclass, ctx C.jobject) {
180 android.mu.Lock()
181 defer android.mu.Unlock()
182 if res := C.gio_jni_GetJavaVM(env, &android.jvm); res != 0 {
183 panic("gio: GetJavaVM failed")
184 }
185 android.appCtx = C.gio_jni_NewGlobalRef(env, ctx)
186 android.gioCls = C.jclass(C.gio_jni_NewGlobalRef(env, C.jobject(gio)))
187 android.mwriteClipboard = getStaticMethodID(env, gio, "writeClipboard", "(Landroid/content/Context;Ljava/lang/String;)V")
188 android.mreadClipboard = getStaticMethodID(env, gio, "readClipboard", "(Landroid/content/Context;)Ljava/lang/String;")
189 android.mwakeupMainThread = getStaticMethodID(env, gio, "wakeupMainThread", "()V")
190 }
191
192 func JavaVM() uintptr {
193 jvm := javaVM()
194 return uintptr(unsafe.Pointer(jvm))
195 }
196
197 func javaVM() *C.JavaVM {
198 android.mu.Lock()
199 defer android.mu.Unlock()
200 return android.jvm
201 }
202
203 func AppContext() uintptr {
204 android.mu.Lock()
205 defer android.mu.Unlock()
206 return uintptr(android.appCtx)
207 }
208
209 func GetDataDir() string {
210 return <-dataDirChan
211 }
212
213 //export Java_org_gioui_GioView_onCreateView
214 func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong {
215 gioView.once.Do(func() {
216 m := &gioView
217 m.getDensity = getMethodID(env, class, "getDensity", "()I")
218 m.getFontScale = getMethodID(env, class, "getFontScale", "()F")
219 m.showTextInput = getMethodID(env, class, "showTextInput", "()V")
220 m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V")
221 m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V")
222 m.setCursor = getMethodID(env, class, "setCursor", "(I)V")
223 })
224 view = C.gio_jni_NewGlobalRef(env, view)
225 wopts := <-mainWindow.out
226 w, ok := windows[wopts.window]
227 if !ok {
228 w = &window{
229 callbacks: wopts.window,
230 }
231 windows[wopts.window] = w
232 }
233 w.callbacks.SetDriver(w)
234 w.view = view
235 handle := C.jlong(view)
236 views[handle] = w
237 w.loadConfig(env, class)
238 applyStateDiff(env, view, windowState{}, w.state)
239 w.setStage(system.StagePaused)
240 w.callbacks.Event(ViewEvent{View: uintptr(view)})
241 return handle
242 }
243
244 //export Java_org_gioui_GioView_onDestroyView
245 func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
246 w := views[handle]
247 w.callbacks.Event(ViewEvent{View: 0})
248 w.callbacks.SetDriver(nil)
249 delete(views, handle)
250 C.gio_jni_DeleteGlobalRef(env, w.view)
251 w.view = 0
252 }
253
254 //export Java_org_gioui_GioView_onStopView
255 func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
256 w := views[handle]
257 w.started = false
258 w.setStage(system.StagePaused)
259 }
260
261 //export Java_org_gioui_GioView_onStartView
262 func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) {
263 w := views[handle]
264 w.started = true
265 if w.aNativeWindow() != nil {
266 w.setVisible()
267 }
268 }
269
270 //export Java_org_gioui_GioView_onSurfaceDestroyed
271 func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) {
272 w := views[handle]
273 w.mu.Lock()
274 w.win = nil
275 w.mu.Unlock()
276 w.setStage(system.StagePaused)
277 }
278
279 //export Java_org_gioui_GioView_onSurfaceChanged
280 func Java_org_gioui_GioView_onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) {
281 w := views[handle]
282 w.mu.Lock()
283 w.win = C.ANativeWindow_fromSurface(env, surf)
284 w.mu.Unlock()
285 if w.started {
286 w.setVisible()
287 }
288 }
289
290 //export Java_org_gioui_GioView_onLowMemory
291 func Java_org_gioui_GioView_onLowMemory() {
292 runtime.GC()
293 debug.FreeOSMemory()
294 }
295
296 //export Java_org_gioui_GioView_onConfigurationChanged
297 func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) {
298 w := views[view]
299 w.loadConfig(env, class)
300 if w.stage >= system.StageRunning {
301 w.draw(true)
302 }
303 }
304
305 //export Java_org_gioui_GioView_onFrameCallback
306 func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong, nanos C.jlong) {
307 w, exist := views[view]
308 if !exist {
309 return
310 }
311 if w.stage < system.StageRunning {
312 return
313 }
314 w.mu.Lock()
315 anim := w.animating
316 w.mu.Unlock()
317 if anim {
318 runInJVM(javaVM(), func(env *C.JNIEnv) {
319 callVoidMethod(env, w.view, gioView.postFrameCallback)
320 })
321 w.draw(false)
322 }
323 }
324
325 //export Java_org_gioui_GioView_onBack
326 func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean {
327 w := views[view]
328 ev := &system.CommandEvent{Type: system.CommandBack}
329 w.callbacks.Event(ev)
330 if ev.Cancel {
331 return C.JNI_TRUE
332 }
333 return C.JNI_FALSE
334 }
335
336 //export Java_org_gioui_GioView_onFocusChange
337 func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) {
338 w := views[view]
339 w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE})
340 }
341
342 //export Java_org_gioui_GioView_onWindowInsets
343 func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) {
344 w := views[view]
345 w.insets = system.Insets{
346 Top: unit.Px(float32(top)),
347 Right: unit.Px(float32(right)),
348 Bottom: unit.Px(float32(bottom)),
349 Left: unit.Px(float32(left)),
350 }
351 if w.stage >= system.StageRunning {
352 w.draw(true)
353 }
354 }
355
356 func (w *window) setVisible() {
357 win := w.aNativeWindow()
358 width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
359 if width == 0 || height == 0 {
360 return
361 }
362 w.setStage(system.StageRunning)
363 w.draw(true)
364 }
365
366 func (w *window) setStage(stage system.Stage) {
367 if stage == w.stage {
368 return
369 }
370 w.stage = stage
371 w.callbacks.Event(system.StageEvent{stage})
372 }
373
374 func (w *window) nativeWindow(visID int) (*C.ANativeWindow, int, int) {
375 win := w.aNativeWindow()
376 var width, height int
377 if win != nil {
378 if C.ANativeWindow_setBuffersGeometry(win, 0, 0, C.int32_t(visID)) != 0 {
379 panic(errors.New("ANativeWindow_setBuffersGeometry failed"))
380 }
381 w, h := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
382 width, height = int(w), int(h)
383 }
384 return win, width, height
385 }
386
387 func (w *window) aNativeWindow() *C.ANativeWindow {
388 w.mu.Lock()
389 defer w.mu.Unlock()
390 return w.win
391 }
392
393 func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) {
394 dpi := int(C.gio_jni_CallIntMethod(env, w.view, gioView.getDensity))
395 w.fontScale = float32(C.gio_jni_CallFloatMethod(env, w.view, gioView.getFontScale))
396 switch dpi {
397 case C.ACONFIGURATION_DENSITY_NONE,
398 C.ACONFIGURATION_DENSITY_DEFAULT,
399 C.ACONFIGURATION_DENSITY_ANY:
400 // Assume standard density.
401 w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM
402 default:
403 w.dpi = int(dpi)
404 }
405 }
406
407 func (w *window) SetAnimating(anim bool) {
408 w.mu.Lock()
409 w.animating = anim
410 w.mu.Unlock()
411 if anim {
412 runOnMain(func(env *C.JNIEnv) {
413 if w.view == 0 {
414 // View was destroyed while switching to main thread.
415 return
416 }
417 callVoidMethod(env, w.view, gioView.postFrameCallback)
418 })
419 }
420 }
421
422 func (w *window) draw(sync bool) {
423 win := w.aNativeWindow()
424 width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win)
425 if width == 0 || height == 0 {
426 return
427 }
428 const inchPrDp = 1.0 / 160
429 ppdp := float32(w.dpi) * inchPrDp
430 w.callbacks.Event(FrameEvent{
431 FrameEvent: system.FrameEvent{
432 Now: time.Now(),
433 Size: image.Point{
434 X: int(width),
435 Y: int(height),
436 },
437 Insets: w.insets,
438 Metric: unit.Metric{
439 PxPerDp: ppdp,
440 PxPerSp: w.fontScale * ppdp,
441 },
442 },
443 Sync: sync,
444 })
445 }
446
447 type keyMapper func(devId, keyCode C.int32_t) rune
448
449 func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) {
450 if jvm == nil {
451 panic("nil JVM")
452 }
453 runtime.LockOSThread()
454 defer runtime.UnlockOSThread()
455 var env *C.JNIEnv
456 if res := C.gio_jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK {
457 if res != C.JNI_EDETACHED {
458 panic(fmt.Errorf("JNI GetEnv failed with error %d", res))
459 }
460 if C.gio_jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK {
461 panic(errors.New("runInJVM: AttachCurrentThread failed"))
462 }
463 defer C.gio_jni_DetachCurrentThread(jvm)
464 }
465
466 f(env)
467 }
468
469 func convertKeyCode(code C.jint) (string, bool) {
470 var n string
471 switch code {
472 case C.AKEYCODE_DPAD_UP:
473 n = key.NameUpArrow
474 case C.AKEYCODE_DPAD_DOWN:
475 n = key.NameDownArrow
476 case C.AKEYCODE_DPAD_LEFT:
477 n = key.NameLeftArrow
478 case C.AKEYCODE_DPAD_RIGHT:
479 n = key.NameRightArrow
480 case C.AKEYCODE_FORWARD_DEL:
481 n = key.NameDeleteForward
482 case C.AKEYCODE_DEL:
483 n = key.NameDeleteBackward
484 case C.AKEYCODE_NUMPAD_ENTER:
485 n = key.NameEnter
486 case C.AKEYCODE_ENTER:
487 n = key.NameEnter
488 default:
489 return "", false
490 }
491 return n, true
492 }
493
494 //export Java_org_gioui_GioView_onKeyEvent
495 func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, t C.jlong) {
496 w := views[handle]
497 if n, ok := convertKeyCode(keyCode); ok {
498 w.callbacks.Event(key.Event{Name: n})
499 }
500 if r != 0 {
501 w.callbacks.Event(key.EditEvent{Text: string(rune(r))})
502 }
503 }
504
505 //export Java_org_gioui_GioView_onTouchEvent
506 func Java_org_gioui_GioView_onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y, scrollX, scrollY C.jfloat, jbtns C.jint, t C.jlong) {
507 w := views[handle]
508 var typ pointer.Type
509 switch action {
510 case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN:
511 typ = pointer.Press
512 case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP:
513 typ = pointer.Release
514 case C.AMOTION_EVENT_ACTION_CANCEL:
515 typ = pointer.Cancel
516 case C.AMOTION_EVENT_ACTION_MOVE:
517 typ = pointer.Move
518 case C.AMOTION_EVENT_ACTION_SCROLL:
519 typ = pointer.Scroll
520 default:
521 return
522 }
523 var src pointer.Source
524 var btns pointer.Buttons
525 if jbtns&C.AMOTION_EVENT_BUTTON_PRIMARY != 0 {
526 btns |= pointer.ButtonPrimary
527 }
528 if jbtns&C.AMOTION_EVENT_BUTTON_SECONDARY != 0 {
529 btns |= pointer.ButtonSecondary
530 }
531 if jbtns&C.AMOTION_EVENT_BUTTON_TERTIARY != 0 {
532 btns |= pointer.ButtonTertiary
533 }
534 switch tool {
535 case C.AMOTION_EVENT_TOOL_TYPE_FINGER:
536 src = pointer.Touch
537 case C.AMOTION_EVENT_TOOL_TYPE_MOUSE:
538 src = pointer.Mouse
539 case C.AMOTION_EVENT_TOOL_TYPE_UNKNOWN:
540 // For example, triggered via 'adb shell input tap'.
541 // Instead of discarding it, treat it as a touch event.
542 src = pointer.Touch
543 default:
544 return
545 }
546 w.callbacks.Event(pointer.Event{
547 Type: typ,
548 Source: src,
549 Buttons: btns,
550 PointerID: pointer.ID(pointerID),
551 Time: time.Duration(t) * time.Millisecond,
552 Position: f32.Point{X: float32(x), Y: float32(y)},
553 Scroll: f32.Pt(float32(scrollX), float32(scrollY)),
554 })
555 }
556
557 func (w *window) ShowTextInput(show bool) {
558 runOnMain(func(env *C.JNIEnv) {
559 if w.view == 0 {
560 return
561 }
562 if show {
563 callVoidMethod(env, w.view, gioView.showTextInput)
564 } else {
565 callVoidMethod(env, w.view, gioView.hideTextInput)
566 }
567 })
568 }
569
570 func javaString(env *C.JNIEnv, str string) C.jstring {
571 if str == "" {
572 return 0
573 }
574 utf16Chars := utf16.Encode([]rune(str))
575 return C.gio_jni_NewString(env, (*C.jchar)(unsafe.Pointer(&utf16Chars[0])), C.int(len(utf16Chars)))
576 }
577
578 func varArgs(args []jvalue) *C.jvalue {
579 if len(args) == 0 {
580 return nil
581 }
582 return (*C.jvalue)(unsafe.Pointer(&args[0]))
583 }
584
585 func callStaticVoidMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) error {
586 C.gio_jni_CallStaticVoidMethodA(env, cls, method, varArgs(args))
587 return exception(env)
588 }
589
590 func callStaticObjectMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) {
591 res := C.gio_jni_CallStaticObjectMethodA(env, cls, method, varArgs(args))
592 return res, exception(env)
593 }
594
595 func callVoidMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) error {
596 C.gio_jni_CallVoidMethodA(env, obj, method, varArgs(args))
597 return exception(env)
598 }
599
600 func callObjectMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (C.jobject, error) {
601 res := C.gio_jni_CallObjectMethodA(env, obj, method, varArgs(args))
602 return res, exception(env)
603 }
604
605 // exception returns an error corresponding to the pending
606 // exception, or nil if no exception is pending. The pending
607 // exception is cleared.
608 func exception(env *C.JNIEnv) error {
609 thr := C.gio_jni_ExceptionOccurred(env)
610 if thr == 0 {
611 return nil
612 }
613 C.gio_jni_ExceptionClear(env)
614 cls := getObjectClass(env, C.jobject(thr))
615 toString := getMethodID(env, cls, "toString", "()Ljava/lang/String;")
616 msg, err := callObjectMethod(env, C.jobject(thr), toString)
617 if err != nil {
618 return err
619 }
620 return errors.New(goString(env, C.jstring(msg)))
621 }
622
623 func getObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass {
624 if obj == 0 {
625 panic("null object")
626 }
627 cls := C.gio_jni_GetObjectClass(env, C.jobject(obj))
628 if err := exception(env); err != nil {
629 // GetObjectClass should never fail.
630 panic(err)
631 }
632 return cls
633 }
634
635 // goString converts the JVM jstring to a Go string.
636 func goString(env *C.JNIEnv, str C.jstring) string {
637 if str == 0 {
638 return ""
639 }
640 strlen := C.gio_jni_GetStringLength(env, C.jstring(str))
641 chars := C.gio_jni_GetStringChars(env, C.jstring(str))
642 var utf16Chars []uint16
643 hdr := (*reflect.SliceHeader)(unsafe.Pointer(&utf16Chars))
644 hdr.Data = uintptr(unsafe.Pointer(chars))
645 hdr.Cap = int(strlen)
646 hdr.Len = int(strlen)
647 utf8 := utf16.Decode(utf16Chars)
648 return string(utf8)
649 }
650
651 func Main() {
652 }
653
654 func NewWindow(window Callbacks, opts *Options) error {
655 mainWindow.in <- windowAndOptions{window, opts}
656 return <-mainWindow.errs
657 }
658
659 func (w *window) WriteClipboard(s string) {
660 runOnMain(func(env *C.JNIEnv) {
661 jstr := javaString(env, s)
662 callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard,
663 jvalue(android.appCtx), jvalue(jstr))
664 })
665 }
666
667 func (w *window) ReadClipboard() {
668 runOnMain(func(env *C.JNIEnv) {
669 c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard,
670 jvalue(android.appCtx))
671 if err != nil {
672 return
673 }
674 content := goString(env, C.jstring(c))
675 w.callbacks.Event(clipboard.Event{Text: content})
676 })
677 }
678
679 func (w *window) Option(opts *Options) {}
680
681 func (w *window) SetCursor(name pointer.CursorName) {
682 w.setState(func(state *windowState) {
683 state.cursor = &name
684 })
685 }
686
687 // setState adjust the window state on the main thread.
688 func (w *window) setState(f func(state *windowState)) {
689 runOnMain(func(env *C.JNIEnv) {
690 f(&w.newState)
691 if w.view == 0 {
692 // No View attached. The state will be applied at next onCreateView.
693 return
694 }
695 old := w.state
696 state := w.newState
697 applyStateDiff(env, w.view, old, state)
698 w.state = state
699 })
700 }
701
702 func applyStateDiff(env *C.JNIEnv, view C.jobject, old, state windowState) {
703 if state.cursor != nil && old.cursor != state.cursor {
704 setCursor(env, view, *state.cursor)
705 }
706 }
707
708 func setCursor(env *C.JNIEnv, view C.jobject, name pointer.CursorName) {
709 var curID int
710 switch name {
711 default:
712 fallthrough
713 case pointer.CursorDefault:
714 curID = 1000 // TYPE_ARROW
715 case pointer.CursorText:
716 curID = 1008 // TYPE_TEXT
717 case pointer.CursorPointer:
718 curID = 1002 // TYPE_HAND
719 case pointer.CursorCrossHair:
720 curID = 1007 // TYPE_CROSSHAIR
721 case pointer.CursorColResize:
722 curID = 1014 // TYPE_HORIZONTAL_DOUBLE_ARROW
723 case pointer.CursorRowResize:
724 curID = 1015 // TYPE_VERTICAL_DOUBLE_ARROW
725 case pointer.CursorNone:
726 curID = 0 // TYPE_NULL
727 }
728 callVoidMethod(env, view, gioView.setCursor, jvalue(curID))
729 }
730
731 // Close the window. Not implemented for Android.
732 func (w *window) Close() {}
733
734 // runOnMain runs a function on the Java main thread.
735 func runOnMain(f func(env *C.JNIEnv)) {
736 go func() {
737 mainFuncs <- f
738 runInJVM(javaVM(), func(env *C.JNIEnv) {
739 callStaticVoidMethod(env, android.gioCls, android.mwakeupMainThread)
740 })
741 }()
742 }
743
744 //export Java_org_gioui_Gio_scheduleMainFuncs
745 func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) {
746 for {
747 select {
748 case f := <-mainFuncs:
749 f(env)
750 default:
751 return
752 }
753 }
754 }
755
756 func (_ ViewEvent) ImplementsEvent() {}
757