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