gioui.org/ui@v0.0.0-20190926171558-ce74bc0cbaea/app/os_android.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 3 package app 4 5 /* 6 #cgo LDFLAGS: -landroid 7 8 #include <android/native_window_jni.h> 9 #include <android/configuration.h> 10 #include <android/keycodes.h> 11 #include <android/input.h> 12 #include <stdlib.h> 13 #include "os_android.h" 14 */ 15 import "C" 16 17 import ( 18 "errors" 19 "fmt" 20 "image" 21 "runtime" 22 "runtime/debug" 23 "sync" 24 "time" 25 "unsafe" 26 27 "gioui.org/ui" 28 "gioui.org/ui/f32" 29 "gioui.org/ui/key" 30 "gioui.org/ui/pointer" 31 ) 32 33 type window struct { 34 *Window 35 36 view C.jobject 37 38 dpi int 39 fontScale float32 40 insets Insets 41 42 stage Stage 43 started bool 44 45 mu sync.Mutex 46 win *C.ANativeWindow 47 animating bool 48 49 mgetDensity C.jmethodID 50 mgetFontScale C.jmethodID 51 mshowTextInput C.jmethodID 52 mhideTextInput C.jmethodID 53 mpostFrameCallback C.jmethodID 54 mpostFrameCallbackOnMainThread C.jmethodID 55 } 56 57 var theJVM *C.JavaVM 58 59 var views = make(map[C.jlong]*window) 60 61 var mainWindow = newWindowRendezvous() 62 63 func jniGetMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID { 64 m := C.CString(method) 65 defer C.free(unsafe.Pointer(m)) 66 s := C.CString(sig) 67 defer C.free(unsafe.Pointer(s)) 68 return C.gio_jni_GetMethodID(env, class, m, s) 69 } 70 71 func jniGetStaticMethodID(env *C.JNIEnv, class C.jclass, method, sig string) C.jmethodID { 72 m := C.CString(method) 73 defer C.free(unsafe.Pointer(m)) 74 s := C.CString(sig) 75 defer C.free(unsafe.Pointer(s)) 76 return C.gio_jni_GetStaticMethodID(env, class, m, s) 77 } 78 79 //export runGoMain 80 func runGoMain(env *C.JNIEnv, class C.jclass, jdataDir C.jbyteArray) { 81 dirBytes := C.gio_jni_GetByteArrayElements(env, jdataDir) 82 if dirBytes == nil { 83 panic("runGoMain: GetByteArrayElements failed") 84 } 85 n := C.gio_jni_GetArrayLength(env, jdataDir) 86 dataDir := C.GoStringN((*C.char)(unsafe.Pointer(dirBytes)), n) 87 setDataDir(dataDir) 88 C.gio_jni_ReleaseByteArrayElements(env, jdataDir, dirBytes) 89 runMain() 90 } 91 92 //export setJVM 93 func setJVM(vm *C.JavaVM) { 94 theJVM = vm 95 } 96 97 //export onCreateView 98 func onCreateView(env *C.JNIEnv, class C.jclass, view C.jobject) C.jlong { 99 view = C.gio_jni_NewGlobalRef(env, view) 100 w := &window{ 101 view: view, 102 mgetDensity: jniGetMethodID(env, class, "getDensity", "()I"), 103 mgetFontScale: jniGetMethodID(env, class, "getFontScale", "()F"), 104 mshowTextInput: jniGetMethodID(env, class, "showTextInput", "()V"), 105 mhideTextInput: jniGetMethodID(env, class, "hideTextInput", "()V"), 106 mpostFrameCallback: jniGetMethodID(env, class, "postFrameCallback", "()V"), 107 mpostFrameCallbackOnMainThread: jniGetMethodID(env, class, "postFrameCallbackOnMainThread", "()V"), 108 } 109 wopts := <-mainWindow.out 110 w.Window = wopts.window 111 w.Window.setDriver(w) 112 handle := C.jlong(view) 113 views[handle] = w 114 w.loadConfig(env, class) 115 w.setStage(StagePaused) 116 return handle 117 } 118 119 //export onDestroyView 120 func onDestroyView(env *C.JNIEnv, class C.jclass, handle C.jlong) { 121 w := views[handle] 122 w.setDriver(nil) 123 delete(views, handle) 124 C.gio_jni_DeleteGlobalRef(env, w.view) 125 w.view = 0 126 } 127 128 //export onStopView 129 func onStopView(env *C.JNIEnv, class C.jclass, handle C.jlong) { 130 w := views[handle] 131 w.started = false 132 w.setStage(StagePaused) 133 } 134 135 //export onStartView 136 func onStartView(env *C.JNIEnv, class C.jclass, handle C.jlong) { 137 w := views[handle] 138 w.started = true 139 if w.aNativeWindow() != nil { 140 w.setVisible() 141 } 142 } 143 144 //export onSurfaceDestroyed 145 func onSurfaceDestroyed(env *C.JNIEnv, class C.jclass, handle C.jlong) { 146 w := views[handle] 147 w.mu.Lock() 148 w.win = nil 149 w.mu.Unlock() 150 w.setStage(StagePaused) 151 } 152 153 //export onSurfaceChanged 154 func onSurfaceChanged(env *C.JNIEnv, class C.jclass, handle C.jlong, surf C.jobject) { 155 w := views[handle] 156 w.mu.Lock() 157 w.win = C.ANativeWindow_fromSurface(env, surf) 158 w.mu.Unlock() 159 if w.started { 160 w.setVisible() 161 } 162 } 163 164 //export onLowMemory 165 func onLowMemory() { 166 runtime.GC() 167 debug.FreeOSMemory() 168 } 169 170 //export onConfigurationChanged 171 func onConfigurationChanged(env *C.JNIEnv, class C.jclass, view C.jlong) { 172 w := views[view] 173 w.loadConfig(env, class) 174 if w.stage >= StageRunning { 175 w.draw(true) 176 } 177 } 178 179 //export onFrameCallback 180 func onFrameCallback(env *C.JNIEnv, class C.jclass, view C.jlong, nanos C.jlong) { 181 w, exist := views[view] 182 if !exist { 183 return 184 } 185 if w.stage < StageRunning { 186 return 187 } 188 w.mu.Lock() 189 anim := w.animating 190 w.mu.Unlock() 191 if anim { 192 runInJVM(func(env *C.JNIEnv) { 193 C.gio_jni_CallVoidMethod(env, w.view, w.mpostFrameCallback) 194 }) 195 w.draw(false) 196 } 197 } 198 199 //export onBack 200 func onBack(env *C.JNIEnv, class C.jclass, view C.jlong) C.jboolean { 201 w := views[view] 202 ev := &CommandEvent{Type: CommandBack} 203 w.event(ev) 204 if ev.Cancel { 205 return C.JNI_TRUE 206 } 207 return C.JNI_FALSE 208 } 209 210 //export onFocusChange 211 func onFocusChange(env *C.JNIEnv, class C.jclass, view C.jlong, focus C.jboolean) { 212 w := views[view] 213 w.event(key.FocusEvent{Focus: focus == C.JNI_TRUE}) 214 } 215 216 //export onWindowInsets 217 func onWindowInsets(env *C.JNIEnv, class C.jclass, view C.jlong, top, right, bottom, left C.jint) { 218 w := views[view] 219 w.insets = Insets{ 220 Top: ui.Px(float32(top)), 221 Right: ui.Px(float32(right)), 222 Bottom: ui.Px(float32(bottom)), 223 Left: ui.Px(float32(left)), 224 } 225 if w.stage >= StageRunning { 226 w.draw(true) 227 } 228 } 229 230 func (w *window) setVisible() { 231 win := w.aNativeWindow() 232 width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win) 233 if width == 0 || height == 0 { 234 return 235 } 236 w.setStage(StageRunning) 237 w.draw(true) 238 } 239 240 func (w *window) setStage(stage Stage) { 241 if stage == w.stage { 242 return 243 } 244 w.stage = stage 245 w.event(StageEvent{stage}) 246 } 247 248 func (w *window) display() unsafe.Pointer { 249 return nil 250 } 251 252 func (w *window) nativeWindow(visID int) (unsafe.Pointer, int, int) { 253 win := w.aNativeWindow() 254 var width, height int 255 if win != nil { 256 if C.ANativeWindow_setBuffersGeometry(win, 0, 0, C.int32_t(visID)) != 0 { 257 panic(errors.New("ANativeWindow_setBuffersGeometry failed")) 258 } 259 w, h := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win) 260 width, height = int(w), int(h) 261 } 262 return unsafe.Pointer(win), width, height 263 } 264 265 func (w *window) aNativeWindow() *C.ANativeWindow { 266 w.mu.Lock() 267 defer w.mu.Unlock() 268 return w.win 269 } 270 271 func (w *window) loadConfig(env *C.JNIEnv, class C.jclass) { 272 dpi := int(C.gio_jni_CallIntMethod(env, w.view, w.mgetDensity)) 273 w.fontScale = float32(C.gio_jni_CallFloatMethod(env, w.view, w.mgetFontScale)) 274 switch dpi { 275 case C.ACONFIGURATION_DENSITY_NONE, 276 C.ACONFIGURATION_DENSITY_DEFAULT, 277 C.ACONFIGURATION_DENSITY_ANY: 278 // Assume standard density. 279 w.dpi = C.ACONFIGURATION_DENSITY_MEDIUM 280 default: 281 w.dpi = int(dpi) 282 } 283 } 284 285 func (w *window) setAnimating(anim bool) { 286 w.mu.Lock() 287 w.animating = anim 288 w.mu.Unlock() 289 if anim { 290 runInJVM(func(env *C.JNIEnv) { 291 C.gio_jni_CallVoidMethod(env, w.view, w.mpostFrameCallbackOnMainThread) 292 }) 293 } 294 } 295 296 func (w *window) draw(sync bool) { 297 win := w.aNativeWindow() 298 width, height := C.ANativeWindow_getWidth(win), C.ANativeWindow_getHeight(win) 299 if width == 0 || height == 0 { 300 return 301 } 302 ppdp := float32(w.dpi) * inchPrDp 303 w.event(UpdateEvent{ 304 Size: image.Point{ 305 X: int(width), 306 Y: int(height), 307 }, 308 Insets: w.insets, 309 Config: Config{ 310 pxPerDp: ppdp, 311 pxPerSp: w.fontScale * ppdp, 312 now: time.Now(), 313 }, 314 sync: sync, 315 }) 316 } 317 318 type keyMapper func(devId, keyCode C.int32_t) rune 319 320 func runInJVM(f func(env *C.JNIEnv)) { 321 runtime.LockOSThread() 322 defer runtime.UnlockOSThread() 323 var env *C.JNIEnv 324 var detach bool 325 if res := C.gio_jni_GetEnv(theJVM, &env, C.JNI_VERSION_1_6); res != C.JNI_OK { 326 if res != C.JNI_EDETACHED { 327 panic(fmt.Errorf("JNI GetEnv failed with error %d", res)) 328 } 329 if C.gio_jni_AttachCurrentThread(theJVM, &env, nil) != C.JNI_OK { 330 panic(errors.New("runInJVM: AttachCurrentThread failed")) 331 } 332 detach = true 333 } 334 335 if detach { 336 defer func() { 337 C.gio_jni_DetachCurrentThread(theJVM) 338 }() 339 } 340 f(env) 341 } 342 343 func convertKeyCode(code C.jint) (rune, bool) { 344 var n rune 345 switch code { 346 case C.AKEYCODE_DPAD_UP: 347 n = key.NameUpArrow 348 case C.AKEYCODE_DPAD_DOWN: 349 n = key.NameDownArrow 350 case C.AKEYCODE_DPAD_LEFT: 351 n = key.NameLeftArrow 352 case C.AKEYCODE_DPAD_RIGHT: 353 n = key.NameRightArrow 354 case C.AKEYCODE_FORWARD_DEL: 355 n = key.NameDeleteForward 356 case C.AKEYCODE_DEL: 357 n = key.NameDeleteBackward 358 default: 359 return 0, false 360 } 361 return n, true 362 } 363 364 //export onKeyEvent 365 func onKeyEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, keyCode, r C.jint, t C.jlong) { 366 w := views[handle] 367 if n, ok := convertKeyCode(keyCode); ok { 368 w.event(key.Event{Name: n}) 369 } 370 if r != 0 { 371 w.event(key.EditEvent{Text: string(rune(r))}) 372 } 373 } 374 375 //export onTouchEvent 376 func onTouchEvent(env *C.JNIEnv, class C.jclass, handle C.jlong, action, pointerID, tool C.jint, x, y C.jfloat, t C.jlong) { 377 w := views[handle] 378 var typ pointer.Type 379 switch action { 380 case C.AMOTION_EVENT_ACTION_DOWN, C.AMOTION_EVENT_ACTION_POINTER_DOWN: 381 typ = pointer.Press 382 case C.AMOTION_EVENT_ACTION_UP, C.AMOTION_EVENT_ACTION_POINTER_UP: 383 typ = pointer.Release 384 case C.AMOTION_EVENT_ACTION_CANCEL: 385 typ = pointer.Cancel 386 case C.AMOTION_EVENT_ACTION_MOVE: 387 typ = pointer.Move 388 default: 389 return 390 } 391 var src pointer.Source 392 switch tool { 393 case C.AMOTION_EVENT_TOOL_TYPE_FINGER: 394 src = pointer.Touch 395 case C.AMOTION_EVENT_TOOL_TYPE_MOUSE: 396 src = pointer.Mouse 397 default: 398 return 399 } 400 w.event(pointer.Event{ 401 Type: typ, 402 Source: src, 403 PointerID: pointer.ID(pointerID), 404 Time: time.Duration(t) * time.Millisecond, 405 Position: f32.Point{X: float32(x), Y: float32(y)}, 406 }) 407 } 408 409 func (w *window) showTextInput(show bool) { 410 if w.view == 0 { 411 return 412 } 413 runInJVM(func(env *C.JNIEnv) { 414 if show { 415 C.gio_jni_CallVoidMethod(env, w.view, w.mshowTextInput) 416 } else { 417 C.gio_jni_CallVoidMethod(env, w.view, w.mhideTextInput) 418 } 419 }) 420 } 421 422 func main() { 423 } 424 425 func createWindow(window *Window, opts *windowOptions) error { 426 mainWindow.in <- windowAndOptions{window, opts} 427 return <-mainWindow.errs 428 }