github.com/Seikaijyu/gio@v0.0.1/app/os_android.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package app 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 jboolean jni_CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID methodID, const jvalue *args) { 68 return (*env)->CallBooleanMethodA(env, obj, methodID, args); 69 } 70 71 static jbyte *jni_GetByteArrayElements(JNIEnv *env, jbyteArray arr) { 72 return (*env)->GetByteArrayElements(env, arr, NULL); 73 } 74 75 static void jni_ReleaseByteArrayElements(JNIEnv *env, jbyteArray arr, jbyte *bytes) { 76 (*env)->ReleaseByteArrayElements(env, arr, bytes, JNI_ABORT); 77 } 78 79 static jsize jni_GetArrayLength(JNIEnv *env, jbyteArray arr) { 80 return (*env)->GetArrayLength(env, arr); 81 } 82 83 static jstring jni_NewString(JNIEnv *env, const jchar *unicodeChars, jsize len) { 84 return (*env)->NewString(env, unicodeChars, len); 85 } 86 87 static jsize jni_GetStringLength(JNIEnv *env, jstring str) { 88 return (*env)->GetStringLength(env, str); 89 } 90 91 static const jchar *jni_GetStringChars(JNIEnv *env, jstring str) { 92 return (*env)->GetStringChars(env, str, NULL); 93 } 94 95 static jthrowable jni_ExceptionOccurred(JNIEnv *env) { 96 return (*env)->ExceptionOccurred(env); 97 } 98 99 static void jni_ExceptionClear(JNIEnv *env) { 100 (*env)->ExceptionClear(env); 101 } 102 103 static jobject jni_CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID method, jvalue *args) { 104 return (*env)->CallObjectMethodA(env, obj, method, args); 105 } 106 107 static jobject jni_CallStaticObjectMethodA(JNIEnv *env, jclass cls, jmethodID method, jvalue *args) { 108 return (*env)->CallStaticObjectMethodA(env, cls, method, args); 109 } 110 111 static jclass jni_FindClass(JNIEnv *env, char *name) { 112 return (*env)->FindClass(env, name); 113 } 114 115 static jobject jni_NewObjectA(JNIEnv *env, jclass cls, jmethodID cons, jvalue *args) { 116 return (*env)->NewObjectA(env, cls, cons, args); 117 } 118 */ 119 import "C" 120 121 import ( 122 "errors" 123 "fmt" 124 "image" 125 "image/color" 126 "math" 127 "os" 128 "path/filepath" 129 "runtime" 130 "runtime/cgo" 131 "runtime/debug" 132 "sync" 133 "time" 134 "unicode/utf16" 135 "unsafe" 136 137 "github.com/Seikaijyu/gio/internal/f32color" 138 139 "github.com/Seikaijyu/gio/f32" 140 "github.com/Seikaijyu/gio/io/clipboard" 141 "github.com/Seikaijyu/gio/io/key" 142 "github.com/Seikaijyu/gio/io/pointer" 143 "github.com/Seikaijyu/gio/io/router" 144 "github.com/Seikaijyu/gio/io/semantic" 145 "github.com/Seikaijyu/gio/io/system" 146 "github.com/Seikaijyu/gio/unit" 147 ) 148 149 type window struct { 150 callbacks *callbacks 151 152 view C.jobject 153 handle cgo.Handle 154 155 dpi int 156 fontScale float32 157 insets pixelInsets 158 159 stage system.Stage 160 started bool 161 animating bool 162 163 win *C.ANativeWindow 164 config Config 165 166 semantic struct { 167 hoverID router.SemanticID 168 rootID router.SemanticID 169 focusID router.SemanticID 170 diffs []router.SemanticID 171 } 172 } 173 174 // gioView hold cached JNI methods for GioView. 175 var gioView struct { 176 once sync.Once 177 getDensity C.jmethodID 178 getFontScale C.jmethodID 179 showTextInput C.jmethodID 180 hideTextInput C.jmethodID 181 setInputHint C.jmethodID 182 postFrameCallback C.jmethodID 183 invalidate C.jmethodID // requests draw, called from UI thread 184 setCursor C.jmethodID 185 setOrientation C.jmethodID 186 setNavigationColor C.jmethodID 187 setStatusColor C.jmethodID 188 setFullscreen C.jmethodID 189 unregister C.jmethodID 190 sendA11yEvent C.jmethodID 191 sendA11yChange C.jmethodID 192 isA11yActive C.jmethodID 193 restartInput C.jmethodID 194 updateSelection C.jmethodID 195 updateCaret C.jmethodID 196 } 197 198 type pixelInsets struct { 199 top, bottom, left, right int 200 } 201 202 // ViewEvent is sent whenever the Window's underlying Android view 203 // changes. 204 type ViewEvent struct { 205 // View is a JNI global reference to the android.view.View 206 // instance backing the Window. The reference is valid until 207 // the next ViewEvent is received. 208 // A zero View means that there is currently no view attached. 209 View uintptr 210 } 211 212 type jvalue uint64 // The largest JNI type fits in 64 bits. 213 214 var dataDirChan = make(chan string, 1) 215 216 var android struct { 217 // mu protects all fields of this structure. However, once a 218 // non-nil jvm is returned from javaVM, all the other fields may 219 // be accessed unlocked. 220 mu sync.Mutex 221 jvm *C.JavaVM 222 223 // appCtx is the global Android App context. 224 appCtx C.jobject 225 // gioCls is the class of the Gio class. 226 gioCls C.jclass 227 228 mwriteClipboard C.jmethodID 229 mreadClipboard C.jmethodID 230 mwakeupMainThread C.jmethodID 231 232 // android.view.accessibility.AccessibilityNodeInfo class. 233 accessibilityNodeInfo struct { 234 cls C.jclass 235 // addChild(View, int) 236 addChild C.jmethodID 237 // setBoundsInScreen(Rect) 238 setBoundsInScreen C.jmethodID 239 // setText(CharSequence) 240 setText C.jmethodID 241 // setContentDescription(CharSequence) 242 setContentDescription C.jmethodID 243 // setParent(View, int) 244 setParent C.jmethodID 245 // addAction(int) 246 addAction C.jmethodID 247 // setClassName(CharSequence) 248 setClassName C.jmethodID 249 // setCheckable(boolean) 250 setCheckable C.jmethodID 251 // setSelected(boolean) 252 setSelected C.jmethodID 253 // setChecked(boolean) 254 setChecked C.jmethodID 255 // setEnabled(boolean) 256 setEnabled C.jmethodID 257 // setAccessibilityFocused(boolean) 258 setAccessibilityFocused C.jmethodID 259 } 260 261 // android.graphics.Rect class. 262 rect struct { 263 cls C.jclass 264 // (int, int, int, int) constructor. 265 cons C.jmethodID 266 } 267 268 strings struct { 269 // "android.view.View" 270 androidViewView C.jstring 271 // "android.widget.Button" 272 androidWidgetButton C.jstring 273 // "android.widget.CheckBox" 274 androidWidgetCheckBox C.jstring 275 // "android.widget.EditText" 276 androidWidgetEditText C.jstring 277 // "android.widget.RadioButton" 278 androidWidgetRadioButton C.jstring 279 // "android.widget.Switch" 280 androidWidgetSwitch C.jstring 281 } 282 } 283 284 var windows = make(map[*callbacks]*window) 285 286 var mainWindow = newWindowRendezvous() 287 288 var mainFuncs = make(chan func(env *C.JNIEnv), 1) 289 290 var ( 291 dataDirOnce sync.Once 292 dataPath string 293 ) 294 295 var ( 296 newAndroidVulkanContext func(w *window) (context, error) 297 newAndroidGLESContext func(w *window) (context, error) 298 ) 299 300 // AccessibilityNodeProvider.HOST_VIEW_ID. 301 const HOST_VIEW_ID = -1 302 303 const ( 304 // AccessibilityEvent constants. 305 TYPE_VIEW_HOVER_ENTER = 128 306 TYPE_VIEW_HOVER_EXIT = 256 307 ) 308 309 const ( 310 // AccessibilityNodeInfo constants. 311 ACTION_ACCESSIBILITY_FOCUS = 64 312 ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128 313 ACTION_CLICK = 16 314 ) 315 316 func (w *window) NewContext() (context, error) { 317 funcs := []func(w *window) (context, error){newAndroidGLESContext, newAndroidVulkanContext} 318 var firstErr error 319 for _, f := range funcs { 320 if f == nil { 321 continue 322 } 323 c, err := f(w) 324 if err != nil { 325 if firstErr == nil { 326 firstErr = err 327 } 328 continue 329 } 330 return c, nil 331 } 332 if firstErr != nil { 333 return nil, firstErr 334 } 335 return nil, errors.New("x11: no available GPU backends") 336 } 337 338 func dataDir() (string, error) { 339 dataDirOnce.Do(func() { 340 dataPath = <-dataDirChan 341 }) 342 return dataPath, nil 343 } 344 345 func getMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID { 346 m := C.CString(method) 347 defer C.free(unsafe.Pointer(m)) 348 s := C.CString(sig) 349 defer C.free(unsafe.Pointer(s)) 350 jm := C.jni_GetMethodID(env, class, m, s) 351 if err := exception(env); err != nil { 352 panic(err) 353 } 354 return jm 355 } 356 357 func getStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID { 358 m := C.CString(method) 359 defer C.free(unsafe.Pointer(m)) 360 s := C.CString(sig) 361 defer C.free(unsafe.Pointer(s)) 362 jm := C.jni_GetStaticMethodID(env, class, m, s) 363 if err := exception(env); err != nil { 364 panic(err) 365 } 366 return jm 367 } 368 369 //export Java_org_gioui_Gio_runGoMain 370 func Java_org_gioui_Gio_runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray, context C.jobject) { 371 initJVM(env, class, context) 372 dirBytes := C.jni_GetByteArrayElements(env, jdataDir) 373 if dirBytes == nil { 374 panic("runGoMain: GetByteArrayElements failed") 375 } 376 n := C.jni_GetArrayLength(env, jdataDir) 377 dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n) 378 379 // Set XDG_CACHE_HOME to make os.UserCacheDir work. 380 if _, exists := os.LookupEnv("XDG_CACHE_HOME"); !exists { 381 cachePath := filepath.Join(dataDir, "cache") 382 os.Setenv("XDG_CACHE_HOME", cachePath) 383 } 384 // Set XDG_CONFIG_HOME to make os.UserConfigDir work. 385 if _, exists := os.LookupEnv("XDG_CONFIG_HOME"); !exists { 386 cfgPath := filepath.Join(dataDir, "config") 387 os.Setenv("XDG_CONFIG_HOME", cfgPath) 388 } 389 // Set HOME to make os.UserHomeDir work. 390 if _, exists := os.LookupEnv("HOME"); !exists { 391 os.Setenv("HOME", dataDir) 392 } 393 394 dataDirChan <- dataDir 395 C.jni_ReleaseByteArrayElements(env, jdataDir, dirBytes) 396 397 runMain() 398 } 399 400 func initJVM(env *C.JNIEnv, gio C.jclass, ctx C.jobject) { 401 android.mu.Lock() 402 defer android.mu.Unlock() 403 if res := C.jni_GetJavaVM(env, &android.jvm); res != 0 { 404 panic("gio: GetJavaVM failed") 405 } 406 android.appCtx = C.jni_NewGlobalRef(env, ctx) 407 android.gioCls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(gio))) 408 409 cls := findClass(env, "android/view/accessibility/AccessibilityNodeInfo") 410 android.accessibilityNodeInfo.cls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(cls))) 411 android.accessibilityNodeInfo.addChild = getMethodID(env, cls, "addChild", "(Landroid/view/View;I)V") 412 android.accessibilityNodeInfo.setBoundsInScreen = getMethodID(env, cls, "setBoundsInScreen", "(Landroid/graphics/Rect;)V") 413 android.accessibilityNodeInfo.setText = getMethodID(env, cls, "setText", "(Ljava/lang/CharSequence;)V") 414 android.accessibilityNodeInfo.setContentDescription = getMethodID(env, cls, "setContentDescription", "(Ljava/lang/CharSequence;)V") 415 android.accessibilityNodeInfo.setParent = getMethodID(env, cls, "setParent", "(Landroid/view/View;I)V") 416 android.accessibilityNodeInfo.addAction = getMethodID(env, cls, "addAction", "(I)V") 417 android.accessibilityNodeInfo.setClassName = getMethodID(env, cls, "setClassName", "(Ljava/lang/CharSequence;)V") 418 android.accessibilityNodeInfo.setCheckable = getMethodID(env, cls, "setCheckable", "(Z)V") 419 android.accessibilityNodeInfo.setSelected = getMethodID(env, cls, "setSelected", "(Z)V") 420 android.accessibilityNodeInfo.setChecked = getMethodID(env, cls, "setChecked", "(Z)V") 421 android.accessibilityNodeInfo.setEnabled = getMethodID(env, cls, "setEnabled", "(Z)V") 422 android.accessibilityNodeInfo.setAccessibilityFocused = getMethodID(env, cls, "setAccessibilityFocused", "(Z)V") 423 424 cls = findClass(env, "android/graphics/Rect") 425 android.rect.cls = C.jclass(C.jni_NewGlobalRef(env, C.jobject(cls))) 426 android.rect.cons = getMethodID(env, cls, "<init>", "(IIII)V") 427 android.mwriteClipboard = getStaticMethodID(env, gio, "writeClipboard", "(Landroid/content/Context;Ljava/lang/String;)V") 428 android.mreadClipboard = getStaticMethodID(env, gio, "readClipboard", "(Landroid/content/Context;)Ljava/lang/String;") 429 android.mwakeupMainThread = getStaticMethodID(env, gio, "wakeupMainThread", "()V") 430 431 intern := func(s string) C.jstring { 432 ref := C.jni_NewGlobalRef(env, C.jobject(javaString(env, s))) 433 return C.jstring(ref) 434 } 435 android.strings.androidViewView = intern("android.view.View") 436 android.strings.androidWidgetButton = intern("android.widget.Button") 437 android.strings.androidWidgetCheckBox = intern("android.widget.CheckBox") 438 android.strings.androidWidgetEditText = intern("android.widget.EditText") 439 android.strings.androidWidgetRadioButton = intern("android.widget.RadioButton") 440 android.strings.androidWidgetSwitch = intern("android.widget.Switch") 441 } 442 443 // JavaVM returns the global JNI JavaVM. 444 func JavaVM() uintptr { 445 jvm := javaVM() 446 return uintptr(unsafe.Pointer(jvm)) 447 } 448 449 func javaVM() *C.JavaVM { 450 android.mu.Lock() 451 defer android.mu.Unlock() 452 return android.jvm 453 } 454 455 // AppContext returns the global Application context as a JNI jobject. 456 func AppContext() uintptr { 457 android.mu.Lock() 458 defer android.mu.Unlock() 459 return uintptr(android.appCtx) 460 } 461 462 //export Java_org_gioui_GioView_onCreateView 463 func Java_org_gioui_GioView_onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong { 464 gioView.once.Do(func() { 465 m := &gioView 466 m.getDensity = getMethodID(env, class, "getDensity", "()I") 467 m.getFontScale = getMethodID(env, class, "getFontScale", "()F") 468 m.showTextInput = getMethodID(env, class, "showTextInput", "()V") 469 m.hideTextInput = getMethodID(env, class, "hideTextInput", "()V") 470 m.setInputHint = getMethodID(env, class, "setInputHint", "(I)V") 471 m.postFrameCallback = getMethodID(env, class, "postFrameCallback", "()V") 472 m.invalidate = getMethodID(env, class, "invalidate", "()V") 473 m.setCursor = getMethodID(env, class, "setCursor", "(I)V") 474 m.setOrientation = getMethodID(env, class, "setOrientation", "(II)V") 475 m.setNavigationColor = getMethodID(env, class, "setNavigationColor", "(II)V") 476 m.setStatusColor = getMethodID(env, class, "setStatusColor", "(II)V") 477 m.setFullscreen = getMethodID(env, class, "setFullscreen", "(Z)V") 478 m.unregister = getMethodID(env, class, "unregister", "()V") 479 m.sendA11yEvent = getMethodID(env, class, "sendA11yEvent", "(II)V") 480 m.sendA11yChange = getMethodID(env, class, "sendA11yChange", "(I)V") 481 m.isA11yActive = getMethodID(env, class, "isA11yActive", "()Z") 482 m.restartInput = getMethodID(env, class, "restartInput", "()V") 483 m.updateSelection = getMethodID(env, class, "updateSelection", "()V") 484 m.updateCaret = getMethodID(env, class, "updateCaret", "(FFFFFFFFFF)V") 485 }) 486 view = C.jni_NewGlobalRef(env, view) 487 wopts := <-mainWindow.out 488 w, ok := windows[wopts.window] 489 if !ok { 490 w = &window{ 491 callbacks: wopts.window, 492 } 493 windows[wopts.window] = w 494 } 495 if w.view != 0 { 496 w.detach(env) 497 } 498 w.view = view 499 w.handle = cgo.NewHandle(w) 500 w.callbacks.SetDriver(w) 501 w.loadConfig(env, class) 502 w.Configure(wopts.options) 503 w.SetInputHint(key.HintAny) 504 w.setStage(system.StagePaused) 505 w.callbacks.Event(ViewEvent{View: uintptr(view)}) 506 return C.jlong(w.handle) 507 } 508 509 //export Java_org_gioui_GioView_onDestroyView 510 func Java_org_gioui_GioView_onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) { 511 w := cgo.Handle(handle).Value().(*window) 512 w.detach(env) 513 } 514 515 //export Java_org_gioui_GioView_onStopView 516 func Java_org_gioui_GioView_onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) { 517 w := cgo.Handle(handle).Value().(*window) 518 w.started = false 519 w.setStage(system.StagePaused) 520 } 521 522 //export Java_org_gioui_GioView_onStartView 523 func Java_org_gioui_GioView_onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) { 524 w := cgo.Handle(handle).Value().(*window) 525 w.started = true 526 if w.win != nil { 527 w.setVisible(env) 528 } 529 } 530 531 //export Java_org_gioui_GioView_onSurfaceDestroyed 532 func Java_org_gioui_GioView_onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) { 533 w := cgo.Handle(handle).Value().(*window) 534 w.win = nil 535 w.setStage(system.StagePaused) 536 } 537 538 //export Java_org_gioui_GioView_onSurfaceChanged 539 func Java_org_gioui_GioView_onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) { 540 w := cgo.Handle(handle).Value().(*window) 541 w.win = C.ANativeWindow_fromSurface(env, surf) 542 if w.started { 543 w.setVisible(env) 544 } 545 } 546 547 //export Java_org_gioui_GioView_onLowMemory 548 func Java_org_gioui_GioView_onLowMemory(env *C.JNIEnv, class C.jclass) { 549 runtime.GC() 550 debug.FreeOSMemory() 551 } 552 553 //export Java_org_gioui_GioView_onConfigurationChanged 554 func Java_org_gioui_GioView_onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) { 555 w := cgo.Handle(view).Value().(*window) 556 w.loadConfig(env, class) 557 if w.stage >= system.StageInactive { 558 w.draw(env, true) 559 } 560 } 561 562 //export Java_org_gioui_GioView_onFrameCallback 563 func Java_org_gioui_GioView_onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong) { 564 w, exist := cgo.Handle(view).Value().(*window) 565 if !exist { 566 return 567 } 568 if w.stage < system.StageInactive { 569 return 570 } 571 if w.animating { 572 w.draw(env, false) 573 callVoidMethod(env, w.view, gioView.postFrameCallback) 574 } 575 } 576 577 //export Java_org_gioui_GioView_onBack 578 func Java_org_gioui_GioView_onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean { 579 w := cgo.Handle(view).Value().(*window) 580 if w.callbacks.Event(key.Event{Name: key.NameBack}) { 581 return C.JNI_TRUE 582 } 583 return C.JNI_FALSE 584 } 585 586 //export Java_org_gioui_GioView_onFocusChange 587 func Java_org_gioui_GioView_onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) { 588 w := cgo.Handle(view).Value().(*window) 589 w.callbacks.Event(key.FocusEvent{Focus: focus == C.JNI_TRUE}) 590 } 591 592 //export Java_org_gioui_GioView_onWindowInsets 593 func Java_org_gioui_GioView_onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) { 594 w := cgo.Handle(view).Value().(*window) 595 w.insets = pixelInsets{ 596 top: int(top), 597 bottom: int(bottom), 598 left: int(left), 599 right: int(right), 600 } 601 if w.stage >= system.StageInactive { 602 w.draw(env, true) 603 } 604 } 605 606 //export Java_org_gioui_GioView_initializeAccessibilityNodeInfo 607 func Java_org_gioui_GioView_initializeAccessibilityNodeInfo(env *C.JNIEnv, class C.jclass, view C.jlong, virtID, screenX, screenY C.jint, info C.jobject) C.jobject { 608 w := cgo.Handle(view).Value().(*window) 609 semID := w.semIDFor(virtID) 610 sem, found := w.callbacks.LookupSemantic(semID) 611 if found { 612 off := image.Pt(int(screenX), int(screenY)) 613 if err := w.initAccessibilityNodeInfo(env, sem, off, info); err != nil { 614 panic(err) 615 } 616 } 617 return info 618 } 619 620 //export Java_org_gioui_GioView_onTouchExploration 621 func Java_org_gioui_GioView_onTouchExploration(env *C.JNIEnv, class C.jclass, view C.jlong, x, y C.jfloat) { 622 w := cgo.Handle(view).Value().(*window) 623 semID, _ := w.callbacks.SemanticAt(f32.Pt(float32(x), float32(y))) 624 if w.semantic.hoverID == semID { 625 return 626 } 627 // Android expects ENTER before EXIT. 628 if semID != 0 { 629 callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_ENTER, jvalue(w.virtualIDFor(semID))) 630 } 631 if prevID := w.semantic.hoverID; prevID != 0 { 632 callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_EXIT, jvalue(w.virtualIDFor(prevID))) 633 } 634 w.semantic.hoverID = semID 635 } 636 637 //export Java_org_gioui_GioView_onExitTouchExploration 638 func Java_org_gioui_GioView_onExitTouchExploration(env *C.JNIEnv, class C.jclass, view C.jlong) { 639 w := cgo.Handle(view).Value().(*window) 640 if w.semantic.hoverID != 0 { 641 callVoidMethod(env, w.view, gioView.sendA11yEvent, TYPE_VIEW_HOVER_EXIT, jvalue(w.virtualIDFor(w.semantic.hoverID))) 642 w.semantic.hoverID = 0 643 } 644 } 645 646 //export Java_org_gioui_GioView_onA11yFocus 647 func Java_org_gioui_GioView_onA11yFocus(env *C.JNIEnv, class C.jclass, view C.jlong, virtID C.jint) { 648 w := cgo.Handle(view).Value().(*window) 649 if semID := w.semIDFor(virtID); semID != w.semantic.focusID { 650 w.semantic.focusID = semID 651 // Android needs invalidate to refresh the TalkBack focus indicator. 652 callVoidMethod(env, w.view, gioView.invalidate) 653 } 654 } 655 656 //export Java_org_gioui_GioView_onClearA11yFocus 657 func Java_org_gioui_GioView_onClearA11yFocus(env *C.JNIEnv, class C.jclass, view C.jlong, virtID C.jint) { 658 w := cgo.Handle(view).Value().(*window) 659 if w.semantic.focusID == w.semIDFor(virtID) { 660 w.semantic.focusID = 0 661 } 662 } 663 664 func (w *window) initAccessibilityNodeInfo(env *C.JNIEnv, sem router.SemanticNode, off image.Point, info C.jobject) error { 665 for _, ch := range sem.Children { 666 err := callVoidMethod(env, info, android.accessibilityNodeInfo.addChild, jvalue(w.view), jvalue(w.virtualIDFor(ch.ID))) 667 if err != nil { 668 return err 669 } 670 } 671 if sem.ParentID != 0 { 672 if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setParent, jvalue(w.view), jvalue(w.virtualIDFor(sem.ParentID))); err != nil { 673 return err 674 } 675 b := sem.Desc.Bounds.Add(off) 676 rect, err := newObject(env, android.rect.cls, android.rect.cons, 677 jvalue(b.Min.X), 678 jvalue(b.Min.Y), 679 jvalue(b.Max.X), 680 jvalue(b.Max.Y), 681 ) 682 if err != nil { 683 return err 684 } 685 if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setBoundsInScreen, jvalue(rect)); err != nil { 686 return err 687 } 688 } 689 d := sem.Desc 690 if l := d.Label; l != "" { 691 jlbl := javaString(env, l) 692 if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setText, jvalue(jlbl)); err != nil { 693 return err 694 } 695 } 696 if d.Description != "" { 697 jd := javaString(env, d.Description) 698 if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setContentDescription, jvalue(jd)); err != nil { 699 return err 700 } 701 } 702 addAction := func(act C.jint) { 703 if err := callVoidMethod(env, info, android.accessibilityNodeInfo.addAction, jvalue(act)); err != nil { 704 panic(err) 705 } 706 } 707 if d.Gestures&router.ClickGesture != 0 { 708 addAction(ACTION_CLICK) 709 } 710 clsName := android.strings.androidViewView 711 selectMethod := android.accessibilityNodeInfo.setChecked 712 checkable := false 713 switch d.Class { 714 case semantic.Button: 715 clsName = android.strings.androidWidgetButton 716 case semantic.CheckBox: 717 checkable = true 718 clsName = android.strings.androidWidgetCheckBox 719 case semantic.Editor: 720 clsName = android.strings.androidWidgetEditText 721 case semantic.RadioButton: 722 selectMethod = android.accessibilityNodeInfo.setSelected 723 clsName = android.strings.androidWidgetRadioButton 724 case semantic.Switch: 725 checkable = true 726 clsName = android.strings.androidWidgetSwitch 727 } 728 if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setClassName, jvalue(clsName)); err != nil { 729 panic(err) 730 } 731 if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setCheckable, jvalue(javaBool(checkable))); err != nil { 732 panic(err) 733 } 734 if err := callVoidMethod(env, info, selectMethod, jvalue(javaBool(d.Selected))); err != nil { 735 panic(err) 736 } 737 if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setEnabled, jvalue(javaBool(!d.Disabled))); err != nil { 738 panic(err) 739 } 740 isFocus := w.semantic.focusID == sem.ID 741 if err := callVoidMethod(env, info, android.accessibilityNodeInfo.setAccessibilityFocused, jvalue(javaBool(isFocus))); err != nil { 742 panic(err) 743 } 744 if isFocus { 745 addAction(ACTION_CLEAR_ACCESSIBILITY_FOCUS) 746 } else { 747 addAction(ACTION_ACCESSIBILITY_FOCUS) 748 } 749 return nil 750 } 751 752 func (w *window) virtualIDFor(id router.SemanticID) C.jint { 753 // TODO: Android virtual IDs are 32-bit Java integers, but childID is a int64. 754 if id == w.semantic.rootID { 755 return HOST_VIEW_ID 756 } 757 return C.jint(id) 758 } 759 760 func (w *window) semIDFor(virtID C.jint) router.SemanticID { 761 if virtID == HOST_VIEW_ID { 762 return w.semantic.rootID 763 } 764 return router.SemanticID(virtID) 765 } 766 767 func (w *window) detach(env *C.JNIEnv) { 768 callVoidMethod(env, w.view, gioView.unregister) 769 w.callbacks.Event(ViewEvent{}) 770 w.callbacks.SetDriver(nil) 771 w.handle.Delete() 772 C.jni_DeleteGlobalRef(env, w.view) 773 w.view = 0 774 } 775 776 func (w *window) setVisible(env *C.JNIEnv) { 777 width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win) 778 if width == 0 || height == 0 { 779 return 780 } 781 w.setStage(system.StageRunning) 782 w.draw(env, true) 783 } 784 785 func (w *window) setStage(stage system.Stage) { 786 if stage == w.stage { 787 return 788 } 789 w.stage = stage 790 w.callbacks.Event(system.StageEvent{stage}) 791 } 792 793 func (w *window) setVisual(visID int) error { 794 if C.ANativeWindow_setBuffersGeometry(w.win, 0, 0, C.int32_t(visID)) != 0 { 795 return errors.New("ANativeWindow_setBuffersGeometry failed") 796 } 797 return nil 798 } 799 800 func (w *window) nativeWindow() (*C.ANativeWindow, int, int) { 801 width, height := C.ANativeWindow_getWidth(w.win), C.ANativeWindow_getHeight(w.win) 802 return w.win, int(width), int(height) 803 } 804 805 func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) { 806 dpi := int(C.jni_CallIntMethod(env, w.view, gioView.getDensity)) 807 w.fontScale = float32(C.jni_CallFloatMethod(env, w.view, gioView.getFontScale)) 808 switch dpi { 809 case C.ACONFIGURATION_DENSITY_NONE, 810 C.ACONFIGURATION_DENSITY_DEFAULT, 811 C.ACONFIGURATION_DENSITY_ANY: 812 // Assume standard density. 813 w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM 814 default: 815 w.dpi = int(dpi) 816 } 817 } 818 819 func (w *window) SetAnimating(anim bool) { 820 w.animating = anim 821 if anim { 822 runInJVM(javaVM(), func(env *C.JNIEnv) { 823 callVoidMethod(env, w.view, gioView.postFrameCallback) 824 }) 825 } 826 } 827 828 func (w *window) draw(env *C.JNIEnv, sync bool) { 829 size := image.Pt(int(C.ANativeWindow_getWidth(w.win)), int(C.ANativeWindow_getHeight(w.win))) 830 if size != w.config.Size { 831 w.config.Size = size 832 w.callbacks.Event(ConfigEvent{Config: w.config}) 833 } 834 if size.X == 0 || size.Y == 0 { 835 return 836 } 837 const inchPrDp = 1.0 / 160 838 ppdp := float32(w.dpi) * inchPrDp 839 dppp := unit.Dp(1.0 / ppdp) 840 insets := system.Insets{ 841 Top: unit.Dp(w.insets.top) * dppp, 842 Bottom: unit.Dp(w.insets.bottom) * dppp, 843 Left: unit.Dp(w.insets.left) * dppp, 844 Right: unit.Dp(w.insets.right) * dppp, 845 } 846 w.callbacks.Event(frameEvent{ 847 FrameEvent: system.FrameEvent{ 848 Now: time.Now(), 849 Size: w.config.Size, 850 Insets: insets, 851 Metric: unit.Metric{ 852 PxPerDp: ppdp, 853 PxPerSp: w.fontScale * ppdp, 854 }, 855 }, 856 Sync: sync, 857 }) 858 a11yActive, err := callBooleanMethod(env, w.view, gioView.isA11yActive) 859 if err != nil { 860 panic(err) 861 } 862 if a11yActive { 863 if newR, oldR := w.callbacks.SemanticRoot(), w.semantic.rootID; newR != oldR { 864 // Remap focus and hover. 865 if oldR == w.semantic.hoverID { 866 w.semantic.hoverID = newR 867 } 868 if oldR == w.semantic.focusID { 869 w.semantic.focusID = newR 870 } 871 w.semantic.rootID = newR 872 callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(newR))) 873 } 874 w.semantic.diffs = w.callbacks.AppendSemanticDiffs(w.semantic.diffs[:0]) 875 for _, id := range w.semantic.diffs { 876 callVoidMethod(env, w.view, gioView.sendA11yChange, jvalue(w.virtualIDFor(id))) 877 } 878 } 879 } 880 881 func runInJVM(jvm *C.JavaVM, f func(env *C.JNIEnv)) { 882 if jvm == nil { 883 panic("nil JVM") 884 } 885 runtime.LockOSThread() 886 defer runtime.UnlockOSThread() 887 var env *C.JNIEnv 888 if res := C.jni_GetEnv(jvm, &env, C.JNI_VERSION_1_6); res != C.JNI_OK { 889 if res != C.JNI_EDETACHED { 890 panic(fmt.Errorf("JNI GetEnv failed with error %d", res)) 891 } 892 if C.jni_AttachCurrentThread(jvm, &env, nil) != C.JNI_OK { 893 panic(errors.New("runInJVM: AttachCurrentThread failed")) 894 } 895 defer C.jni_DetachCurrentThread(jvm) 896 } 897 898 f(env) 899 } 900 901 func convertKeyCode(code C.jint) (string, bool) { 902 var n string 903 switch code { 904 case C.AKEYCODE_FORWARD_DEL: 905 n = key.NameDeleteForward 906 case C.AKEYCODE_DEL: 907 n = key.NameDeleteBackward 908 case C.AKEYCODE_NUMPAD_ENTER: 909 n = key.NameEnter 910 case C.AKEYCODE_ENTER: 911 n = key.NameReturn 912 case C.AKEYCODE_CTRL_LEFT, C.AKEYCODE_CTRL_RIGHT: 913 n = key.NameCtrl 914 case C.AKEYCODE_SHIFT_LEFT, C.AKEYCODE_SHIFT_RIGHT: 915 n = key.NameShift 916 case C.AKEYCODE_ALT_LEFT, C.AKEYCODE_ALT_RIGHT: 917 n = key.NameAlt 918 case C.AKEYCODE_META_LEFT, C.AKEYCODE_META_RIGHT: 919 n = key.NameSuper 920 case C.AKEYCODE_DPAD_UP: 921 n = key.NameUpArrow 922 case C.AKEYCODE_DPAD_DOWN: 923 n = key.NameDownArrow 924 case C.AKEYCODE_DPAD_LEFT: 925 n = key.NameLeftArrow 926 case C.AKEYCODE_DPAD_RIGHT: 927 n = key.NameRightArrow 928 default: 929 return "", false 930 } 931 return n, true 932 } 933 934 //export Java_org_gioui_GioView_onKeyEvent 935 func Java_org_gioui_GioView_onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, pressed C.jboolean, t C.jlong) { 936 w := cgo.Handle(handle).Value().(*window) 937 if pressed == C.JNI_TRUE && keyCode == C.AKEYCODE_DPAD_CENTER { 938 w.callbacks.ClickFocus() 939 return 940 } 941 if n, ok := convertKeyCode(keyCode); ok { 942 state := key.Release 943 if pressed == C.JNI_TRUE { 944 state = key.Press 945 } 946 w.callbacks.Event(key.Event{Name: n, State: state}) 947 } 948 if pressed == C.JNI_TRUE && r != 0 && r != '\n' { // Checking for "\n" to prevent duplication with key.NameEnter (gio#224). 949 w.callbacks.EditorInsert(string(rune(r))) 950 } 951 } 952 953 //export Java_org_gioui_GioView_onTouchEvent 954 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) { 955 w := cgo.Handle(handle).Value().(*window) 956 var kind pointer.Kind 957 switch action { 958 case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN: 959 kind = pointer.Press 960 case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP: 961 kind = pointer.Release 962 case C.AMOTION_EVENT_ACTION_CANCEL: 963 kind = pointer.Cancel 964 case C.AMOTION_EVENT_ACTION_MOVE: 965 kind = pointer.Move 966 case C.AMOTION_EVENT_ACTION_SCROLL: 967 kind = pointer.Scroll 968 default: 969 return 970 } 971 var src pointer.Source 972 var btns pointer.Buttons 973 if jbtns&C.AMOTION_EVENT_BUTTON_PRIMARY != 0 { 974 btns |= pointer.ButtonPrimary 975 } 976 if jbtns&C.AMOTION_EVENT_BUTTON_SECONDARY != 0 { 977 btns |= pointer.ButtonSecondary 978 } 979 if jbtns&C.AMOTION_EVENT_BUTTON_TERTIARY != 0 { 980 btns |= pointer.ButtonTertiary 981 } 982 switch tool { 983 case C.AMOTION_EVENT_TOOL_TYPE_FINGER: 984 src = pointer.Touch 985 case C.AMOTION_EVENT_TOOL_TYPE_STYLUS: 986 src = pointer.Touch 987 case C.AMOTION_EVENT_TOOL_TYPE_MOUSE: 988 src = pointer.Mouse 989 case C.AMOTION_EVENT_TOOL_TYPE_UNKNOWN: 990 // For example, triggered via 'adb shell input tap'. 991 // Instead of discarding it, treat it as a touch event. 992 src = pointer.Touch 993 default: 994 return 995 } 996 w.callbacks.Event(pointer.Event{ 997 Kind: kind, 998 Source: src, 999 Buttons: btns, 1000 PointerID: pointer.ID(pointerID), 1001 Time: time.Duration(t) * time.Millisecond, 1002 Position: f32.Point{X: float32(x), Y: float32(y)}, 1003 Scroll: f32.Pt(float32(scrollX), float32(scrollY)), 1004 }) 1005 } 1006 1007 //export Java_org_gioui_GioView_imeSelectionStart 1008 func Java_org_gioui_GioView_imeSelectionStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint { 1009 w := cgo.Handle(handle).Value().(*window) 1010 sel := w.callbacks.EditorState().Selection 1011 start := sel.Start 1012 if sel.End < sel.Start { 1013 start = sel.End 1014 } 1015 return C.jint(start) 1016 } 1017 1018 //export Java_org_gioui_GioView_imeSelectionEnd 1019 func Java_org_gioui_GioView_imeSelectionEnd(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint { 1020 w := cgo.Handle(handle).Value().(*window) 1021 sel := w.callbacks.EditorState().Selection 1022 end := sel.End 1023 if sel.End < sel.Start { 1024 end = sel.Start 1025 } 1026 return C.jint(end) 1027 } 1028 1029 //export Java_org_gioui_GioView_imeComposingStart 1030 func Java_org_gioui_GioView_imeComposingStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint { 1031 w := cgo.Handle(handle).Value().(*window) 1032 comp := w.callbacks.EditorState().compose 1033 start := comp.Start 1034 if e := comp.End; e < start { 1035 start = e 1036 } 1037 return C.jint(start) 1038 } 1039 1040 //export Java_org_gioui_GioView_imeComposingEnd 1041 func Java_org_gioui_GioView_imeComposingEnd(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint { 1042 w := cgo.Handle(handle).Value().(*window) 1043 comp := w.callbacks.EditorState().compose 1044 end := comp.End 1045 if s := comp.Start; s > end { 1046 end = s 1047 } 1048 return C.jint(end) 1049 } 1050 1051 //export Java_org_gioui_GioView_imeSnippet 1052 func Java_org_gioui_GioView_imeSnippet(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jstring { 1053 w := cgo.Handle(handle).Value().(*window) 1054 snip := w.callbacks.EditorState().Snippet.Text 1055 return javaString(env, snip) 1056 } 1057 1058 //export Java_org_gioui_GioView_imeSnippetStart 1059 func Java_org_gioui_GioView_imeSnippetStart(env *C.JNIEnv, class C.jclass, handle C.jlong) C.jint { 1060 w := cgo.Handle(handle).Value().(*window) 1061 return C.jint(w.callbacks.EditorState().Snippet.Start) 1062 } 1063 1064 //export Java_org_gioui_GioView_imeSetSnippet 1065 func Java_org_gioui_GioView_imeSetSnippet(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) { 1066 w := cgo.Handle(handle).Value().(*window) 1067 if start < 0 { 1068 start = 0 1069 } 1070 if end < start { 1071 end = start 1072 } 1073 r := key.Range{Start: int(start), End: int(end)} 1074 w.callbacks.SetEditorSnippet(r) 1075 } 1076 1077 //export Java_org_gioui_GioView_imeSetSelection 1078 func Java_org_gioui_GioView_imeSetSelection(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) { 1079 w := cgo.Handle(handle).Value().(*window) 1080 r := key.Range{Start: int(start), End: int(end)} 1081 w.callbacks.SetEditorSelection(r) 1082 } 1083 1084 //export Java_org_gioui_GioView_imeSetComposingRegion 1085 func Java_org_gioui_GioView_imeSetComposingRegion(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint) { 1086 w := cgo.Handle(handle).Value().(*window) 1087 w.callbacks.SetComposingRegion(key.Range{ 1088 Start: int(start), 1089 End: int(end), 1090 }) 1091 } 1092 1093 //export Java_org_gioui_GioView_imeReplace 1094 func Java_org_gioui_GioView_imeReplace(env *C.JNIEnv, class C.jclass, handle C.jlong, start, end C.jint, jtext C.jstring) { 1095 w := cgo.Handle(handle).Value().(*window) 1096 r := key.Range{Start: int(start), End: int(end)} 1097 text := goString(env, jtext) 1098 w.callbacks.EditorReplace(r, text) 1099 } 1100 1101 //export Java_org_gioui_GioView_imeToRunes 1102 func Java_org_gioui_GioView_imeToRunes(env *C.JNIEnv, class C.jclass, handle C.jlong, chars C.jint) C.jint { 1103 w := cgo.Handle(handle).Value().(*window) 1104 state := w.callbacks.EditorState() 1105 return C.jint(state.RunesIndex(int(chars))) 1106 } 1107 1108 //export Java_org_gioui_GioView_imeToUTF16 1109 func Java_org_gioui_GioView_imeToUTF16(env *C.JNIEnv, class C.jclass, handle C.jlong, runes C.jint) C.jint { 1110 w := cgo.Handle(handle).Value().(*window) 1111 state := w.callbacks.EditorState() 1112 return C.jint(state.UTF16Index(int(runes))) 1113 } 1114 1115 func (w *window) EditorStateChanged(old, new editorState) { 1116 runInJVM(javaVM(), func(env *C.JNIEnv) { 1117 if old.Snippet != new.Snippet { 1118 callVoidMethod(env, w.view, gioView.restartInput) 1119 return 1120 } 1121 if old.Selection.Range != new.Selection.Range { 1122 w.callbacks.SetComposingRegion(key.Range{Start: -1, End: -1}) 1123 callVoidMethod(env, w.view, gioView.updateSelection) 1124 } 1125 if old.Selection.Transform != new.Selection.Transform || old.Selection.Caret != new.Selection.Caret { 1126 sel := new.Selection 1127 m00, m01, m02, m10, m11, m12 := sel.Transform.Elems() 1128 f := func(v float32) jvalue { 1129 return jvalue(math.Float32bits(v)) 1130 } 1131 c := sel.Caret 1132 callVoidMethod(env, w.view, gioView.updateCaret, f(m00), f(m01), f(m02), f(m10), f(m11), f(m12), f(c.Pos.X), f(c.Pos.Y-c.Ascent), f(c.Pos.Y), f(c.Pos.Y+c.Descent)) 1133 } 1134 }) 1135 } 1136 1137 func (w *window) ShowTextInput(show bool) { 1138 runInJVM(javaVM(), func(env *C.JNIEnv) { 1139 if show { 1140 callVoidMethod(env, w.view, gioView.showTextInput) 1141 } else { 1142 callVoidMethod(env, w.view, gioView.hideTextInput) 1143 } 1144 }) 1145 } 1146 1147 func (w *window) SetInputHint(mode key.InputHint) { 1148 // Constants defined at https://developer.android.com/reference/android/text/InputType. 1149 const ( 1150 TYPE_NULL = 0 1151 1152 TYPE_CLASS_TEXT = 1 1153 TYPE_TEXT_VARIATION_EMAIL_ADDRESS = 32 1154 TYPE_TEXT_VARIATION_URI = 16 1155 TYPE_TEXT_VARIATION_PASSWORD = 128 1156 TYPE_TEXT_FLAG_CAP_SENTENCES = 16384 1157 TYPE_TEXT_FLAG_AUTO_CORRECT = 32768 1158 1159 TYPE_CLASS_NUMBER = 2 1160 TYPE_NUMBER_FLAG_DECIMAL = 8192 1161 TYPE_NUMBER_FLAG_SIGNED = 4096 1162 1163 TYPE_CLASS_PHONE = 3 1164 ) 1165 1166 runInJVM(javaVM(), func(env *C.JNIEnv) { 1167 var m jvalue 1168 switch mode { 1169 case key.HintText: 1170 m = TYPE_CLASS_TEXT | TYPE_TEXT_FLAG_AUTO_CORRECT | TYPE_TEXT_FLAG_CAP_SENTENCES 1171 case key.HintNumeric: 1172 m = TYPE_CLASS_NUMBER | TYPE_NUMBER_FLAG_DECIMAL | TYPE_NUMBER_FLAG_SIGNED 1173 case key.HintEmail: 1174 m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_EMAIL_ADDRESS 1175 case key.HintURL: 1176 m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_URI 1177 case key.HintTelephone: 1178 m = TYPE_CLASS_PHONE 1179 case key.HintPassword: 1180 m = TYPE_CLASS_TEXT | TYPE_TEXT_VARIATION_PASSWORD 1181 default: 1182 m = TYPE_CLASS_TEXT 1183 } 1184 1185 callVoidMethod(env, w.view, gioView.setInputHint, m) 1186 }) 1187 } 1188 1189 func javaBool(b bool) C.jboolean { 1190 if b { 1191 return C.JNI_TRUE 1192 } else { 1193 return C.JNI_FALSE 1194 } 1195 } 1196 1197 func javaString(env *C.JNIEnv, str string) C.jstring { 1198 utf16Chars := utf16.Encode([]rune(str)) 1199 var ptr *C.jchar 1200 if len(utf16Chars) > 0 { 1201 ptr = (*C.jchar)(unsafe.Pointer(&utf16Chars[0])) 1202 } 1203 return C.jni_NewString(env, ptr, C.int(len(utf16Chars))) 1204 } 1205 1206 func varArgs(args []jvalue) *C.jvalue { 1207 if len(args) == 0 { 1208 return nil 1209 } 1210 return (*C.jvalue)(unsafe.Pointer(&args[0])) 1211 } 1212 1213 func callStaticVoidMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) error { 1214 C.jni_CallStaticVoidMethodA(env, cls, method, varArgs(args)) 1215 return exception(env) 1216 } 1217 1218 func callStaticObjectMethod(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) { 1219 res := C.jni_CallStaticObjectMethodA(env, cls, method, varArgs(args)) 1220 return res, exception(env) 1221 } 1222 1223 func callVoidMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) error { 1224 C.jni_CallVoidMethodA(env, obj, method, varArgs(args)) 1225 return exception(env) 1226 } 1227 1228 func callBooleanMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (bool, error) { 1229 res := C.jni_CallBooleanMethodA(env, obj, method, varArgs(args)) 1230 return res == C.JNI_TRUE, exception(env) 1231 } 1232 1233 func callObjectMethod(env *C.JNIEnv, obj C.jobject, method C.jmethodID, args ...jvalue) (C.jobject, error) { 1234 res := C.jni_CallObjectMethodA(env, obj, method, varArgs(args)) 1235 return res, exception(env) 1236 } 1237 1238 func newObject(env *C.JNIEnv, cls C.jclass, method C.jmethodID, args ...jvalue) (C.jobject, error) { 1239 res := C.jni_NewObjectA(env, cls, method, varArgs(args)) 1240 return res, exception(env) 1241 } 1242 1243 // exception returns an error corresponding to the pending 1244 // exception, or nil if no exception is pending. The pending 1245 // exception is cleared. 1246 func exception(env *C.JNIEnv) error { 1247 thr := C.jni_ExceptionOccurred(env) 1248 if thr == 0 { 1249 return nil 1250 } 1251 C.jni_ExceptionClear(env) 1252 cls := getObjectClass(env, C.jobject(thr)) 1253 toString := getMethodID(env, cls, "toString", "()Ljava/lang/String;") 1254 msg, err := callObjectMethod(env, C.jobject(thr), toString) 1255 if err != nil { 1256 return err 1257 } 1258 return errors.New(goString(env, C.jstring(msg))) 1259 } 1260 1261 func getObjectClass(env *C.JNIEnv, obj C.jobject) C.jclass { 1262 if obj == 0 { 1263 panic("null object") 1264 } 1265 cls := C.jni_GetObjectClass(env, C.jobject(obj)) 1266 if err := exception(env); err != nil { 1267 // GetObjectClass should never fail. 1268 panic(err) 1269 } 1270 return cls 1271 } 1272 1273 // goString converts the JVM jstring to a Go string. 1274 func goString(env *C.JNIEnv, str C.jstring) string { 1275 if str == 0 { 1276 return "" 1277 } 1278 strlen := C.jni_GetStringLength(env, C.jstring(str)) 1279 chars := C.jni_GetStringChars(env, C.jstring(str)) 1280 utf16Chars := unsafe.Slice((*uint16)(unsafe.Pointer(chars)), strlen) 1281 utf8 := utf16.Decode(utf16Chars) 1282 return string(utf8) 1283 } 1284 1285 func findClass(env *C.JNIEnv, name string) C.jclass { 1286 cn := C.CString(name) 1287 defer C.free(unsafe.Pointer(cn)) 1288 return C.jni_FindClass(env, cn) 1289 } 1290 1291 func osMain() { 1292 } 1293 1294 func newWindow(window *callbacks, options []Option) error { 1295 mainWindow.in <- windowAndConfig{window, options} 1296 return <-mainWindow.errs 1297 } 1298 1299 func (w *window) WriteClipboard(s string) { 1300 runInJVM(javaVM(), func(env *C.JNIEnv) { 1301 jstr := javaString(env, s) 1302 callStaticVoidMethod(env, android.gioCls, android.mwriteClipboard, 1303 jvalue(android.appCtx), jvalue(jstr)) 1304 }) 1305 } 1306 1307 func (w *window) ReadClipboard() { 1308 runInJVM(javaVM(), func(env *C.JNIEnv) { 1309 c, err := callStaticObjectMethod(env, android.gioCls, android.mreadClipboard, 1310 jvalue(android.appCtx)) 1311 if err != nil { 1312 return 1313 } 1314 content := goString(env, C.jstring(c)) 1315 w.callbacks.Event(clipboard.Event{Text: content}) 1316 }) 1317 } 1318 1319 func (w *window) Configure(options []Option) { 1320 runInJVM(javaVM(), func(env *C.JNIEnv) { 1321 prev := w.config 1322 cnf := w.config 1323 cnf.apply(unit.Metric{}, options) 1324 // Decorations are never disabled. 1325 cnf.Decorated = true 1326 1327 if prev.Orientation != cnf.Orientation { 1328 w.config.Orientation = cnf.Orientation 1329 setOrientation(env, w.view, cnf.Orientation) 1330 } 1331 if prev.NavigationColor != cnf.NavigationColor { 1332 w.config.NavigationColor = cnf.NavigationColor 1333 setNavigationColor(env, w.view, cnf.NavigationColor) 1334 } 1335 if prev.StatusColor != cnf.StatusColor { 1336 w.config.StatusColor = cnf.StatusColor 1337 setStatusColor(env, w.view, cnf.StatusColor) 1338 } 1339 if prev.Mode != cnf.Mode { 1340 switch cnf.Mode { 1341 case Fullscreen: 1342 callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_TRUE) 1343 w.config.Mode = Fullscreen 1344 case Windowed: 1345 callVoidMethod(env, w.view, gioView.setFullscreen, C.JNI_FALSE) 1346 w.config.Mode = Windowed 1347 } 1348 } 1349 if cnf.Decorated != prev.Decorated { 1350 w.config.Decorated = cnf.Decorated 1351 } 1352 w.callbacks.Event(ConfigEvent{Config: w.config}) 1353 }) 1354 } 1355 1356 func (w *window) Perform(system.Action) {} 1357 1358 func (w *window) SetCursor(cursor pointer.Cursor) { 1359 runInJVM(javaVM(), func(env *C.JNIEnv) { 1360 setCursor(env, w.view, cursor) 1361 }) 1362 } 1363 1364 func (w *window) Wakeup() { 1365 runOnMain(func(env *C.JNIEnv) { 1366 w.callbacks.Event(wakeupEvent{}) 1367 }) 1368 } 1369 1370 var androidCursor = [...]uint16{ 1371 pointer.CursorDefault: 1000, // TYPE_ARROW 1372 pointer.CursorNone: 0, 1373 pointer.CursorText: 1008, // TYPE_TEXT 1374 pointer.CursorVerticalText: 1009, // TYPE_VERTICAL_TEXT 1375 pointer.CursorPointer: 1002, // TYPE_HAND 1376 pointer.CursorCrosshair: 1007, // TYPE_CROSSHAIR 1377 pointer.CursorAllScroll: 1013, // TYPE_ALL_SCROLL 1378 pointer.CursorColResize: 1014, // TYPE_HORIZONTAL_DOUBLE_ARROW 1379 pointer.CursorRowResize: 1015, // TYPE_VERTICAL_DOUBLE_ARROW 1380 pointer.CursorGrab: 1020, // TYPE_GRAB 1381 pointer.CursorGrabbing: 1021, // TYPE_GRABBING 1382 pointer.CursorNotAllowed: 1012, // TYPE_NO_DROP 1383 pointer.CursorWait: 1004, // TYPE_WAIT 1384 pointer.CursorProgress: 1000, // TYPE_ARROW 1385 pointer.CursorNorthWestResize: 1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW 1386 pointer.CursorNorthEastResize: 1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW 1387 pointer.CursorSouthWestResize: 1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW 1388 pointer.CursorSouthEastResize: 1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW 1389 pointer.CursorNorthSouthResize: 1015, // TYPE_VERTICAL_DOUBLE_ARROW 1390 pointer.CursorEastWestResize: 1014, // TYPE_HORIZONTAL_DOUBLE_ARROW 1391 pointer.CursorWestResize: 1014, // TYPE_HORIZONTAL_DOUBLE_ARROW 1392 pointer.CursorEastResize: 1014, // TYPE_HORIZONTAL_DOUBLE_ARROW 1393 pointer.CursorNorthResize: 1015, // TYPE_VERTICAL_DOUBLE_ARROW 1394 pointer.CursorSouthResize: 1015, // TYPE_VERTICAL_DOUBLE_ARROW 1395 pointer.CursorNorthEastSouthWestResize: 1016, // TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW 1396 pointer.CursorNorthWestSouthEastResize: 1017, // TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW 1397 } 1398 1399 func setCursor(env *C.JNIEnv, view C.jobject, cursor pointer.Cursor) { 1400 curID := androidCursor[cursor] 1401 callVoidMethod(env, view, gioView.setCursor, jvalue(curID)) 1402 } 1403 1404 func setOrientation(env *C.JNIEnv, view C.jobject, mode Orientation) { 1405 var ( 1406 id int 1407 idFallback int // Used only for SDK 17 or older. 1408 ) 1409 // Constants defined at https://developer.android.com/reference/android/content/pm/ActivityInfo. 1410 switch mode { 1411 case AnyOrientation: 1412 id, idFallback = 2, 2 // SCREEN_ORIENTATION_USER 1413 case LandscapeOrientation: 1414 id, idFallback = 11, 0 // SCREEN_ORIENTATION_USER_LANDSCAPE (or SCREEN_ORIENTATION_LANDSCAPE) 1415 case PortraitOrientation: 1416 id, idFallback = 12, 1 // SCREEN_ORIENTATION_USER_PORTRAIT (or SCREEN_ORIENTATION_PORTRAIT) 1417 } 1418 callVoidMethod(env, view, gioView.setOrientation, jvalue(id), jvalue(idFallback)) 1419 } 1420 1421 func setStatusColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) { 1422 callVoidMethod(env, view, gioView.setStatusColor, 1423 jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)), 1424 jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)), 1425 ) 1426 } 1427 1428 func setNavigationColor(env *C.JNIEnv, view C.jobject, color color.NRGBA) { 1429 callVoidMethod(env, view, gioView.setNavigationColor, 1430 jvalue(uint32(color.A)<<24|uint32(color.R)<<16|uint32(color.G)<<8|uint32(color.B)), 1431 jvalue(int(f32color.LinearFromSRGB(color).Luminance()*255)), 1432 ) 1433 } 1434 1435 // runOnMain runs a function on the Java main thread. 1436 func runOnMain(f func(env *C.JNIEnv)) { 1437 go func() { 1438 mainFuncs <- f 1439 runInJVM(javaVM(), func(env *C.JNIEnv) { 1440 callStaticVoidMethod(env, android.gioCls, android.mwakeupMainThread) 1441 }) 1442 }() 1443 } 1444 1445 //export Java_org_gioui_Gio_scheduleMainFuncs 1446 func Java_org_gioui_Gio_scheduleMainFuncs(env *C.JNIEnv, cls C.jclass) { 1447 for { 1448 select { 1449 case f := <-mainFuncs: 1450 f(env) 1451 default: 1452 return 1453 } 1454 } 1455 } 1456 1457 func (_ ViewEvent) ImplementsEvent() {}