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