diff --git a/README.md b/README.md index 38f812e0..b699d508 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,60 @@ # flutter-pi -A light-weight Flutter Engine Embedder for Raspberry Pi that's using the broadcom APIs. Inspired by https://github.com/chinmaygarde/flutter_from_scratch. +A light-weight Flutter Engine Embedder for Raspberry Pi. Inspired by https://github.com/chinmaygarde/flutter_from_scratch. +Flutter-pi also runs without X11, so you don't need to boot into Raspbian Desktop & have X11 and LXDE load up; just boot into the command-line. -Currently supported are basic, pure-dart Apps & mouse input (no mouse cursor yet). -Not yet supported are Method & Platform-channels, touchscreen input; and probably a lot more. +Currently supported are basic, pure-dart Apps (not using any plugins), mouse input (no mouse cursor yet), touchscreen input, and the StandardMethodCodec method-channels (currently needs fixing). +Not yet supported are JSON method-channels. Generally, flutter-pi is not yet ready to be used as a base for your project. ## Running +This branch (feature-v3d-anholt) doesn't support the legacy GL driver anymore. You need to activate the anholt v3d driver in raspi-config. Go to raspi-config -> Advanced -> GL Driver -> and select fake-KMS. Full-KMS is a bit buggy and doesn't work with the Raspberry Pi 7" display (or generally, any DSI display). + +For some reason performance is much better when I gave the GPU only 16M RAM in fake-kms. I don't know why. + +Also, you need to tell flutter-pi which input device to use and whether it's a touchscreen or mouse. Input devices are typically located at `/dev/input/...`. Just run `evtest` (`sudo apt install evtest`) to find out which exact path you should use. Currently only one input device is supported by flutter-pi. + Run using ```bash -./flutter-pi /path/without/trailing/slash [flutter arguments...] +./flutter-pi [flutter-pi options...] /path/without/trailing/slash [flutter engine arguments...] ``` -where `/path/without/trailing/slash` is the path of the flutter asset bundle directory (i.e. the directory containing the kernel_blob.bin) + +`[flutter-pi options...]` are: +- `-t /path/to/device` where `/path/to/device` is a path to a touchscreen input device (typically `/dev/input/event0` or similiar) +- `-m /path/to/device` where `/path/to/device` is a path to a mouse input device (typically `/dev/input/mouse0` or `/dev/input/event0` or similiar) + +`/path/without/trailing/slash` is the path of the flutter asset bundle directory (i.e. the directory containing the kernel_blob.bin) of the flutter app you're trying to run. -The `[flutter arguments...]` will be passed as commandline arguments to the flutter engine. +`[flutter engine arguments...]` will be passed as commandline arguments to the flutter engine. You can find a list of commandline options for the flutter engine [Here](https://github.com/flutter/engine/blob/master/shell/common/switches.h); ## Building the asset bundle +You need a correctly installed flutter SDK. (i.e. the `flutter` tool must be in your PATH) + Example for flutter_gallery: (note that the flutter_gallery example doesn't work with flutter-pi, since it requires plugins) ```bash -cd ./flutter/examples/flutter_gallery -../../bin/flutter build bundle +cd flutter/examples/flutter_gallery +flutter build bundle ``` -After that `./flutter/examples/flutter_gallery/build/flutter_assets` would be a valid path to pass as an argument to flutter-pi. +After that `flutter/examples/flutter_gallery/build/flutter_assets` would be a valid path to pass as an argument to flutter-pi. + +## Compiling (on the Raspberry Pi) +You first need a `libflutter_engine.so` and `flutter_embedder.h`. [Here](https://medium.com/flutter/flutter-on-raspberry-pi-mostly-from-scratch-2824c5e7dcb1) +are some rough guidelines on how to build it. (Note: the icudtl.dat that is generated during the engine compilation needs to be on the RPi too, but it's not needed for compilation of flutter-pi) -## Compiling -You first need a valid `libflutter_engine.so`. [Here](https://medium.com/flutter/flutter-on-raspberry-pi-mostly-from-scratch-2824c5e7dcb1) -are some rough guidelines on how to build it. +You also need some dependencies; run `sudo apt install libgl1-mesa-dev libgles2-mesa-dev libegl-meso0 libdrm-dev libgbm-dev`. Compiling the embedder: ```bash mkdir out cc -D_GNU_SOURCE \ - -lrt -lbrcmGLESv2 -lflutter_engine -lpthread -ldl -lbcm_host -lvcos -lvchiq_arm -lm \ + `pkg-config --cflags --libs dri gbm libdrm glesv2 egl` -lrt -lflutter_engine -lpthread -ldl \ ./src/flutter-pi.c ./src/methodchannel.c -o ./out/flutter-pi ``` -## Cross-Compiling -You need a valid `libflutter_engine.so`, `flutter_embedder.h`, a valid raspberry pi sysroot including the /opt directory, and a valid toolchain targeting -arm-linux-gnueabihf. Then execute: -```bash -mkdir out -/path/to/cross_c_compiler \ - -D_GNU_SOURCE \ - --sysroot /path/to/sysroot \ - -I/path/to/sysroot/opt/vc/include \ - -I/directory/containing/flutter_embedder.h/ \ - -L/path/to/sysroot/opt/vc/lib \ - -L/directory/containing/libflutter_engine.so/ \ - -lrt -lbrcmEGL -lbrcmGLESv2 -lflutter_engine -lpthread -ldl -lbcm_host -lvcos -lvchiq_arm -lm \ - ./src/flutter-pi.c ./src/methodchannel.c -o ./out/flutter-rpi -``` +## Performance +Performance is actually better than I expected. With most of the apps inside the `flutter SDK -> examples -> catalog` directory I get smooth 50-60fps. + +## Touchscreen Bug +~~If you use the official 7 inch touchscreen, performance will feel much worse while dragging something. This seems to be some bug in the touchscreen driver. The embedder / userspace only gets around 25 touch events a second, meaning that while dragging something (like in tabbed_app_bar.dart), the position of the object being dragged is only updated 25 times a second. This results in the app looking like it runs at 25fps. The touchscreen could do up to 100 touch updates a second though.~~ + +[This has been fixed.](https://github.com/raspberrypi/linux/issues/3227) If you want to get the fix, you can run (rpi-update)[https://github.com/hexxeh/rpi-update], which will update your system to the newest version. + diff --git a/src/flutter-pi.c b/src/flutter-pi.c index bfb432cc..cb013796 100644 --- a/src/flutter-pi.c +++ b/src/flutter-pi.c @@ -4,20 +4,26 @@ #include #include #include +#include #include #include -#include -#include -#include -#include -#include #include #include #include - +#include #include #include #include +#include + +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -25,104 +31,381 @@ #include "methodchannel.h" -char* usage = "Flutter Raspberry Pi\n\nUsage:\n flutter-pi \n"; - -int argc; -const char* const *argv; -char asset_bundle_path[1024]; -char kernel_blob_path[1024]; -char executable_path[1024]; -char icu_data_path[1024]; -uint32_t width; -uint32_t height; -EGLDisplay display; -EGLConfig config; -EGLContext context; -EGLSurface surface; -DISPMANX_DISPLAY_HANDLE_T dispman_display; -DISPMANX_ELEMENT_HANDLE_T dispman_element; -EGL_DISPMANX_WINDOW_T native_window; -FlutterRendererConfig renderer_config; -FlutterProjectArgs project_args; -int mouse_filehandle; -double mouse_x = 0; -double mouse_y = 0; -uint8_t button = 0; +char* usage ="\ +Flutter for Raspberry Pi\n\n\ +Usage:\n\ + flutter-pi [options] \n\n\ +Options:\n\ + -m Path to the mouse device file. Typically /dev/input/mouseX or /dev/input/eventX\n\ + -t Path to the touchscreen device file. Typically /dev/input/touchscreenX or /dev/input/eventX\n\ +"; + +// width & height of the display in pixels +uint32_t width, height; + +// physical width & height of the display in millimeters +uint32_t width_mm, height_mm; +uint32_t refresh_rate; + +// this is the pixel ratio (used by flutter) for the Official Raspberry Pi 7inch display. +// if a HDMI screen is connected and being used by this application, the pixel ratio will be +// computed inside init_display. +// for DSI the pixel ratio can not be calculated, because there's no (general) way to query the physical +// size for DSI displays. +double pixel_ratio = 1.3671; + +struct { + char device[128]; + int fd; + uint32_t connector_id; + drmModeModeInfo *mode; + uint32_t crtc_id; + size_t crtc_index; + int waiting_for_flip; + struct gbm_bo *previous_bo; + drmEventContext evctx; +} drm = {0}; + +struct { + struct gbm_device *device; + struct gbm_surface *surface; + uint32_t format; + uint64_t modifier; +} gbm = {0}; + +struct { + EGLDisplay display; + EGLConfig config; + EGLContext context; + EGLSurface surface; + + bool modifiers_supported; + + EGLDisplay (*eglGetPlatformDisplayEXT)(EGLenum platform, void *native_display, const EGLint *attrib_list); + EGLSurface (*eglCreatePlatformWindowSurfaceEXT)(EGLDisplay dpy, EGLConfig config, void *native_window, const EGLint *attrib_list); + EGLSurface (*eglCreatePlatformPixmapSurfaceEXT)(EGLDisplay dpy, EGLConfig config, void *native_pixmap, const EGLint *attrib_list); +} egl = {0}; + +struct { + char asset_bundle_path[256]; + char kernel_blob_path[256]; + char executable_path[256]; + char icu_data_path[256]; + FlutterRendererConfig renderer_config; + FlutterProjectArgs args; + int engine_argc; + const char* const *engine_argv; + intptr_t next_vblank_baton; +} flutter = {0}; + +struct { + char device_path[128]; + int fd; + double x, y; + uint8_t button; + struct TouchscreenSlot ts_slots[10]; + struct TouchscreenSlot* ts_slot; + bool is_mouse; + bool is_touchscreen; +} input = {0}; pthread_t io_thread_id; pthread_t platform_thread_id; -struct LinkedTaskListElement task_list_head_sentinel - = {.next = NULL, .target_time = 0, .task = {.runner = NULL, .task = 0}}; +struct LinkedTaskListElement task_list_head_sentinel = { + .next = NULL, + .is_vblank_event = false, + .target_time = 0, + .task = {.runner = NULL, .task = 0} +}; pthread_mutex_t task_list_lock; bool should_notify_platform_thread = false; sigset_t sigusr1_set; + + /********************* * FLUTTER CALLBACKS * *********************/ -bool make_current(void* userdata) { - if (eglMakeCurrent(display, surface, surface, context) != EGL_TRUE) { +bool make_current(void* userdata) { + if (eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context) != EGL_TRUE) { fprintf(stderr, "Could not make the context current.\n"); return false; } return true; } -bool clear_current(void* userdata) { - if (eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) { +bool clear_current(void* userdata) { + if (eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT) != EGL_TRUE) { fprintf(stderr, "Could not clear the current context.\n"); return false; } return true; } -bool present(void* userdata) { - if (eglSwapBuffers(display, surface) != EGL_TRUE) { - fprintf(stderr, "Could not swap buffers to present the screen.\n"); +void page_flip_handler(int fd, unsigned int frame, unsigned int sec, unsigned int usec, void *data) { + int *waiting_for_flip = data; + *waiting_for_flip = 0; +} +void drm_fb_destroy_callback(struct gbm_bo *bo, void *data) { + struct drm_fb *fb = data; + + if (fb->fb_id) + drmModeRmFB(drm.fd, fb->fb_id); + + free(fb); +} +struct drm_fb* drm_fb_get_from_bo(struct gbm_bo *bo) { + uint32_t width, height, format, strides[4] = {0}, handles[4] = {0}, offsets[4] = {0}, flags = 0; + int ok = -1; + + struct drm_fb *fb = gbm_bo_get_user_data(bo); + + if (fb) return fb; + + fb = calloc(1, sizeof(struct drm_fb)); + fb->bo = bo; + + width = gbm_bo_get_width(bo); + height = gbm_bo_get_height(bo); + format = gbm_bo_get_format(bo); + + if (gbm_bo_get_modifier && gbm_bo_get_plane_count && gbm_bo_get_stride_for_plane && gbm_bo_get_offset) { + uint64_t modifiers[4] = {0}; + modifiers[0] = gbm_bo_get_modifier(bo); + const int num_planes = gbm_bo_get_plane_count(bo); + + for (int i = 0; i < num_planes; i++) { + strides[i] = gbm_bo_get_stride_for_plane(bo, i); + handles[i] = gbm_bo_get_handle(bo).u32; + offsets[i] = gbm_bo_get_offset(bo, i); + modifiers[i] = modifiers[0]; + } + + if (modifiers[0]) { + flags = DRM_MODE_FB_MODIFIERS; + } + + ok = drmModeAddFB2WithModifiers(drm.fd, width, height, format, handles, strides, offsets, modifiers, &fb->fb_id, flags); + } + + if (ok) { + if (flags) + fprintf(stderr, "Modifiers failed!\n"); + + memcpy(handles, (uint32_t [4]){gbm_bo_get_handle(bo).u32,0,0,0}, 16); + memcpy(strides, (uint32_t [4]){gbm_bo_get_stride(bo),0,0,0}, 16); + memset(offsets, 0, 16); + + ok = drmModeAddFB2(drm.fd, width, height, format, handles, strides, offsets, &fb->fb_id, 0); + } + + if (ok) { + fprintf(stderr, "failed to create fb: %s\n", strerror(errno)); + free(fb); + return NULL; + } + + gbm_bo_set_user_data(bo, fb, drm_fb_destroy_callback); + + return fb; +} +bool present(void* userdata) { + fd_set fds; + struct gbm_bo *next_bo; + struct drm_fb *fb; + int ok; + + eglSwapBuffers(egl.display, egl.surface); + next_bo = gbm_surface_lock_front_buffer(gbm.surface); + fb = drm_fb_get_from_bo(next_bo); + + /* wait for vsync, + ok = drmModePageFlip(drm.fd, drm.crtc_id, fb->fb_id, DRM_MODE_PAGE_FLIP_EVENT, &drm.waiting_for_flip); + if (ok) { + fprintf(stderr, "failed to queue page flip: %s\n", strerror(errno)); return false; } - GLenum error = glGetError(); - if (error != GL_NO_ERROR) { - printf("got gl error: %d\n", error); + uint64_t t1 = FlutterEngineGetCurrentTime(); + + while (drm.waiting_for_flip) { + FD_ZERO(&fds); + FD_SET(0, &fds); + FD_SET(drm.fd, &fds); + + ok = select(drm.fd+1, &fds, NULL, NULL, NULL); + if (ok < 0) { + fprintf(stderr, "select err: %s\n", strerror(errno)); + return false; + } else if (ok == 0) { + fprintf(stderr, "select timeout!\n"); + return false; + } else if (FD_ISSET(0, &fds)) { + fprintf(stderr, "user interrupted!\n"); + return false; + } + + drmHandleEvent(drm.fd, &drm.evctx); } + + uint64_t t2 = FlutterEngineGetCurrentTime(); + printf("waited %" PRIu64 " nanoseconds for page flip\n", t2-t1); + */ + + gbm_surface_release_buffer(gbm.surface, drm.previous_bo); + drm.previous_bo = next_bo; + + drm.waiting_for_flip = 1; return true; } -uint32_t fbo_callback(void* userdata) { +uint32_t fbo_callback(void* userdata) { return 0; } -void* proc_resolver(void* userdata, const char* name) { +void cut_word_from_string(char* string, char* word) { + size_t word_length = strlen(word); + char* word_in_str = strstr(string, word); + + // check if the given word is surrounded by spaces in the string + if (word_in_str + && ((word_in_str == string) || (word_in_str[-1] == ' ')) + && ((word_in_str[word_length] == 0) || (word_in_str[word_length] == ' ')) + ) { + if (word_in_str[word_length] == ' ') word_length++; + + int i = 0; + do { + word_in_str[i] = word_in_str[i+word_length]; + } while (word_in_str[i++ + word_length] != 0); + } +} +const GLubyte* hacked_glGetString(GLenum name) { + if (name == GL_EXTENSIONS) { + static GLubyte* extensions; + + if (extensions == NULL) { + GLubyte* orig_extensions = glGetString(GL_EXTENSIONS); + size_t len_orig_extensions = strlen(orig_extensions); + + extensions = malloc(len_orig_extensions+1); + strcpy(extensions, orig_extensions); + + /* + * working (apparently) + */ + //cut_word_from_string(extensions, "GL_EXT_blend_minmax"); + //cut_word_from_string(extensions, "GL_EXT_multi_draw_arrays"); + //cut_word_from_string(extensions, "GL_EXT_texture_format_BGRA8888"); + //cut_word_from_string(extensions, "GL_OES_compressed_ETC1_RGB8_texture"); + //cut_word_from_string(extensions, "GL_OES_depth24"); + //cut_word_from_string(extensions, "GL_OES_texture_npot"); + //cut_word_from_string(extensions, "GL_OES_vertex_half_float"); + //cut_word_from_string(extensions, "GL_OES_EGL_image"); + //cut_word_from_string(extensions, "GL_OES_depth_texture"); + //cut_word_from_string(extensions, "GL_AMD_performance_monitor"); + //cut_word_from_string(extensions, "GL_OES_EGL_image_external"); + //cut_word_from_string(extensions, "GL_EXT_occlusion_query_boolean"); + //cut_word_from_string(extensions, "GL_KHR_texture_compression_astc_ldr"); + //cut_word_from_string(extensions, "GL_EXT_compressed_ETC1_RGB8_sub_texture"); + //cut_word_from_string(extensions, "GL_EXT_draw_elements_base_vertex"); + //cut_word_from_string(extensions, "GL_EXT_texture_border_clamp"); + //cut_word_from_string(extensions, "GL_OES_draw_elements_base_vertex"); + //cut_word_from_string(extensions, "GL_OES_texture_border_clamp"); + //cut_word_from_string(extensions, "GL_KHR_texture_compression_astc_sliced_3d"); + //cut_word_from_string(extensions, "GL_MESA_tile_raster_order"); + + /* + * should be working, but isn't + */ + cut_word_from_string(extensions, "GL_EXT_map_buffer_range"); + + /* + * definitely broken + */ + cut_word_from_string(extensions, "GL_OES_element_index_uint"); + cut_word_from_string(extensions, "GL_OES_fbo_render_mipmap"); + cut_word_from_string(extensions, "GL_OES_mapbuffer"); + cut_word_from_string(extensions, "GL_OES_rgb8_rgba8"); + cut_word_from_string(extensions, "GL_OES_stencil8"); + cut_word_from_string(extensions, "GL_OES_texture_3D"); + cut_word_from_string(extensions, "GL_OES_packed_depth_stencil"); + cut_word_from_string(extensions, "GL_OES_get_program_binary"); + cut_word_from_string(extensions, "GL_APPLE_texture_max_level"); + cut_word_from_string(extensions, "GL_EXT_discard_framebuffer"); + cut_word_from_string(extensions, "GL_EXT_read_format_bgra"); + cut_word_from_string(extensions, "GL_EXT_frag_depth"); + cut_word_from_string(extensions, "GL_NV_fbo_color_attachments"); + cut_word_from_string(extensions, "GL_OES_EGL_sync"); + cut_word_from_string(extensions, "GL_OES_vertex_array_object"); + cut_word_from_string(extensions, "GL_EXT_unpack_subimage"); + cut_word_from_string(extensions, "GL_NV_draw_buffers"); + cut_word_from_string(extensions, "GL_NV_read_buffer"); + cut_word_from_string(extensions, "GL_NV_read_depth"); + cut_word_from_string(extensions, "GL_NV_read_depth_stencil"); + cut_word_from_string(extensions, "GL_NV_read_stencil"); + cut_word_from_string(extensions, "GL_EXT_draw_buffers"); + cut_word_from_string(extensions, "GL_KHR_debug"); + cut_word_from_string(extensions, "GL_OES_required_internalformat"); + cut_word_from_string(extensions, "GL_OES_surfaceless_context"); + cut_word_from_string(extensions, "GL_EXT_separate_shader_objects"); + cut_word_from_string(extensions, "GL_KHR_context_flush_control"); + cut_word_from_string(extensions, "GL_KHR_no_error"); + cut_word_from_string(extensions, "GL_KHR_parallel_shader_compile"); + } + + return extensions; + } else { + return glGetString(name); + } +} +void* proc_resolver(void* userdata, const char* name) { if (name == NULL) return NULL; - - printf("calling proc_resolver with %s\n", name); + + /* + * The mesa v3d driver reports some OpenGL ES extensions as supported and working + * even though they aren't. hacked_glGetString is a workaround for this, which will + * cut out the non-working extensions from the list of supported extensions. + */ + if (strcmp(name, "glGetString") == 0) { + return hacked_glGetString; + } void* address; - if ((address = dlsym(RTLD_DEFAULT, name))) { + if ((address = dlsym(RTLD_DEFAULT, name)) || (address = eglGetProcAddress(name))) { return address; } + printf("could not resolve symbol %s\n", name); + return NULL; } -void on_platform_message(const FlutterPlatformMessage* message, void* userdata) { - struct MethodCall methodcall; +void on_platform_message(const FlutterPlatformMessage* message, void* userdata) { + struct MethodCall* methodcall; + if (!MethodChannel_decode(message->message_size, (uint8_t*) (message->message), &methodcall)) { fprintf(stderr, "Decoding method call failed\n"); return; } - printf("MethodCall: method name: %s argument type: %d\n", methodcall.method, methodcall.argument.type); - if (strcmp(methodcall.method, "counter") == 0) { - printf("method \"counter\" was called with argument %d\n", methodcall.argument.value.int_value); + printf("MethodCall: method name: %s argument type: %d\n", methodcall->method, methodcall->argument.type); + + if (strcmp(methodcall->method, "counter") == 0) { + printf("method \"counter\" was called with argument %d\n", methodcall->argument.int_value); } MethodChannel_freeMethodCall(&methodcall); } +void vsync_callback(void* userdata, intptr_t baton) { + // not yet implemented + fprintf(stderr, "flutter vsync callback not yet implemented\n"); +} + + /************************ * PLATFORM TASK-RUNNER * ************************/ -void handle_signal(int _) {} +void handle_sigusr1(int _) {} bool init_message_loop() { platform_thread_id = pthread_self(); @@ -134,8 +417,7 @@ bool init_message_loop() { sigemptyset(&sigusr1_set); sigaddset(&sigusr1_set, SIGUSR1); - - sigaction(SIGUSR1, &(struct sigaction) {.sa_handler = &handle_signal}, NULL); + sigaction(SIGUSR1, &(struct sigaction) {.sa_handler = &handle_sigusr1}, NULL); pthread_sigmask(SIG_UNBLOCK, &sigusr1_set, NULL); return true; @@ -147,8 +429,8 @@ bool message_loop(void) { pthread_mutex_lock(&task_list_lock); if (task_list_head_sentinel.next == NULL) { pthread_mutex_unlock(&task_list_lock); - sigwaitinfo(&sigusr1_set, NULL); + continue; } else { uint64_t target_time = task_list_head_sentinel.next->target_time; uint64_t current_time = FlutterEngineGetCurrentTime(); @@ -157,28 +439,28 @@ bool message_loop(void) { uint64_t diff = target_time - current_time; struct timespec target_timespec = { - .tv_sec = (uint64_t) (diff / 1000000000l), - .tv_nsec = (uint64_t) (diff % 1000000000l) + .tv_sec = (uint64_t) (diff / 1000000000ull), + .tv_nsec = (uint64_t) (diff % 1000000000ull) }; pthread_mutex_unlock(&task_list_lock); - - int result = sigtimedwait(&sigusr1_set, NULL, &target_timespec); - if (result == EINTR) continue; - } else { - pthread_mutex_unlock(&task_list_lock); + sigtimedwait(&sigusr1_set, NULL, &target_timespec); + continue; } } - pthread_mutex_lock(&task_list_lock); FlutterTask task = task_list_head_sentinel.next->task; + bool is_vblank_event = task_list_head_sentinel.next->is_vblank_event; + drmVBlankReply vbl = task_list_head_sentinel.next->vbl; struct LinkedTaskListElement* new_first = task_list_head_sentinel.next->next; free(task_list_head_sentinel.next); task_list_head_sentinel.next = new_first; pthread_mutex_unlock(&task_list_lock); - - if (FlutterEngineRunTask(engine, &task) != kSuccess) { + + if (is_vblank_event) { + + } else if (FlutterEngineRunTask(engine, &task) != kSuccess) { fprintf(stderr, "Error running platform task\n"); return false; }; @@ -189,6 +471,7 @@ bool message_loop(void) { void post_platform_task(FlutterTask task, uint64_t target_time, void* userdata) { struct LinkedTaskListElement* to_insert = malloc(sizeof(struct LinkedTaskListElement)); to_insert->next = NULL; + to_insert->is_vblank_event = false; to_insert->task = task; to_insert->target_time = target_time; @@ -210,188 +493,369 @@ bool runs_platform_tasks_on_current_thread(void* userdata) { } + /****************** * INITIALIZATION * ******************/ bool setup_paths(void) { #define PATH_EXISTS(path) (access((path),R_OK)==0) - if (!PATH_EXISTS(asset_bundle_path)) { - fprintf(stderr, "Asset Bundle Directory \"%s\" does not exist\n", asset_bundle_path); + if (!PATH_EXISTS(flutter.asset_bundle_path)) { + fprintf(stderr, "Asset Bundle Directory \"%s\" does not exist\n", flutter.asset_bundle_path); return false; } - snprintf(kernel_blob_path, 1024, "%s/kernel_blob.bin", asset_bundle_path); - if (!PATH_EXISTS(kernel_blob_path)) { + snprintf(flutter.kernel_blob_path, sizeof(flutter.kernel_blob_path), "%s/kernel_blob.bin", flutter.asset_bundle_path); + if (!PATH_EXISTS(flutter.kernel_blob_path)) { fprintf(stderr, "Kernel blob does not exist inside Asset Bundle Directory.\n"); return false; } - #ifdef ICUDTL_IN_EXECUTABLE_DIR - char _link_path[256]; - snprintf(_link_path, 256, "/proc/%d/exe", getpid()); - size_t size = readlink(_link_path, executable_path, 1023); - if (size <= 0) sprintf(executable_path, ""); - - char* lastSlash = strrchr(executable_path, ("/")[0]); - if (lastSlash == NULL) sprintf(icu_data_path, "/icudtl.dat"); - else snprintf(icu_data_path, 1024, "%.*s/icudtl.dat", (int) (lastSlash - executable_path), executable_path); - #else - snprintf(icu_data_path, 1024, "/usr/lib/icudtl.dat"); - #endif + snprintf(flutter.icu_data_path, sizeof(flutter.icu_data_path), "/usr/lib/icudtl.dat"); - if (!PATH_EXISTS(icu_data_path)) { - fprintf(stderr, "ICU Data file not find at %s.\n", icu_data_path); + if (!PATH_EXISTS(flutter.icu_data_path)) { + fprintf(stderr, "ICU Data file not find at %s.\n", flutter.icu_data_path); return false; } + snprintf(drm.device, sizeof(drm.device), "/dev/dri/card0"); + return true; #undef PATH_EXISTS } bool init_display(void) { - printf("Initializing bcm_host...\n"); - bcm_host_init(); + /********************** + * DRM INITIALIZATION * + **********************/ + + drmModeRes *resources; + drmModeConnector *connector; + drmModeEncoder *encoder; + int i, ok, area; - // setup the EGL Display - printf("Getting the EGL display...\n"); - display = eglGetDisplay(EGL_DEFAULT_DISPLAY); - if (display == EGL_NO_DISPLAY) { - fprintf(stderr, "Could not get the EGL display.\n"); + + printf("Opening DRM device...\n"); + drm.fd = open(drm.device, O_RDWR); + if (drm.fd < 0) { + fprintf(stderr, "Could not open DRM device\n"); return false; } - - printf("Initializing EGL...\n"); - if (eglInitialize(display, NULL, NULL) != EGL_TRUE) { - fprintf(stderr, "Could not initialize the EGL display.\n"); + + + printf("Getting DRM resources...\n"); + resources = drmModeGetResources(drm.fd); + if (resources == NULL) { + if (errno == EOPNOTSUPP) fprintf(stderr, "%s doesn't look like a modeset device\n", drm.device); + else fprintf(stderr, "drmModeGetResources failed: %s\n", strerror(errno)); + return false; } - - // choose an EGL config - EGLConfig config = {0}; - EGLint num_config = 0; - EGLint attribute_list[] = { - EGL_RED_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_BLUE_SIZE, 8, - EGL_ALPHA_SIZE, 8, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_NONE - }; - - printf("Choosing an EGL config...\n"); - if (eglChooseConfig(display, attribute_list, &config, 1, &num_config) != EGL_TRUE) { - fprintf(stderr, "Could not choose an EGL config.\n"); + + + printf("Finding a connected connector...\n"); + for (i = 0; i < resources->count_connectors; i++) { + connector = drmModeGetConnector(drm.fd, resources->connectors[i]); + if (connector->connection == DRM_MODE_CONNECTED) { + width_mm = connector->mmWidth; + height_mm = connector->mmHeight; + break; + } + drmModeFreeConnector(connector); + connector = NULL; + } + if (!connector) { + fprintf(stderr, "could not find a connected connector!\n"); return false; } + + printf("Choosing DRM mode...\n"); + for (i = 0, area = 0; i < connector->count_modes; i++) { + drmModeModeInfo *current_mode = &connector->modes[i]; + + if (current_mode->type & DRM_MODE_TYPE_PREFERRED) { + drm.mode = current_mode; + width = drm.mode->hdisplay; + height = drm.mode->vdisplay; + refresh_rate = drm.mode->vrefresh; + + if (width_mm) pixel_ratio = (10.0 * width) / (width_mm * 38.0); + + break; + } + + int current_area = current_mode->hdisplay * current_mode->vdisplay; + if (current_area > area) { + drm.mode = current_mode; + area = current_area; + } + } + if (!drm.mode) { + fprintf(stderr, "could not find a suitable DRM mode!\n"); + return false; + } + + printf("Display properties:\n %u x %u, %uHz\n %umm x %umm\n pixel_ratio = %f\n", width, height, refresh_rate, width_mm, height_mm, pixel_ratio); + + printf("Finding DRM encoder...\n"); + for (i = 0; i < resources->count_encoders; i++) { + encoder = drmModeGetEncoder(drm.fd, resources->encoders[i]); + if (encoder->encoder_id == connector->encoder_id) + break; + drmModeFreeEncoder(encoder); + encoder = NULL; + } - // create the EGL context - EGLint context_attributes[] = { - EGL_CONTEXT_CLIENT_VERSION, - 2, - EGL_NONE - }; - - printf("Creating the EGL context...\n"); - context = eglCreateContext(display, config, EGL_NO_CONTEXT, context_attributes); - if (context == EGL_NO_CONTEXT) { - fprintf(stderr, "Could not create the EGL context.\n"); + if (encoder) { + drm.crtc_id = encoder->crtc_id; + } else { + fprintf(stderr, "could not find a suitable crtc!\n"); return false; } - // query current display size - printf("Querying the display size...\n"); - if (graphics_get_display_size(0, &width, &height) < 0) { - fprintf(stderr, "Could not query the display size.\n"); + for (i = 0; i < resources->count_crtcs; i++) { + if (resources->crtcs[i] == drm.crtc_id) { + drm.crtc_index = i; + break; + } + } + + drmModeFreeResources(resources); + + drm.connector_id = connector->connector_id; + + + + /********************** + * GBM INITIALIZATION * + **********************/ + printf("Creating GBM device\n"); + gbm.device = gbm_create_device(drm.fd); + gbm.format = DRM_FORMAT_XRGB8888; + gbm.surface = NULL; + + if (gbm_surface_create_with_modifiers) { + gbm.surface = gbm_surface_create_with_modifiers(gbm.device, width, height, gbm.format, &gbm.modifier, 1); + } + + if (!gbm.surface) { + if (gbm.modifier != 0) { + fprintf(stderr, "GBM Surface creation modifiers requested but not supported by GBM\n"); + return false; + } + gbm.surface = gbm_surface_create(gbm.device, width, height, gbm.format, GBM_BO_USE_SCANOUT | GBM_BO_USE_RENDERING); + } + + if (!gbm.surface) { + fprintf(stderr, "failed to create GBM surface\n"); return false; } - - // setup dispman display - printf("Opening the dispmanx display...\n"); - dispman_display = vc_dispmanx_display_open(0); - - printf("Setting up the dispmanx display...\n"); - DISPMANX_UPDATE_HANDLE_T update = vc_dispmanx_update_start(0); - const VC_RECT_T dest_rect = { - .x = 0, .y = 0, - .width = width, .height = height, + + /********************** + * EGL INITIALIZATION * + **********************/ + EGLint major, minor; + + static const EGLint context_attribs[] = { + EGL_CONTEXT_CLIENT_VERSION, 2, + EGL_NONE }; - const VC_RECT_T src_rect = { - .x = 0, .y = 0, - .width = width << 16, .height = height << 16, + + const EGLint config_attribs[] = { + EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL_NONE }; + + const char *egl_exts_client, *egl_exts_dpy, *gl_exts; + + printf("Querying EGL client extensions...\n"); + egl_exts_client = eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS); + + egl.eglGetPlatformDisplayEXT = (void*) eglGetProcAddress("eglGetPlatformDisplayEXT"); + printf("Getting EGL display for GBM device...\n"); + if (egl.eglGetPlatformDisplayEXT) egl.display = egl.eglGetPlatformDisplayEXT(EGL_PLATFORM_GBM_KHR, gbm.device, NULL); + else egl.display = eglGetDisplay((void*) gbm.device); - dispman_element = vc_dispmanx_element_add( - update, - dispman_display, - 0, - &dest_rect, - 0, - &src_rect, - DISPMANX_PROTECTION_NONE, - 0, - 0, - DISPMANX_NO_ROTATE - ); - - vc_dispmanx_update_submit_sync(update); - - native_window.element = dispman_element; - native_window.width = width; - native_window.height = height; - - - // Create EGL window surface - printf("Creating the EGL window surface...\n"); - surface = eglCreateWindowSurface(display, config, &native_window, NULL); - if (surface == EGL_NO_SURFACE) { - fprintf(stderr, "Could not create the EGL Surface.\n"); + if (!egl.display) { + fprintf(stderr, "Couldn't get EGL display\n"); return false; } - - return true; -} -void destroy_display(void) { - if (surface != EGL_NO_SURFACE) { - eglDestroySurface(display, surface); - surface = EGL_NO_SURFACE; + + printf("Initializing EGL...\n"); + if (!eglInitialize(egl.display, &major, &minor)) { + fprintf(stderr, "failed to initialize EGL\n"); + return false; } - - vc_dispmanx_display_close(dispman_display); - - if (context != EGL_NO_CONTEXT) { - eglDestroyContext(display, context); - context = EGL_NO_CONTEXT; + + printf("Querying EGL display extensions...\n"); + egl_exts_dpy = eglQueryString(egl.display, EGL_EXTENSIONS); + egl.modifiers_supported = strstr(egl_exts_dpy, "EGL_EXT_image_dma_buf_import_modifiers") != NULL; + + + printf("Using display %d with EGL version %d.%d\n", egl.display, major, minor); + printf("===================================\n"); + printf("EGL information:\n"); + printf(" version: %s\n", eglQueryString(egl.display, EGL_VERSION)); + printf(" vendor: \"%s\"\n", eglQueryString(egl.display, EGL_VENDOR)); + printf(" client extensions: \"%s\"\n", egl_exts_client); + printf(" display extensions: \"%s\"\n", egl_exts_dpy); + printf("===================================\n"); + + + printf("Binding OpenGL ES API...\n"); + if (!eglBindAPI(EGL_OPENGL_ES_API)) { + fprintf(stderr, "failed to bind OpenGL ES API\n"); + return false; } + + + printf("Choosing EGL config...\n"); + EGLint count = 0, matched = 0; + EGLConfig *configs; + bool _found_matching_config = false; - if (display != EGL_NO_DISPLAY) { - eglTerminate(display); - display = EGL_NO_DISPLAY; + if (!eglGetConfigs(egl.display, NULL, 0, &count) || count < 1) { + fprintf(stderr, "No EGL configs to choose from.\n"); + return false; } - - bcm_host_deinit(); + + configs = malloc(count * sizeof(EGLConfig)); + if (!configs) return false; + + printf("Finding EGL configs with appropriate attributes...\n"); + if (!eglChooseConfig(egl.display, config_attribs, configs, count, &matched) || !matched) { + fprintf(stderr, "No EGL configs with appropriate attributes.\n"); + free(configs); + return false; + } + + if (!gbm.format) { + _found_matching_config = true; + } else { + for (int i = 0; i < count; i++) { + EGLint id; + if (!eglGetConfigAttrib(egl.display, configs[i], EGL_NATIVE_VISUAL_ID, &id)) continue; + + if (id == gbm.format) { + egl.config = configs[i]; + _found_matching_config = true; + break; + } + } + } + free(configs); + + if (!_found_matching_config) { + fprintf(stderr, "Could not find context with appropriate attributes and matching native visual ID.\n"); + return false; + } + + + printf("Creating EGL context...\n"); + egl.context = eglCreateContext(egl.display, egl.config, EGL_NO_CONTEXT, context_attribs); + if (egl.context == NULL) { + fprintf(stderr, "failed to create EGL context\n"); + return false; + } + + + printf("Creating EGL window surface...\n"); + egl.surface = eglCreateWindowSurface(egl.display, egl.config, (EGLNativeWindowType) gbm.surface, NULL); + if (egl.surface == EGL_NO_SURFACE) { + fprintf(stderr, "failed to create EGL window surface\n"); + return false; + } + + if (!eglMakeCurrent(egl.display, egl.surface, egl.surface, egl.context)) { + fprintf(stderr, "Could not make EGL context current to get OpenGL information\n"); + return false; + } + + gl_exts = (char*) glGetString(GL_EXTENSIONS); + printf("===================================\n"); + printf("OpenGL ES 2.x information:\n"); + printf(" version: \"%s\"\n", glGetString(GL_VERSION)); + printf(" shading language version: \"%s\"\n", glGetString(GL_SHADING_LANGUAGE_VERSION)); + printf(" vendor: \"%s\"\n", glGetString(GL_VENDOR)); + printf(" renderer: \"%s\"\n", glGetString(GL_RENDERER)); + printf(" extensions: \"%s\"\n", gl_exts); + printf("===================================\n"); + + + drm.evctx.version = 2; + drm.evctx.page_flip_handler = page_flip_handler; + //drm.evctx.vblank_handler = vblank_handler; + + printf("Swapping buffers...\n"); + eglSwapBuffers(egl.display, egl.surface); + + printf("Locking front buffer...\n"); + drm.previous_bo = gbm_surface_lock_front_buffer(gbm.surface); + + + printf("getting new framebuffer for BO...\n"); + struct drm_fb *fb = drm_fb_get_from_bo(drm.previous_bo); + if (!fb) { + fprintf(stderr, "failed to get a new framebuffer BO\n"); + return false; + } + + + printf("Setting CRTC...\n"); + ok = drmModeSetCrtc(drm.fd, drm.crtc_id, fb->fb_id, 0, 0, &drm.connector_id, 1, drm.mode); + if (ok) { + fprintf(stderr, "failed to set mode: %s\n", strerror(errno)); + return false; + } + + drm.waiting_for_flip = 1; + + printf("Clearing current context...\n"); + if (!eglMakeCurrent(egl.display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + fprintf(stderr, "Could not clear EGL context\n"); + return false; + } + + printf("finished display setup!\n"); + + return true; +} +void destroy_display(void) { + fprintf(stderr, "Deinitializing display not yet implemented\n"); } bool init_application(void) { // configure flutter rendering - renderer_config.type = kOpenGL; - renderer_config.open_gl.struct_size = sizeof(renderer_config.open_gl); - renderer_config.open_gl.make_current = make_current; - renderer_config.open_gl.clear_current = clear_current; - renderer_config.open_gl.present = present; - renderer_config.open_gl.fbo_callback = fbo_callback; - renderer_config.open_gl.gl_proc_resolver= proc_resolver; + flutter.renderer_config.type = kOpenGL; + flutter.renderer_config.open_gl.struct_size = sizeof(flutter.renderer_config.open_gl); + flutter.renderer_config.open_gl.make_current = make_current; + flutter.renderer_config.open_gl.clear_current = clear_current; + flutter.renderer_config.open_gl.present = present; + flutter.renderer_config.open_gl.fbo_callback = fbo_callback; + flutter.renderer_config.open_gl.gl_proc_resolver= proc_resolver; + + for (int i=0; itype == EV_REL) { + if (ev->code == REL_X) { // mouse moved in the x-direction + input.x += ev->value; + phase = input.button ? kMove : kHover; + } else if (ev->code == REL_Y) { // mouse moved in the y-direction + input.y += ev->value; + phase = input.button ? kMove : kHover; + } + } else if (ev->type == EV_ABS) { + if (ev->code == ABS_X) { + input.x = ev->value; + phase = input.button ? kMove : kHover; + } else if (ev->code == ABS_Y) { + input.y = ev->value; + phase = input.button ? kMove : kHover; + } + } else if ((ev->type == EV_KEY) && ((ev->code == BTN_LEFT) || (ev->code == BTN_RIGHT))) { + // either the left or the right mouse button was pressed + // the 1st bit in "button" is set when BTN_LEFT is down. the 2nd bit when BTN_RIGHT is down. + uint8_t mask = ev->code == BTN_LEFT ? 1 : 2; + if (ev->value == 1) input.button |= mask; + else input.button &= ~mask; + + phase = ev->value == 1 ? kDown : kUp; } - } else if ((event[i].type == EV_KEY) && ((event[i].code == BTN_LEFT) || (event[i].code == BTN_RIGHT))) { - // either the left or the right mouse button was pressed - // the 1st bit in "button" is set when BTN_LEFT is down. the 2nd bit when BTN_RIGHT is down. - uint8_t mask = event[i].code == BTN_LEFT ? 1 : 2; - if (event[i].value == 1) button |= mask; - else button &= ~mask; - phase = event[i].value == 1 ? kDown : kUp; + if (phase != kCancel) { + // if something changed, send the pointer event to flutter + ok = FlutterEngineSendPointerEvent( + engine, + & (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .timestamp = (size_t) (ev->time.tv_sec * 1000000ul) + ev->time.tv_usec, + .phase=phase, .x=input.x, .y=input.y, + .signal_kind = kFlutterPointerSignalKindNone + }, + 1 + ) == kSuccess; + if (!ok) return false; + } } - - if (phase != kCancel) { - // if something changed, send the pointer event to flutter - ok = FlutterEngineSendPointerEvent( - engine, - & (FlutterPointerEvent) { - .struct_size = sizeof(FlutterPointerEvent), - .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), - .phase=phase, .x=mouse_x, .y=mouse_y, - .signal_kind = kFlutterPointerSignalKindNone - }, - 1 - ) == kSuccess; - if (!ok) return false; + + printf("mouse position: %f, %f\n", input.x, input.y); + } + } else if (input.is_touchscreen) { + for (int j = 0; j<10; j++) { + printf("Sending kAdd %d to Flutter Engine\n", j); + input.ts_slots[j].id = -1; + ok = FlutterEngineSendPointerEvent( + engine, + & (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = kAdd, + .timestamp = (size_t) (FlutterEngineGetCurrentTime()*1000), + .device = j, + .x = 0, + .y = 0, + .signal_kind = kFlutterPointerSignalKindNone + }, + 1 + ) == kSuccess; + if (!ok) { + fprintf(stderr, "Error sending Pointer message to flutter engine\n"); + return false; } } - printf("mouse position: %f, %f\n", mouse_x, mouse_y); + input.ts_slot = &(input.ts_slots[0]); + while (1) { + int rd = read(input.fd, &event, sizeof(struct input_event)*64); + if (rd < (int) sizeof(struct input_event)) { + perror("error reading from input device"); + return false; + } + + n_pointerevents = 0; + for (int i = 0; i < rd / sizeof(struct input_event); i++) { + ev = &(event[i]); + + if (ev->type == EV_ABS) { + if (ev->code == ABS_MT_SLOT) { + input.ts_slot = &(input.ts_slots[ev->value]); + } else if (ev->code == ABS_MT_TRACKING_ID) { + if (input.ts_slot->id == -1) { + input.ts_slot->id = ev->value; + input.ts_slot->phase = kDown; + } else if (ev->value == -1) { + input.ts_slot->id = ev->value; + input.ts_slot->phase = kUp; + } + } else if (ev->code == ABS_MT_POSITION_X) { + input.ts_slot->x = ev->value; + if (input.ts_slot->phase == kCancel) input.ts_slot->phase = kMove; + } else if (ev->code == ABS_MT_POSITION_Y) { + input.ts_slot->y = ev->value; + if (input.ts_slot->phase == kCancel) input.ts_slot->phase = kMove; + } + } else if ((ev->type == EV_SYN) && (ev->code == SYN_REPORT)) { + for (int j = 0; j < 10; j++) { + if (input.ts_slots[j].phase != kCancel) { + pointer_event[n_pointerevents++] = (FlutterPointerEvent) { + .struct_size = sizeof(FlutterPointerEvent), + .phase = input.ts_slots[j].phase, + .timestamp = (size_t) (ev->time.tv_sec * 1000000ul) + ev->time.tv_usec, + .device = j, + .x = input.ts_slots[j].x, + .y = input.ts_slots[j].y, + .signal_kind = kFlutterPointerSignalKindNone + }; + input.ts_slots[j].phase = kCancel; + } + } + } + } + + ok = FlutterEngineSendPointerEvent( + engine, + pointer_event, + n_pointerevents + ) == kSuccess; + + if (!ok) { + fprintf(stderr, "Error sending pointer events to flutter\n"); + return false; + } + } } return NULL; @@ -535,16 +1101,64 @@ bool run_io_thread(void) { } -int main(int argc, const char *const * argv) { - if (argc <= 1) { - fprintf(stderr, "Invalid Arguments\n"); - fprintf(stdout, "%s", usage); - return EXIT_FAILURE; + +bool parse_cmd_args(int argc, char **argv) { + int opt; + int index = 0; + + while ((opt = getopt(argc, (char *const *) argv, "+m:t:")) != -1) { + index++; + switch(opt) { + case 'm': + printf("Using mouse input from mouse %s\n", optarg); + snprintf(input.device_path, sizeof(input.device_path), "%s", optarg); + input.is_mouse = true; + input.is_touchscreen = false; + + index++; + break; + case 't': + printf("Using touchscreen input from %s\n", optarg); + snprintf(input.device_path, sizeof(input.device_path), "%s", optarg); + input.is_mouse = false; + input.is_touchscreen = true; + + index++; + break; + default: + fprintf(stderr, "Unknown Option: %c\n%s", (char) optopt, usage); + return false; + } + } + + if (strlen(input.device_path) == 0) { + fprintf(stderr, "At least one of -t or -r has to be given\n%s", usage); + return false; } + + if (optind >= argc) { + fprintf(stderr, "Expected Asset bundle path argument after options\n%s", usage); + return false; + } + + snprintf(flutter.asset_bundle_path, sizeof(flutter.asset_bundle_path), "%s", argv[optind]); + printf("Asset bundle path: %s\n", flutter.asset_bundle_path); + + argv[optind] = argv[0]; + flutter.engine_argc = argc-optind; + flutter.engine_argv = &(argv[optind]); + + for (int i=0; i +#include +#include #include #include +#define EGL_PLATFORM_GBM_KHR 0x31D7 + struct LinkedTaskListElement { struct LinkedTaskListElement* next; - FlutterTask task; + bool is_vblank_event; + union { + FlutterTask task; + drmVBlankReply vbl; + }; uint64_t target_time; }; +struct TouchscreenSlot { + int id; + int x; + int y; + FlutterPointerPhase phase; +}; + +struct drm_fb { + struct gbm_bo *bo; + uint32_t fb_id; +}; + FlutterEngine engine; #endif \ No newline at end of file diff --git a/src/methodchannel.c b/src/methodchannel.c index 0f21497e..4073b073 100644 --- a/src/methodchannel.c +++ b/src/methodchannel.c @@ -48,21 +48,21 @@ bool MethodChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, break; case kTypeString: - size = strlen(value->value.string_value); + size = strlen(value->string_value); *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size *p_buffer_size += size; break; case kTypeByteArray: - size = value->value.bytearray_value.size; + size = value->bytearray_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size *p_buffer_size += size; break; case kTypeIntArray: - size = value->value.intarray_value.size; + size = value->intarray_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size *p_buffer_size = (((*p_buffer_size) + 3) | 3) - 3; // 4-byte aligned @@ -70,7 +70,7 @@ bool MethodChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, break; case kTypeLongArray: - size = value->value.longarray_value.size; + size = value->longarray_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size *p_buffer_size = (((*p_buffer_size) + 7) | 7) - 7; // 8-byte aligned @@ -78,7 +78,7 @@ bool MethodChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, break; case kTypeDoubleArray: - size = value->value.doublearray_value.size; + size = value->doublearray_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write array size *p_buffer_size = (((*p_buffer_size) + 7) | 7) - 7; // 8-byte aligned @@ -86,20 +86,20 @@ bool MethodChannel_calculateValueSizeInBuffer(struct MethodChannelValue* value, break; case kTypeList: - size = value->value.list_value.size; + size = value->list_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write list size for (int i = 0; ivalue.list_value.list[i]), p_buffer_size)) return false; + if (!MethodChannel_calculateValueSizeInBuffer(&(value->list_value.list[i]), p_buffer_size)) return false; break; case kTypeMap: - size = value->value.map_value.size; + size = value->map_value.size; *p_buffer_size += (size < 254) ? 1 : (size <= 0xFFFF) ? 3 : 5; // write map size for (int i = 0; ivalue.list_value.list[i*2 ]), p_buffer_size)) return false; - if (!MethodChannel_calculateValueSizeInBuffer(&(value->value.list_value.list[i*2+1]), p_buffer_size)) return false; + if (!MethodChannel_calculateValueSizeInBuffer(&(value->list_value.list[i*2 ]), p_buffer_size)) return false; + if (!MethodChannel_calculateValueSizeInBuffer(&(value->list_value.list[i*2+1]), p_buffer_size)) return false; } break; @@ -145,31 +145,31 @@ bool MethodChannel_writeValueToBuffer(struct MethodChannelValue* value, uint8_t* case kFalse: break; case kTypeInt: - *(int32_t*) *p_buffer = value->value.int_value; + *(int32_t*) *p_buffer = value->int_value; NEXTN(*p_buffer, 4) break; case kTypeLong: - *(int64_t*) *p_buffer = value->value.long_value; + *(int64_t*) *p_buffer = value->long_value; NEXTN(*p_buffer, 8) break; case kTypeDouble: MethodChannel_alignBuffer(8, p_buffer); - *(double*) *p_buffer = value->value.double_value; + *(double*) *p_buffer = value->double_value; NEXTN(*p_buffer, 8) break; case kTypeBigInt: case kTypeString: case kTypeByteArray: if (value->type == kTypeBigInt) { - size = strlen(value->value.bigint_value); - byteArray = (uint8_t*) value->value.bigint_value; + size = strlen(value->bigint_value); + byteArray = (uint8_t*) value->bigint_value; } else if (value->type == kTypeString) { - size = strlen(value->value.string_value); - byteArray = (uint8_t*) value->value.string_value; + size = strlen(value->string_value); + byteArray = (uint8_t*) value->string_value; } else if (value->type == kTypeByteArray) { - size = value->value.bytearray_value.size; - byteArray = (uint8_t*) value->value.bytearray_value.array; + size = value->bytearray_value.size; + byteArray = (uint8_t*) value->bytearray_value.array; } MethodChannel_writeSizeValueToBuffer(size, p_buffer); @@ -179,55 +179,55 @@ bool MethodChannel_writeValueToBuffer(struct MethodChannelValue* value, uint8_t* } break; case kTypeIntArray: - size = value->value.intarray_value.size; + size = value->intarray_value.size; MethodChannel_writeSizeValueToBuffer(size, p_buffer); MethodChannel_alignBuffer(4, p_buffer); for (int i=0; ivalue.intarray_value.array[i]; + *(int32_t*) *p_buffer = value->intarray_value.array[i]; NEXTN(*p_buffer, 4) } break; case kTypeLongArray: - size = value->value.longarray_value.size; + size = value->longarray_value.size; MethodChannel_writeSizeValueToBuffer(size, p_buffer); MethodChannel_alignBuffer(8, p_buffer); for (int i=0; ivalue.longarray_value.array[i]; + *(int64_t*) *p_buffer = value->longarray_value.array[i]; NEXTN(*p_buffer, 8) } break; case kTypeDoubleArray: - size = value->value.doublearray_value.size; + size = value->doublearray_value.size; MethodChannel_writeSizeValueToBuffer(size, p_buffer); MethodChannel_alignBuffer(8, p_buffer); for (int i=0; ivalue.doublearray_value.array[i]; + *(double*) *p_buffer = value->doublearray_value.array[i]; NEXTN(*p_buffer, 8) } break; case kTypeList: - size = value->value.list_value.size; + size = value->list_value.size; MethodChannel_writeSizeValueToBuffer(size, p_buffer); for (int i=0; ivalue.list_value.list[i]), p_buffer)) return false; + if (!MethodChannel_writeValueToBuffer(&(value->list_value.list[i]), p_buffer)) return false; break; case kTypeMap: - size = value->value.map_value.size; + size = value->map_value.size; MethodChannel_writeSizeValueToBuffer(size, p_buffer); for (int i=0; ivalue.map_value.map[i*2 ]), p_buffer)) return false; - if (!MethodChannel_writeValueToBuffer(&(value->value.map_value.map[i*2+1]), p_buffer)) return false; + if (!MethodChannel_writeValueToBuffer(&(value->map_value.map[i*2 ]), p_buffer)) return false; + if (!MethodChannel_writeValueToBuffer(&(value->map_value.map[i*2+1]), p_buffer)) return false; } break; default: @@ -246,9 +246,7 @@ bool MethodChannel_call(char* channel, char* method, struct MethodChannelValue* // the method name is encoded as a String value and is the first value written to the buffer. struct MethodChannelValue method_name_value = { .type = kTypeString, - .value = { - .string_value = method - } + .string_value = method }; // calculate buffer size @@ -331,14 +329,14 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str case kTypeInt: ASSERT_RETURN_BOOL(*buffer_remaining >= 4, "Error decoding platform message: while decoding kTypeInt: message ended to soon") - value->value.int_value = *(int32_t*) *p_buffer; + value->int_value = *(int32_t*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, 4) break; case kTypeLong: ASSERT_RETURN_BOOL(*buffer_remaining >= 8, "Error decoding platform message: while decoding kTypeLong: message ended too soon") - value->value.long_value = *(int64_t*) *p_buffer; + value->long_value = *(int64_t*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, 8) break; @@ -346,7 +344,7 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str ASSERT_RETURN_BOOL(*buffer_remaining >= 8 + ALIGNMENT_DIFF(*p_buffer, 8), "Error decoding platform message: while decoding kTypeDouble: message ended too soon") ALIGN(*p_buffer, *buffer_remaining, 8) - value->value.double_value = *(double*) *p_buffer; + value->double_value = *(double*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, 8) break; @@ -360,15 +358,15 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str c_string[i] = **p_buffer; NEXT(*p_buffer, *buffer_remaining) } - value->value.string_value = c_string; + value->string_value = c_string; break; case kTypeByteArray: if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; ASSERT_RETURN_BOOL(*buffer_remaining >= size, "Error decoding platform message: while decoding kTypeByteArray: message ended too soon") - value->value.bytearray_value.size = size; - value->value.bytearray_value.array = *p_buffer; + value->bytearray_value.size = size; + value->bytearray_value.array = *p_buffer; NEXTN(*p_buffer, *buffer_remaining, size); @@ -379,8 +377,8 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str ASSERT_RETURN_BOOL(*buffer_remaining >= size*4 + ALIGNMENT_DIFF(*p_buffer, 4), "Error decoding platform message: while decoding kTypeIntArray: message ended too soon") ALIGN(*p_buffer, *buffer_remaining, 4) - value->value.intarray_value.size = size; - value->value.intarray_value.array = (int32_t*) *p_buffer; + value->intarray_value.size = size; + value->intarray_value.array = (int32_t*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, size*4) @@ -391,8 +389,8 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str ASSERT_RETURN_BOOL(*buffer_remaining >= size*8 + ALIGNMENT_DIFF(*p_buffer, 8), "Error decoding platform message: while decoding kTypeLongArray: message ended too soon") ALIGN(*p_buffer, *buffer_remaining, 8) - value->value.longarray_value.size = size; - value->value.longarray_value.array = (int64_t*) *p_buffer; + value->longarray_value.size = size; + value->longarray_value.array = (int64_t*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, size*8) @@ -403,8 +401,8 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str ASSERT_RETURN_BOOL(*buffer_remaining >= size*8 + ALIGNMENT_DIFF(*p_buffer, 8), "Error decoding platform message: while decoding kTypeIntArray: message ended too soon") ALIGN(*p_buffer, *buffer_remaining, 8) - value->value.doublearray_value.size = size; - value->value.doublearray_value.array = (double*) *p_buffer; + value->doublearray_value.size = size; + value->doublearray_value.array = (double*) *p_buffer; NEXTN(*p_buffer, *buffer_remaining, size*8) @@ -412,23 +410,23 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str case kTypeList: if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; - value->value.list_value.size = size; - value->value.list_value.list = calloc(size, sizeof(struct MethodChannelValue)); + value->list_value.size = size; + value->list_value.list = calloc(size, sizeof(struct MethodChannelValue)); for (int i = 0; i < size; i++) { - if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->value.list_value.list[i]))) return false; + if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->list_value.list[i]))) return false; } break; case kTypeMap: if (!MethodChannel_decodeSize(p_buffer, buffer_remaining, &size)) return false; - value->value.map_value.size = size; - value->value.map_value.map = calloc(size*2, sizeof(struct MethodChannelValue)); + value->map_value.size = size; + value->map_value.map = calloc(size*2, sizeof(struct MethodChannelValue)); for (int i = 0; i < size; i++) { - if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->value.list_value.list[i*2 ]))) return false; - if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->value.list_value.list[i*2+1]))) return false; + if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->list_value.list[i*2 ]))) return false; + if (!MethodChannel_decodeValue(p_buffer, buffer_remaining, &(value->list_value.list[i*2+1]))) return false; } break; @@ -439,17 +437,28 @@ bool MethodChannel_decodeValue(uint8_t** p_buffer, size_t* buffer_remaining, str return true; } -bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall* result) { +bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult) { + *presult = malloc(sizeof(struct MethodCall)); + struct MethodCall* result = *presult; + uint8_t* buffer_cursor = buffer; size_t buffer_remaining = buffer_size; + if (*buffer == (char) 123) { + result->protocol = kJSONProtocol; + fprintf(stderr, "Error decoding Method Call: JSON Protocol not supported yet.\n"); + return false; + } else { + result->protocol = kStandardProtocol; + } + struct MethodChannelValue method_name; if (!MethodChannel_decodeValue(&buffer_cursor, &buffer_remaining, &method_name)) return false; if (method_name.type != kTypeString) { fprintf(stderr, "Error decoding Method Call: expected type of first value in buffer to be string (i.e. method name), got %d\n", method_name.type); return false; } - result->method = method_name.value.string_value; + result->method = method_name.string_value; if (!MethodChannel_decodeValue(&buffer_cursor, &buffer_remaining, &(result->argument))) return false; @@ -460,30 +469,35 @@ bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall bool MethodChannel_freeValue(struct MethodChannelValue* p_value) { switch (p_value->type) { case kTypeString: - free(p_value->value.string_value); + free(p_value->string_value); break; case kTypeList: - for (int i=0; i < p_value->value.list_value.size; i++) - if (!MethodChannel_freeValue(&(p_value->value.list_value.list[i]))) return false; + for (int i=0; i < p_value->list_value.size; i++) + if (!MethodChannel_freeValue(&(p_value->list_value.list[i]))) return false; - free(p_value->value.list_value.list); + free(p_value->list_value.list); break; case kTypeMap: - for (int i=0; i< p_value->value.map_value.size; i++) { - if (!MethodChannel_freeValue(&(p_value->value.map_value.map[i*2 ]))) return false; - if (!MethodChannel_freeValue(&(p_value->value.map_value.map[i*2+1]))) return false; + for (int i=0; i< p_value->map_value.size; i++) { + if (!MethodChannel_freeValue(&(p_value->map_value.map[i*2 ]))) return false; + if (!MethodChannel_freeValue(&(p_value->map_value.map[i*2+1]))) return false; } - free(p_value->value.map_value.map); + free(p_value->map_value.map); default: break; } return true; } -bool MethodChannel_freeMethodCall(struct MethodCall* methodcall) { +bool MethodChannel_freeMethodCall(struct MethodCall **pmethodcall) { + struct MethodCall* methodcall = *pmethodcall; + free(methodcall->method); if (!MethodChannel_freeValue(&(methodcall->argument))) return false; + free(methodcall); + + *pmethodcall = NULL; return true; } diff --git a/src/methodchannel.h b/src/methodchannel.h index 6534023c..2e84f84d 100644 --- a/src/methodchannel.h +++ b/src/methodchannel.h @@ -55,17 +55,21 @@ struct MethodChannelValue { size_t size; struct MethodChannelValue* map; } map_value; - } value; + }; }; struct MethodCall { + enum { + kStandardProtocol, + kJSONProtocol + } protocol; char* method; struct MethodChannelValue argument; }; bool MethodChannel_call(char* channel, char* method, struct MethodChannelValue* argument); bool MethodChannel_respond(FlutterPlatformMessageResponseHandle* response_handle, struct MethodChannelValue* response_value); -bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall* result); -bool MethodChannel_freeMethodCall(struct MethodCall* methodcall); +bool MethodChannel_decode(size_t buffer_size, uint8_t* buffer, struct MethodCall** presult); +bool MethodChannel_freeMethodCall(struct MethodCall** pmethodcall); #endif \ No newline at end of file