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